package org.mtransit.android.data; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Locale; import java.util.WeakHashMap; import java.util.concurrent.TimeUnit; import org.mtransit.android.R; import org.mtransit.android.commons.CollectionUtils; import org.mtransit.android.commons.Constants; import org.mtransit.android.commons.LocationUtils; import org.mtransit.android.commons.MTLog; import org.mtransit.android.commons.SensorUtils; import org.mtransit.android.commons.TaskUtils; import org.mtransit.android.commons.ThemeUtils; import org.mtransit.android.commons.TimeUtils; import org.mtransit.android.commons.api.SupportFactory; import org.mtransit.android.commons.data.AppStatus; import org.mtransit.android.commons.data.AvailabilityPercent; import org.mtransit.android.commons.data.POI; import org.mtransit.android.commons.data.POIStatus; import org.mtransit.android.commons.data.Route; import org.mtransit.android.commons.data.RouteTripStop; import org.mtransit.android.commons.data.Schedule; import org.mtransit.android.commons.data.ServiceUpdate; import org.mtransit.android.commons.task.MTAsyncTask; import org.mtransit.android.commons.ui.widget.MTArrayAdapter; import org.mtransit.android.provider.FavoriteManager; import org.mtransit.android.task.ServiceUpdateLoader; import org.mtransit.android.task.StatusLoader; import org.mtransit.android.ui.MainActivity; import org.mtransit.android.ui.fragment.AgencyTypeFragment; import org.mtransit.android.ui.fragment.NearbyFragment; import org.mtransit.android.ui.fragment.RTSRouteFragment; import org.mtransit.android.ui.view.MTCompassView; import org.mtransit.android.ui.view.MTJPathsView; import org.mtransit.android.ui.view.MTOnClickListener; import org.mtransit.android.ui.view.MTOnItemClickListener; import org.mtransit.android.ui.view.MTOnItemLongClickListener; import org.mtransit.android.ui.view.MTOnLongClickListener; import org.mtransit.android.ui.view.MTPieChartPercentView; import android.app.Activity; import android.content.BroadcastReceiver; import android.graphics.Typeface; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.location.Location; import android.os.Handler; import android.text.TextUtils; import android.util.Pair; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.Adapter; import android.widget.AdapterView; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; public class POIArrayAdapter extends MTArrayAdapter<POIManager> implements SensorUtils.CompassListener, AdapterView.OnItemClickListener, AdapterView.OnItemLongClickListener, SensorEventListener, AbsListView.OnScrollListener, StatusLoader.StatusLoaderListener, ServiceUpdateLoader.ServiceUpdateLoaderListener, FavoriteManager.FavoriteUpdateListener, SensorUtils.SensorTaskCompleted, TimeUtils.TimeChangedReceiver.TimeChangedListener { private static final String TAG = POIArrayAdapter.class.getSimpleName(); private String tag = TAG; @Override public String getLogTag() { return tag; } public void setTag(String tag) { this.tag = TAG + "-" + tag; } public static final int TYPE_HEADER_NONE = 0; public static final int TYPE_HEADER_BASIC = 1; public static final int TYPE_HEADER_ALL_NEARBY = 2; public static final int TYPE_HEADER_MORE = 3; private LayoutInflater layoutInflater; private LinkedHashMap<Integer, ArrayList<POIManager>> poisByType; private HashSet<String> favUUIDs; private HashMap<String, Integer> favUUIDsFolderIds; private WeakReference<Activity> activityWR; private Location location; private int lastCompassInDegree = -1; private float locationDeclination; private HashSet<String> closestPoiUuids; private float[] accelerometerValues = new float[3]; private float[] magneticFieldValues = new float[3]; private boolean showStatus = true; // show times / availability private boolean showServiceUpdate = true; // show warning icon private boolean showFavorite = true; // show favorite star private boolean showBrowseHeaderSection = false; // show header with shortcut to agency type screens private int showTypeHeader = TYPE_HEADER_NONE; private boolean showTypeHeaderNearby = false; // show nearby header instead of default type header private boolean infiniteLoading = false; // infinite loading private InfiniteLoadingListener infiniteLoadingListener; private ViewGroup manualLayout; private ScrollView manualScrollView; private long lastNotifyDataSetChanged = -1l; private int scrollState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE; private long nowToTheMinute = -1l; private boolean timeChangedReceiverEnabled = false; private boolean compassUpdatesEnabled = false; private long lastCompassChanged = -1l; private FavoriteManager.FavoriteUpdateListener favoriteUpdateListener = this; public POIArrayAdapter(Activity activity) { super(activity, -1); setActivity(activity); this.layoutInflater = LayoutInflater.from(getContext()); } public void setManualLayout(ViewGroup manualLayout) { this.manualLayout = manualLayout; } public void setFavoriteUpdateListener(FavoriteManager.FavoriteUpdateListener favoriteUpdateListener) { this.favoriteUpdateListener = favoriteUpdateListener; } public void setShowStatus(boolean showData) { this.showStatus = showData; } public void setShowServiceUpdate(boolean showServiceUpdate) { this.showServiceUpdate = showServiceUpdate; } public void setShowFavorite(boolean showFavorite) { this.showFavorite = showFavorite; } public void setShowBrowseHeaderSection(boolean showBrowseHeaderSection) { this.showBrowseHeaderSection = showBrowseHeaderSection; } public void setShowTypeHeader(int showTypeHeader) { this.showTypeHeader = showTypeHeader; } public void setShowTypeHeaderNearby(boolean showTypeHeaderNearby) { this.showTypeHeaderNearby = showTypeHeaderNearby; } public void setInfiniteLoading(boolean infiniteLoading) { this.infiniteLoading = infiniteLoading; } public void setInfiniteLoadingListener(InfiniteLoadingListener infiniteLoadingListener) { this.infiniteLoadingListener = infiniteLoadingListener; } public interface InfiniteLoadingListener { boolean isLoadingMore(); boolean showingDone(); } private static final int VIEW_TYPE_COUNT = 11; /** * @see #getItemViewType(int) */ @Override public int getViewTypeCount() { return VIEW_TYPE_COUNT; } /** * @see #getViewTypeCount() */ @Override public int getItemViewType(int position) { POIManager poim = getItem(position); if (poim == null) { if (this.showBrowseHeaderSection && position == 0) { return 0; // BROWSE SECTION } if (this.infiniteLoading && position + 1 == getCount()) { return 9; // LOADING FOOTER } if (this.showTypeHeader != TYPE_HEADER_NONE) { if (this.poisByType != null) { Integer typeId = getItemTypeHeader(position); if (typeId != null) { if (FavoriteManager.isFavoriteDataSourceId(typeId)) { return 10; // TYPE FAVORITE FOLDER } return 8; // TYPE HEADER } } } MTLog.d(this, "Cannot find type for at position '%s'!", position); return Adapter.IGNORE_ITEM_VIEW_TYPE; } int type = poim.poi.getType(); int statusType = poim.getStatusType(); switch (type) { case POI.ITEM_VIEW_TYPE_TEXT_MESSAGE: return 7; // TEXT MESSAGE case POI.ITEM_VIEW_TYPE_MODULE: switch (statusType) { case POI.ITEM_STATUS_TYPE_SCHEDULE: return 5; // MODULE & APP STATUS default: return 6; // MODULE } case POI.ITEM_VIEW_TYPE_ROUTE_TRIP_STOP: switch (statusType) { case POI.ITEM_STATUS_TYPE_SCHEDULE: return 3; // RTS & SCHEDULE default: return 4; // RTS } case POI.ITEM_VIEW_TYPE_BASIC_POI: default: switch (statusType) { case POI.ITEM_STATUS_TYPE_AVAILABILITY_PERCENT: return 1; // DEFAULT & AVAILABILITY % default: return 2; // DEFAULT } } } private int count = -1; @Override public int getCount() { if (this.count < 0) { initCount(); } return this.count; } private void initCount() { this.count = 0; if (this.showBrowseHeaderSection) { this.count++; } if (this.poisByType != null) { for (Integer type : this.poisByType.keySet()) { if (this.showTypeHeader != TYPE_HEADER_NONE) { this.count++; } this.count += this.poisByType.get(type).size(); } } if (this.infiniteLoading) { this.count++; } } @Override public int getPosition(POIManager item) { int position = 0; if (this.showBrowseHeaderSection) { position++; } if (this.poisByType != null) { for (Integer type : this.poisByType.keySet()) { if (this.showTypeHeader != TYPE_HEADER_NONE) { position++; } int indexOf = this.poisByType.get(type).indexOf(item); if (indexOf >= 0) { return position + indexOf; } position += this.poisByType.get(type).size(); } } return position; } @Override public POIManager getItem(int position) { int index = 0; if (this.showBrowseHeaderSection) { index++; } if (this.poisByType != null) { for (Integer type : this.poisByType.keySet()) { if (this.showTypeHeader != TYPE_HEADER_NONE) { index++; } if (position >= index && position < index + this.poisByType.get(type).size()) { return this.poisByType.get(type).get(position - index); } index += this.poisByType.get(type).size(); } } return null; } public POIManager getItem(String uuid) { if (this.poisByType != null) { for (Integer type : this.poisByType.keySet()) { for (POIManager poim : this.poisByType.get(type)) { if (poim.poi.getUUID().equals(uuid)) { return poim; } } } } return null; } public Integer getItemTypeHeader(int position) { int index = 0; if (this.showBrowseHeaderSection) { index++; } if (this.showTypeHeader != TYPE_HEADER_NONE && this.poisByType != null) { for (Integer type : this.poisByType.keySet()) { if (index == position) { return type; } index++; index += this.poisByType.get(type).size(); } } return null; } @Override public View getView(int position, View convertView, ViewGroup parent) { POIManager poim = getItem(position); if (poim == null) { if (this.showBrowseHeaderSection && position == 0) { return getBrowseHeaderSectionView(convertView, parent); } if (this.infiniteLoading && position + 1 == getCount()) { return getInfiniteLoadingView(convertView, parent); } if (this.showTypeHeader != TYPE_HEADER_NONE) { Integer typeId = getItemTypeHeader(position); if (typeId != null) { if (FavoriteManager.isFavoriteDataSourceId(typeId)) { int favoriteFolderId = FavoriteManager.extractFavoriteFolderId(typeId); if (FavoriteManager.get(getContext()).hasFavoriteFolder(favoriteFolderId)) { return getFavoriteFolderHeaderView(FavoriteManager.get(getContext()).getFolder(favoriteFolderId), convertView, parent); } } DataSourceType dst = DataSourceType.parseId(typeId); if (dst != null) { return getTypeHeaderView(dst, convertView, parent); } } } MTLog.w(this, "getView() > Cannot create view for null poi at position '%s'!", position); return null; // CRASH!!! } switch (poim.poi.getType()) { case POI.ITEM_VIEW_TYPE_TEXT_MESSAGE: return getTextMessageView(poim, convertView, parent); case POI.ITEM_VIEW_TYPE_MODULE: return getModuleView(poim, convertView, parent); case POI.ITEM_VIEW_TYPE_ROUTE_TRIP_STOP: return getRouteTripStopView(poim, convertView, parent); case POI.ITEM_VIEW_TYPE_BASIC_POI: return getBasicPOIView(poim, convertView, parent); default: MTLog.w(this, "getView() > Unknow view type at position %s!", position); return null; // CRASH!!! } } private View getInfiniteLoadingView(View convertView, ViewGroup parent) { if (convertView == null) { convertView = this.layoutInflater.inflate(R.layout.layout_poi_infinite_loading, parent, false); InfiniteLoadingViewHolder holder = new InfiniteLoadingViewHolder(); holder.progressBar = convertView.findViewById(R.id.progress_bar); holder.worldExplored = convertView.findViewById(R.id.worldExploredTv); convertView.setTag(holder); } InfiniteLoadingViewHolder holder = (InfiniteLoadingViewHolder) convertView.getTag(); if (this.infiniteLoadingListener != null) { if (this.infiniteLoadingListener.isLoadingMore()) { holder.worldExplored.setVisibility(View.GONE); holder.progressBar.setVisibility(View.VISIBLE); convertView.setVisibility(View.VISIBLE); } else if (this.infiniteLoadingListener.showingDone()) { holder.progressBar.setVisibility(View.GONE); holder.worldExplored.setVisibility(View.VISIBLE); convertView.setVisibility(View.VISIBLE); } else { convertView.setVisibility(View.GONE); } } else { convertView.setVisibility(View.GONE); } return convertView; } private int nbAgencyTypes = -1; private View getBrowseHeaderSectionView(View convertView, ViewGroup parent) { Activity activity = this.activityWR == null ? null : this.activityWR.get(); DataSourceProvider dataSourceProvider = DataSourceProvider.get(activity); int agenciesCount = dataSourceProvider == null ? 0 : dataSourceProvider.getAllAgenciesCount(); if (convertView == null || this.nbAgencyTypes != agenciesCount) { if (convertView == null) { convertView = this.layoutInflater.inflate(R.layout.layout_poi_list_browse_header, parent, false); } LinearLayout gridLL = (LinearLayout) convertView.findViewById(R.id.gridLL); gridLL.removeAllViews(); ArrayList<DataSourceType> allAgencyTypes = dataSourceProvider == null ? null : dataSourceProvider.getAvailableAgencyTypes(); this.nbAgencyTypes = CollectionUtils.getSize(allAgencyTypes); if (allAgencyTypes == null) { gridLL.setVisibility(View.GONE); } else { int availableButtons = 0; View gridLine = null; View btn; TextView btnTv; for (final DataSourceType dst : allAgencyTypes) { if (dst.getId() == DataSourceType.TYPE_MODULE.getId() && availableButtons == 0 && allAgencyTypes.size() > 2) { continue; } if (dst.getId() == DataSourceType.TYPE_PLACE.getId()) { continue; } if (availableButtons == 0) { gridLine = this.layoutInflater.inflate(R.layout.layout_poi_list_browse_header_line, this.manualLayout, false); gridLL.addView(gridLine); availableButtons = 2; } btn = gridLine.findViewById(availableButtons == 2 ? R.id.btn1 : R.id.btn2); btnTv = (TextView) gridLine.findViewById(availableButtons == 2 ? R.id.btn1Tv : R.id.btn2Tv); btnTv.setText(dst.getAllStringResId()); if (dst.getWhiteIconResId() != -1) { btnTv.setCompoundDrawablesWithIntrinsicBounds(dst.getWhiteIconResId(), 0, 0, 0); } else { btnTv.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); } btn.setOnClickListener(new MTOnClickListener() { @Override public void onClickMT(View view) { onTypeHeaderButtonClick(TypeHeaderButtonsClickListener.BUTTON_ALL, dst); } }); btn.setVisibility(View.VISIBLE); availableButtons--; } if (gridLine != null && availableButtons == 1) { gridLine.findViewById(R.id.btn2).setVisibility(View.GONE); } gridLL.setVisibility(View.VISIBLE); } } return convertView; } private void updateCommonViewManual(int position, View convertView) { if (convertView == null || convertView.getTag() == null || !(convertView.getTag() instanceof CommonViewHolder)) { return; } CommonViewHolder holder = (CommonViewHolder) convertView.getTag(); POIManager poim = getItem(position); updateCommonView(holder, poim); updatePOIStatus(holder.statusViewHolder, poim); } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { MTOnItemClickListener.onItemClickS(parent, view, position, id, new MTOnItemClickListener() { @Override public void onItemClickMT(AdapterView<?> parent, View view, int position, long id) { showPoiViewerScreen(position); } }); } @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { return MTOnItemLongClickListener.onItemLongClickS(parent, view, position, id, new MTOnItemLongClickListener() { @Override public boolean onItemLongClickMT(AdapterView<?> parent, View view, int position, long id) { return showPoiMenu(position); } }); } public interface OnClickHandledListener { void onLeaving(); } private WeakReference<OnClickHandledListener> onClickHandledListenerWR; public void setOnClickHandledListener(OnClickHandledListener onClickHandledListener) { this.onClickHandledListenerWR = new WeakReference<POIArrayAdapter.OnClickHandledListener>(onClickHandledListener); } public interface OnPOISelectedListener { boolean onPOISelected(POIManager poim); boolean onPOILongSelected(POIManager poim); } private WeakReference<OnPOISelectedListener> onPoiSelectedListenerWR; public void setOnPoiSelectedListener(OnPOISelectedListener onPoiSelectedListener) { this.onPoiSelectedListenerWR = new WeakReference<POIArrayAdapter.OnPOISelectedListener>(onPoiSelectedListener); } public boolean showPoiViewerScreen(int position) { boolean handled = false; POIManager poim = getItem(position); if (poim != null) { OnPOISelectedListener listener = this.onPoiSelectedListenerWR == null ? null : this.onPoiSelectedListenerWR.get(); handled = listener != null && listener.onPOISelected(poim); if (!handled) { handled = showPoiViewerScreen(poim); } } return handled; } public boolean showPoiMenu(int position) { boolean handled = false; POIManager poim = getItem(position); if (poim != null) { OnPOISelectedListener listener = this.onPoiSelectedListenerWR == null ? null : this.onPoiSelectedListenerWR.get(); handled = listener != null && listener.onPOILongSelected(poim); if (!handled) { handled = showPoiMenu(poim); } } return handled; } @Override public boolean areAllItemsEnabled() { return false; // to hide divider around disabled items (list view background visible behind hidden divider) // return true; // to show divider around disabled items } @Override public boolean isEnabled(int position) { return getItemTypeHeader(position) == null; // is NOT separator } public boolean showPoiViewerScreen(POIManager poim) { if (poim == null) { return false; } Activity activity = this.activityWR == null ? null : this.activityWR.get(); if (activity == null) { return false; } OnClickHandledListener listener = this.onClickHandledListenerWR == null ? null : this.onClickHandledListenerWR.get(); return poim.onActionItemClick(activity, FavoriteManager.get(getContext()).getFavoriteFolders(), this.favoriteUpdateListener, listener); } public boolean showPoiMenu(POIManager poim) { if (poim == null) { return false; } Activity activity = this.activityWR == null ? null : this.activityWR.get(); if (activity == null) { return false; } OnClickHandledListener listener = this.onClickHandledListenerWR == null ? null : this.onClickHandledListenerWR.get(); return poim.onActionItemLongClick(activity, FavoriteManager.get(getContext()).getFavoriteFolders(), this.favoriteUpdateListener, listener); } @Override public void onFavoriteUpdated() { refreshFavorites(); } public void setPois(ArrayList<POIManager> pois) { if (this.poisByType != null) { this.poisByType.clear(); } this.poiUUID.clear(); append(pois, true); } private HashSet<String> poiUUID = new HashSet<String>(); public void appendPois(ArrayList<POIManager> pois) { boolean dataSetChanged = append(pois, false); if (dataSetChanged) { notifyDataSetChanged(); } } private boolean append(ArrayList<POIManager> pois, boolean dataSetChanged) { if (pois != null) { if (this.poisByType == null) { this.poisByType = new LinkedHashMap<Integer, ArrayList<POIManager>>(); } for (POIManager poim : pois) { if (!this.poisByType.containsKey(poim.poi.getDataSourceTypeId())) { this.poisByType.put(poim.poi.getDataSourceTypeId(), new ArrayList<POIManager>()); } if (!this.poiUUID.contains(poim.poi.getUUID())) { this.poisByType.get(poim.poi.getDataSourceTypeId()).add(poim); this.poiUUID.add(poim.poi.getUUID()); dataSetChanged = true; } } } if (dataSetChanged) { this.lastNotifyDataSetChanged = -1; // last notify was with old data initCount(); initPoisCount(); refreshFavorites(); updateClosestPoi(); } return dataSetChanged; } private void resetCounts() { this.count = -1; this.poisCount = -1; } public boolean isInitialized() { return this.poisByType != null; } private int poisCount = -1; public int getPoisCount() { if (this.poisCount < 0) { initPoisCount(); } return this.poisCount; } private void initPoisCount() { this.poisCount = 0; if (this.poisByType != null) { for (Integer type : this.poisByType.keySet()) { this.poisCount += this.poisByType.get(type).size(); } } } public boolean hasPois() { return getPoisCount() > 0; } private void updateClosestPoi() { if (getPoisCount() == 0) { this.closestPoiUuids = null; return; } this.closestPoiUuids = new HashSet<String>(); if (this.poisByType != null) { for (Integer type : this.poisByType.keySet()) { ArrayList<POIManager> orderedPoims = new ArrayList<POIManager>(this.poisByType.get(type)); if (orderedPoims.size() > 0) { CollectionUtils.sort(orderedPoims, LocationUtils.POI_DISTANCE_COMPARATOR); POIManager theClosestOne = orderedPoims.get(0); float theClosestDistance = theClosestOne.getDistance(); if (theClosestDistance > 0) { for (POIManager poim : orderedPoims) { if (poim.getDistance() <= theClosestDistance) { this.closestPoiUuids.add(poim.poi.getUUID()); continue; } break; } } } } } } public boolean hasClosestPOI() { return this.closestPoiUuids != null && this.closestPoiUuids.size() > 0; } public boolean isClosestPOI(int position) { if (this.closestPoiUuids == null) { return false; } POIManager poim = getItem(position); return poim != null && this.closestPoiUuids.contains(poim.poi.getUUID()); } public POIManager getClosestPOI() { if (this.closestPoiUuids == null || this.closestPoiUuids.size() == 0) { return null; } String closestPOIUUID = this.closestPoiUuids.iterator().next(); return getItem(closestPOIUUID); } private MTAsyncTask<Location, Void, Void> updateDistanceWithStringTask; private void updateDistances(Location currentLocation) { TaskUtils.cancelQuietly(this.updateDistanceWithStringTask, true); if (currentLocation != null && getPoisCount() > 0) { this.updateDistanceWithStringTask = new MTAsyncTask<Location, Void, Void>() { @Override public String getLogTag() { return POIArrayAdapter.class.getSimpleName() + ">updateDistanceWithStringTask"; } @Override protected Void doInBackgroundMT(Location... params) { android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND); try { if (POIArrayAdapter.this.poisByType != null) { Iterator<ArrayList<POIManager>> it = POIArrayAdapter.this.poisByType.values().iterator(); while (it.hasNext()) { if (isCancelled()) { break; } LocationUtils.updateDistanceWithString(POIArrayAdapter.this.getContext(), it.next(), params[0], this); } } } catch (Exception e) { MTLog.w(POIArrayAdapter.this, e, "Error while update POIs distance strings!"); } return null; } @Override protected void onPostExecute(Void result) { if (isCancelled()) { return; } updateClosestPoi(); notifyDataSetChanged(true); } }; TaskUtils.execute(this.updateDistanceWithStringTask, currentLocation); } } @Deprecated public void updateDistancesNowSync(Location currentLocation) { if (currentLocation != null) { if (this.poisByType != null) { Iterator<ArrayList<POIManager>> it = this.poisByType.values().iterator(); while (it.hasNext()) { ArrayList<POIManager> pois = it.next(); LocationUtils.updateDistanceWithString(getContext(), pois, currentLocation, null); } } updateClosestPoi(); } setLocation(currentLocation); } public void updateDistanceNowAsync(Location currentLocation) { this.location = null; // clear current location to force refresh setLocation(currentLocation); } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { setScrollState(scrollState); } public void setScrollState(int scrollState) { this.scrollState = scrollState; } @Override public void onStatusLoaded(POIStatus status) { if (this.showStatus) { CommonStatusViewHolder statusViewHolder = this.poiStatusViewHoldersWR.get(status.getTargetUUID()); if (statusViewHolder != null && status.getTargetUUID().equals(statusViewHolder.uuid)) { updatePOIStatus(statusViewHolder, status); } else { notifyDataSetChanged(false); } } } @Override public void onServiceUpdatesLoaded(String targetUUID, ArrayList<ServiceUpdate> serviceUpdates) { if (this.showServiceUpdate) { CommonStatusViewHolder statusViewHolder = this.poiStatusViewHoldersWR.get(targetUUID); if (statusViewHolder != null && targetUUID.equals(statusViewHolder.uuid)) { updateServiceUpdate(statusViewHolder, ServiceUpdate.isSeverityWarning(serviceUpdates)); } else { notifyDataSetChanged(false); } } } public void notifyDataSetChanged(boolean force) { notifyDataSetChanged(force, Constants.ADAPTER_NOTIFY_THRESHOLD_IN_MS); } private Handler handler = new Handler(); private Runnable notifyDataSetChangedLater = new Runnable() { @Override public void run() { notifyDataSetChanged(true); // still really need to show new data } }; public void notifyDataSetChanged(boolean force, long minAdapterThresoldInMs) { long now = TimeUtils.currentTimeMillis(); long adapterThreshold = Math.max(minAdapterThresoldInMs, Constants.ADAPTER_NOTIFY_THRESHOLD_IN_MS); if (this.scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE && (force || (now - this.lastNotifyDataSetChanged) > adapterThreshold)) { notifyDataSetChanged(); notifyDataSetChangedManual(); this.lastNotifyDataSetChanged = now; this.handler.removeCallbacks(this.notifyDataSetChangedLater); } else { if (force) { this.handler.postDelayed(this.notifyDataSetChangedLater, adapterThreshold); } } } private void notifyDataSetChangedManual() { if (this.manualLayout != null && hasPois()) { int position = 0; for (int i = 0; i < this.manualLayout.getChildCount(); i++) { View view = this.manualLayout.getChildAt(i); if (view instanceof FrameLayout) { view = ((FrameLayout) view).getChildAt(0); } Object tag = view.getTag(); if (tag != null && tag instanceof CommonViewHolder) { updateCommonViewManual(position, view); position++; } } } } public void setListView(AbsListView listView) { listView.setOnItemClickListener(this); listView.setOnItemLongClickListener(this); listView.setOnScrollListener(this); listView.setAdapter(this); } public void initManual() { if (this.manualLayout != null && hasPois()) { this.manualLayout.removeAllViews(); // clear the previous list for (int i = 0; i < getPoisCount(); i++) { if (this.manualLayout.getChildCount() > 0) { this.manualLayout.addView(this.layoutInflater.inflate(R.layout.list_view_divider, this.manualLayout, false)); } View view = getView(i, null, this.manualLayout); FrameLayout frameLayout = new FrameLayout(getContext()); frameLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); frameLayout.addView(view); View selectorView = new View(getContext()); SupportFactory.get().setBackground(selectorView, ThemeUtils.obtainStyledDrawable(getContext(), R.attr.selectableItemBackground)); selectorView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); frameLayout.addView(selectorView); final int position = i; frameLayout.setOnClickListener(new MTOnClickListener() { @Override public void onClickMT(View view) { showPoiViewerScreen(position); } }); frameLayout.setOnLongClickListener(new MTOnLongClickListener() { @Override public boolean onLongClickkMT(View view) { return showPoiMenu(position); } }); this.manualLayout.addView(frameLayout); } } } public void scrollManualScrollViewTo(int x, int y) { if (this.manualScrollView != null) { this.manualScrollView.scrollTo(x, y); } } public void setManualScrollView(ScrollView scrollView) { this.manualScrollView = scrollView; if (scrollView == null) { return; } scrollView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_SCROLL: case MotionEvent.ACTION_MOVE: setScrollState(AbsListView.OnScrollListener.SCROLL_STATE_FLING); break; case MotionEvent.ACTION_DOWN: setScrollState(AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: // scroll view can still by flying setScrollState(AbsListView.OnScrollListener.SCROLL_STATE_IDLE); break; default: MTLog.v(POIArrayAdapter.this, "Unexpected event %s", event); } return false; } }); } public void setLocation(Location newLocation) { if (newLocation != null) { if (this.location == null || LocationUtils.isMoreRelevant(getLogTag(), this.location, newLocation)) { this.location = newLocation; this.locationDeclination = SensorUtils.getLocationDeclination(this.location); if (!this.compassUpdatesEnabled) { SensorUtils.registerCompassListener(getContext(), this); this.compassUpdatesEnabled = true; } updateDistances(this.location); } } } public void onPause() { if (this.activityWR != null) { this.activityWR.clear(); this.activityWR = null; } if (this.compassUpdatesEnabled) { SensorUtils.unregisterSensorListener(getContext(), this); this.compassUpdatesEnabled = false; } this.handler.removeCallbacks(this.notifyDataSetChangedLater); TaskUtils.cancelQuietly(this.refreshFavoritesTask, true); disableTimeChangeddReceiver(); } @Override public String toString() { return new StringBuilder().append(POIArrayAdapter.class.getSimpleName()) // .append(getLogTag()) // .toString(); } public void onResume(Activity activity, Location userLocation) { setActivity(activity); this.location = null; // clear current location to force refresh setLocation(userLocation); refreshFavorites(); } public void setActivity(Activity activity) { this.activityWR = new WeakReference<Activity>(activity); } @Override public void clear() { if (this.poisByType != null) { this.poisByType.clear(); this.poisByType = null; // not initialized } resetCounts(); if (this.poiUUID != null) { this.poiUUID.clear(); } if (this.closestPoiUuids != null) { this.closestPoiUuids.clear(); this.closestPoiUuids = null; } disableTimeChangeddReceiver(); if (this.compassImgsWR != null) { this.compassImgsWR.clear(); } this.lastCompassChanged = -1; this.lastCompassInDegree = -1; this.accelerometerValues = new float[3]; this.magneticFieldValues = new float[3]; this.lastNotifyDataSetChanged = -1l; this.handler.removeCallbacks(this.notifyDataSetChangedLater); this.poiStatusViewHoldersWR.clear(); TaskUtils.cancelQuietly(this.refreshFavoritesTask, true); TaskUtils.cancelQuietly(this.updateDistanceWithStringTask, true); this.location = null; this.locationDeclination = 0f; super.clear(); } public void onDestroy() { disableTimeChangeddReceiver(); if (this.poisByType != null) { this.poisByType.clear(); this.poisByType = null; } resetCounts(); if (this.poiUUID != null) { this.poiUUID.clear(); } if (this.compassImgsWR != null) { this.compassImgsWR.clear(); } this.poiStatusViewHoldersWR.clear(); if (this.onClickHandledListenerWR != null) { this.onClickHandledListenerWR.clear(); } if (this.onPoiSelectedListenerWR != null) { this.onPoiSelectedListenerWR.clear(); } } @Override public void updateCompass(float orientation, boolean force) { if (getPoisCount() == 0) { return; } long now = TimeUtils.currentTimeMillis(); int roundedOrientation = SensorUtils.convertToPosivite360Degree((int) orientation); SensorUtils.updateCompass(force, this.location, roundedOrientation, now, this.scrollState, this.lastCompassChanged, this.lastCompassInDegree, Constants.ADAPTER_NOTIFY_THRESHOLD_IN_MS, this); } @Override public void onSensorTaskCompleted(boolean result, int roundedOrientation, long now) { if (!result) { return; } this.lastCompassInDegree = roundedOrientation; this.lastCompassChanged = now; if (!this.compassUpdatesEnabled || this.location == null || this.lastCompassInDegree < 0) { return; } if (this.compassImgsWR == null) { return; } for (WeakHashMap.Entry<MTCompassView, View> compassAndDistance : this.compassImgsWR.entrySet()) { MTCompassView compassView = compassAndDistance.getKey(); if (compassView != null && compassView.isHeadingSet()) { compassView.generateAndSetHeading(this.location, this.lastCompassInDegree, this.locationDeclination); } } } @Override public void onSensorChanged(SensorEvent se) { SensorUtils.checkForCompass(getContext(), se, this.accelerometerValues, this.magneticFieldValues, this); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } private int getTypeHeaderLayoutResId() { switch (this.showTypeHeader) { case TYPE_HEADER_BASIC: return R.layout.layout_poi_list_header; case TYPE_HEADER_MORE: return R.layout.layout_poi_list_header_with_more; case TYPE_HEADER_ALL_NEARBY: return R.layout.layout_poi_list_header_with_all_nearby; default: MTLog.w(this, "Unexected header type '%s'!", this.showTypeHeader); return R.layout.layout_poi_list_header; } } private WeakReference<TypeHeaderButtonsClickListener> typeHeaderButtonsClickListenerWR; public void setOnTypeHeaderButtonsClickListener(TypeHeaderButtonsClickListener listener) { this.typeHeaderButtonsClickListenerWR = new WeakReference<TypeHeaderButtonsClickListener>(listener); } private void onTypeHeaderButtonClick(int buttonId, DataSourceType type) { TypeHeaderButtonsClickListener listener = this.typeHeaderButtonsClickListenerWR == null ? null : this.typeHeaderButtonsClickListenerWR.get(); if (listener != null && listener.onTypeHeaderButtonClick(buttonId, type)) { return; } switch (buttonId) { case TypeHeaderButtonsClickListener.BUTTON_ALL: if (type != null) { Activity activity = this.activityWR == null ? null : this.activityWR.get(); if (activity != null) { leaving(); ((MainActivity) activity).addFragmentToStack(AgencyTypeFragment.newInstance(type.getId(), type)); } } break; case TypeHeaderButtonsClickListener.BUTTON_NEARBY: if (type != null) { Activity activity = this.activityWR == null ? null : this.activityWR.get(); if (activity != null) { leaving(); ((MainActivity) activity).addFragmentToStack(NearbyFragment.newNearbyInstance(null, type.getId())); } } break; case TypeHeaderButtonsClickListener.BUTTON_MORE: if (type != null) { Activity activity = this.activityWR == null ? null : this.activityWR.get(); if (activity != null) { leaving(); ((MainActivity) activity).addFragmentToStack(AgencyTypeFragment.newInstance(type.getId(), type)); } } break; default: MTLog.w(this, "Unexected type header button %s'' click", type); } } private void leaving() { OnClickHandledListener onClickHandledListener = this.onClickHandledListenerWR == null ? null : this.onClickHandledListenerWR.get(); if (onClickHandledListener != null) { onClickHandledListener.onLeaving(); } } private View getTypeHeaderView(final DataSourceType type, View convertView, ViewGroup parent) { if (convertView == null) { int layoutRes = getTypeHeaderLayoutResId(); convertView = this.layoutInflater.inflate(layoutRes, parent, false); TypeHeaderViewHolder holder = new TypeHeaderViewHolder(); holder.nameTv = (TextView) convertView.findViewById(R.id.name); holder.nearbyBtn = convertView.findViewById(R.id.nearbyBtn); holder.allBtn = convertView.findViewById(R.id.allBtn); holder.allBtnTv = (TextView) convertView.findViewById(R.id.allBtnTv); holder.moreBtn = convertView.findViewById(R.id.moreBtn); convertView.setTag(holder); } TypeHeaderViewHolder holder = (TypeHeaderViewHolder) convertView.getTag(); holder.nameTv.setText(this.showTypeHeaderNearby ? type.getNearbyNameResId() : type.getPoiShortNameResId()); if (type.getGrey600IconResId() != -1) { holder.nameTv.setCompoundDrawablesWithIntrinsicBounds(type.getGrey600IconResId(), 0, 0, 0); } if (holder.allBtn != null) { holder.allBtn.setOnClickListener(new MTOnClickListener() { @Override public void onClickMT(View view) { onTypeHeaderButtonClick(TypeHeaderButtonsClickListener.BUTTON_ALL, type); } }); } if (holder.nearbyBtn != null) { holder.nearbyBtn.setOnClickListener(new MTOnClickListener() { @Override public void onClickMT(View view) { onTypeHeaderButtonClick(TypeHeaderButtonsClickListener.BUTTON_NEARBY, type); } }); } if (holder.moreBtn != null) { holder.moreBtn.setOnClickListener(new MTOnClickListener() { @Override public void onClickMT(View view) { onTypeHeaderButtonClick(TypeHeaderButtonsClickListener.BUTTON_MORE, type); } }); } return convertView; } private View getFavoriteFolderHeaderView(final Favorite.Folder favoriteFolder, View convertView, ViewGroup parent) { if (convertView == null) { convertView = this.layoutInflater.inflate(R.layout.layout_poi_list_header_with_delete, parent, false); FavoriteFolderHeaderViewHolder holder = new FavoriteFolderHeaderViewHolder(); holder.nameTv = (TextView) convertView.findViewById(R.id.name); holder.renameBtn = convertView.findViewById(R.id.renameBtn); holder.deleteBtn = convertView.findViewById(R.id.deleteBtn); convertView.setTag(holder); } FavoriteFolderHeaderViewHolder holder = (FavoriteFolderHeaderViewHolder) convertView.getTag(); holder.nameTv.setText(favoriteFolder == null ? null : favoriteFolder.getName()); if (holder.renameBtn != null) { holder.renameBtn.setOnClickListener(new MTOnClickListener() { @Override public void onClickMT(View view) { FavoriteManager.showUpdateFolderDialog(getContext(), POIArrayAdapter.this.layoutInflater, favoriteFolder, POIArrayAdapter.this.favoriteUpdateListener); } }); } if (holder.deleteBtn != null) { holder.deleteBtn.setOnClickListener(new MTOnClickListener() { @Override public void onClickMT(View view) { FavoriteManager.showDeleteFolderDialog(POIArrayAdapter.this.getContext(), favoriteFolder, POIArrayAdapter.this.favoriteUpdateListener); } }); } return convertView; } private WeakHashMap<String, CommonStatusViewHolder> poiStatusViewHoldersWR = new WeakHashMap<String, CommonStatusViewHolder>(); private View getBasicPOIView(POIManager poim, View convertView, ViewGroup parent) { if (convertView == null) { convertView = this.layoutInflater.inflate(getBasicPOILayout(poim.getStatusType()), parent, false); BasicPOIViewHolder holder = new BasicPOIViewHolder(); initCommonViewHolder(holder, convertView, poim.poi.getUUID()); holder.statusViewHolder = initPOIStatusViewHolder(poim.getStatusType(), convertView); convertView.setTag(holder); } updateBasicPOIView(poim, convertView); return convertView; } private CommonStatusViewHolder initPOIStatusViewHolder(int status, View convertView) { switch (status) { case POI.ITEM_STATUS_TYPE_NONE: return null; case POI.ITEM_STATUS_TYPE_AVAILABILITY_PERCENT: return initAvailabilityPercentViewHolder(convertView); case POI.ITEM_STATUS_TYPE_SCHEDULE: return initScheduleViewHolder(convertView); case POI.ITEM_STATUS_TYPE_APP: return initAppStatusViewHolder(convertView); default: MTLog.w(this, "Unexpected status '%s' (no view holder)!", status); return null; } } private CommonStatusViewHolder initScheduleViewHolder(View convertView) { ScheduleStatusViewHolder scheduleStatusViewHolder = new ScheduleStatusViewHolder(); initCommonStatusViewHolderHolder(scheduleStatusViewHolder, convertView); scheduleStatusViewHolder.dataNextLine1Tv = (TextView) convertView.findViewById(R.id.data_next_line_1); scheduleStatusViewHolder.dataNextLine2Tv = (TextView) convertView.findViewById(R.id.data_next_line_2); return scheduleStatusViewHolder; } private CommonStatusViewHolder initAppStatusViewHolder(View convertView) { AppStatusViewHolder appStatusViewHolder = new AppStatusViewHolder(); initCommonStatusViewHolderHolder(appStatusViewHolder, convertView); appStatusViewHolder.textTv = (TextView) convertView.findViewById(R.id.textTv); return appStatusViewHolder; } private CommonStatusViewHolder initAvailabilityPercentViewHolder(View convertView) { AvailabilityPercentStatusViewHolder availabilityPercentStatusViewHolder = new AvailabilityPercentStatusViewHolder(); initCommonStatusViewHolderHolder(availabilityPercentStatusViewHolder, convertView); availabilityPercentStatusViewHolder.textTv = (TextView) convertView.findViewById(R.id.textTv); availabilityPercentStatusViewHolder.piePercentV = (MTPieChartPercentView) convertView.findViewById(R.id.pie); return availabilityPercentStatusViewHolder; } private int getBasicPOILayout(int status) { int layoutRes = R.layout.layout_poi_basic; switch (status) { case POI.ITEM_STATUS_TYPE_NONE: break; case POI.ITEM_STATUS_TYPE_AVAILABILITY_PERCENT: layoutRes = R.layout.layout_poi_basic_with_availability_percent; break; default: MTLog.w(this, "Unexpected status '%s' (basic view w/o status)!", status); break; } return layoutRes; } private WeakHashMap<MTCompassView, View> compassImgsWR = new WeakHashMap<MTCompassView, View>(); private void initCommonViewHolder(CommonViewHolder holder, View convertView, String poiUUID) { holder.view = convertView; holder.nameTv = (TextView) convertView.findViewById(R.id.name); holder.favImg = (ImageView) convertView.findViewById(R.id.fav); holder.locationTv = (TextView) convertView.findViewById(R.id.location); holder.distanceTv = (TextView) convertView.findViewById(R.id.distance); holder.compassV = (MTCompassView) convertView.findViewById(R.id.compass); } private static void initCommonStatusViewHolderHolder(CommonStatusViewHolder holder, View convertView) { holder.statusV = convertView.findViewById(R.id.status); holder.warningImg = (ImageView) convertView.findViewById(R.id.service_update_warning); } private View updateBasicPOIView(POIManager poim, View convertView) { if (convertView == null || poim == null) { return convertView; } BasicPOIViewHolder holder = (BasicPOIViewHolder) convertView.getTag(); updateCommonView(holder, poim); updatePOIStatus(holder.statusViewHolder, poim); return convertView; } private void updateAppStatus(CommonStatusViewHolder statusViewHolder, POIManager poim) { if (this.showStatus && poim != null && statusViewHolder instanceof AppStatusViewHolder) { poim.setStatusLoaderListener(this); updateAppStatus(statusViewHolder, poim.getStatus(getContext())); } else { statusViewHolder.statusV.setVisibility(View.INVISIBLE); } if (poim != null) { poim.setServiceUpdateLoaderListener(this); updateServiceUpdate(statusViewHolder, poim.isServiceUpdateWarning(getContext())); } } private void updateAppStatus(CommonStatusViewHolder statusViewHolder, POIStatus status) { AppStatusViewHolder appStatusViewHolder = (AppStatusViewHolder) statusViewHolder; if (status != null && status instanceof AppStatus) { AppStatus appStatus = (AppStatus) status; appStatusViewHolder.textTv.setText(appStatus.getStatusMsg(getContext())); appStatusViewHolder.textTv.setVisibility(View.VISIBLE); statusViewHolder.statusV.setVisibility(View.VISIBLE); } else { statusViewHolder.statusV.setVisibility(View.INVISIBLE); } } private void updateServiceUpdate(CommonStatusViewHolder statusViewHolder, Boolean isServiceUpdateWarning) { if (statusViewHolder.warningImg == null) { return; } if (this.showServiceUpdate && isServiceUpdateWarning != null) { statusViewHolder.warningImg.setVisibility(isServiceUpdateWarning ? View.VISIBLE : View.GONE); } else { statusViewHolder.warningImg.setVisibility(View.GONE); } } private void updateAvailabilityPercent(CommonStatusViewHolder statusViewHolder, POIManager poim) { if (this.showStatus && poim != null && statusViewHolder instanceof AvailabilityPercentStatusViewHolder) { poim.setStatusLoaderListener(this); updateAvailabilityPercent(statusViewHolder, poim.getStatus(getContext())); } else { statusViewHolder.statusV.setVisibility(View.INVISIBLE); } if (poim != null) { poim.setServiceUpdateLoaderListener(this); updateServiceUpdate(statusViewHolder, poim.isServiceUpdateWarning(getContext())); } } private void updateAvailabilityPercent(CommonStatusViewHolder statusViewHolder, POIStatus status) { AvailabilityPercentStatusViewHolder availabilityPercentStatusViewHolder = (AvailabilityPercentStatusViewHolder) statusViewHolder; if (status != null && status instanceof AvailabilityPercent) { AvailabilityPercent availabilityPercent = (AvailabilityPercent) status; if (!availabilityPercent.isStatusOK()) { availabilityPercentStatusViewHolder.piePercentV.setVisibility(View.GONE); availabilityPercentStatusViewHolder.textTv.setText(availabilityPercent.getStatusMsg(getContext())); availabilityPercentStatusViewHolder.textTv.setVisibility(View.VISIBLE); } else if (availabilityPercent.isShowingLowerValue()) { availabilityPercentStatusViewHolder.piePercentV.setVisibility(View.GONE); availabilityPercentStatusViewHolder.textTv.setText(availabilityPercent.getLowerValueText(getContext())); availabilityPercentStatusViewHolder.textTv.setVisibility(View.VISIBLE); } else { availabilityPercentStatusViewHolder.textTv.setVisibility(View.GONE); availabilityPercentStatusViewHolder.piePercentV.setValueColors( // availabilityPercent.getValue1Color(), // availabilityPercent.getValue1ColorBg(), // availabilityPercent.getValue2Color(), // availabilityPercent.getValue2ColorBg() // ); availabilityPercentStatusViewHolder.piePercentV.setValues(availabilityPercent.getValue1(), availabilityPercent.getValue2()); availabilityPercentStatusViewHolder.piePercentV.setVisibility(View.VISIBLE); } statusViewHolder.statusV.setVisibility(View.VISIBLE); } else { statusViewHolder.statusV.setVisibility(View.INVISIBLE); } } private int getRTSLayout(int status) { int layoutRes = R.layout.layout_poi_rts; switch (status) { case POI.ITEM_STATUS_TYPE_NONE: break; case POI.ITEM_STATUS_TYPE_SCHEDULE: if (this.showExtra) { layoutRes = R.layout.layout_poi_rts_with_schedule; } else { layoutRes = R.layout.layout_poi_basic_with_schedule; } break; default: MTLog.w(this, "Unexpected status '%s' (rts view w/o status)!", status); break; } return layoutRes; } private View getTextMessageView(POIManager poim, View convertView, ViewGroup parent) { if (convertView == null) { convertView = this.layoutInflater.inflate(R.layout.layout_poi_basic, parent, false); TextViewViewHolder holder = new TextViewViewHolder(); initCommonViewHolder(holder, convertView, poim.poi.getUUID()); convertView.setTag(holder); } updateTextMessageView(poim, convertView); return convertView; } private View updateTextMessageView(POIManager poim, View convertView) { if (convertView == null || poim == null) { return convertView; } TextViewViewHolder holder = (TextViewViewHolder) convertView.getTag(); updateCommonView(holder, poim); return convertView; } private View getModuleView(POIManager poim, View convertView, ViewGroup parent) { if (convertView == null) { convertView = this.layoutInflater.inflate(getModuleLayout(poim.getStatusType()), parent, false); ModuleViewHolder holder = new ModuleViewHolder(); initCommonViewHolder(holder, convertView, poim.poi.getUUID()); initModuleExtra(convertView, holder); holder.statusViewHolder = initPOIStatusViewHolder(poim.getStatusType(), convertView); convertView.setTag(holder); } updateModuleView(poim, convertView); return convertView; } private void initModuleExtra(View convertView, ModuleViewHolder holder) { holder.moduleExtraTypeImg = (ImageView) convertView.findViewById(R.id.extra); } private View updateModuleView(POIManager poim, View convertView) { if (convertView == null || poim == null) { return convertView; } ModuleViewHolder holder = (ModuleViewHolder) convertView.getTag(); updateCommonView(holder, poim); updateModuleExtra(poim, holder); updatePOIStatus(holder.statusViewHolder, poim); return convertView; } private void updateModuleExtra(POIManager poim, ModuleViewHolder holder) { if (this.showExtra && poim.poi != null && poim.poi instanceof Module) { Module module = (Module) poim.poi; holder.moduleExtraTypeImg.setBackgroundColor(poim.getColor(getContext())); DataSourceType moduleType = DataSourceType.parseId(module.getTargetTypeId()); if (moduleType != null) { holder.moduleExtraTypeImg.setImageResource(moduleType.getWhiteIconResId()); } else { holder.moduleExtraTypeImg.setImageResource(0); } holder.moduleExtraTypeImg.setVisibility(View.VISIBLE); } else { holder.moduleExtraTypeImg.setVisibility(View.GONE); } } private int getModuleLayout(int status) { int layoutRes = R.layout.layout_poi_module; switch (status) { case POI.ITEM_STATUS_TYPE_NONE: break; case POI.ITEM_STATUS_TYPE_APP: layoutRes = R.layout.layout_poi_module_with_app_status; break; default: MTLog.w(this, "Unexpected status '%s' (module view w/o status)!", status); break; } return layoutRes; } private View getRouteTripStopView(POIManager poim, View convertView, ViewGroup parent) { if (convertView == null) { convertView = this.layoutInflater.inflate(getRTSLayout(poim.getStatusType()), parent, false); RouteTripStopViewHolder holder = new RouteTripStopViewHolder(); initCommonViewHolder(holder, convertView, poim.poi.getUUID()); initRTSExtra(convertView, holder); holder.statusViewHolder = initPOIStatusViewHolder(poim.getStatusType(), convertView); convertView.setTag(holder); } updateRouteTripStopView(poim, convertView); return convertView; } private void initRTSExtra(View convertView, RouteTripStopViewHolder holder) { holder.rtsExtraV = convertView.findViewById(R.id.extra); holder.routeFL = convertView.findViewById(R.id.route); holder.routeShortNameTv = (TextView) convertView.findViewById(R.id.route_short_name); holder.routeTypeImg = (MTJPathsView) convertView.findViewById(R.id.route_type_img); holder.tripHeadingTv = (TextView) convertView.findViewById(R.id.trip_heading); holder.tripHeadingBg = convertView.findViewById(R.id.trip_heading_bg); } private View updateRouteTripStopView(POIManager poim, View convertView) { if (convertView == null || poim == null) { return convertView; } RouteTripStopViewHolder holder = (RouteTripStopViewHolder) convertView.getTag(); updateCommonView(holder, poim); updateRTSExtra(poim, holder); updatePOIStatus(holder.statusViewHolder, poim); return convertView; } private boolean showExtra = true; public void setShowExtra(boolean showExtra) { this.showExtra = showExtra; } private void updateRTSExtra(POIManager poim, RouteTripStopViewHolder holder) { if (poim.poi instanceof RouteTripStop) { RouteTripStop rts = (RouteTripStop) poim.poi; if (!this.showExtra || rts.getRoute() == null) { if (holder.rtsExtraV != null) { holder.rtsExtraV.setVisibility(View.GONE); } if (holder.routeFL != null) { holder.routeFL.setVisibility(View.GONE); } if (holder.tripHeadingBg != null) { holder.tripHeadingBg.setVisibility(View.GONE); } } else { final String authority = rts.getAuthority(); final Route route = rts.getRoute(); if (TextUtils.isEmpty(route.getShortName())) { holder.routeShortNameTv.setVisibility(View.INVISIBLE); if (holder.routeTypeImg.hasPaths() && poim.poi.getAuthority().equals(holder.routeTypeImg.getTag())) { holder.routeTypeImg.setVisibility(View.VISIBLE); } else { AgencyProperties agency = DataSourceProvider.get(getContext()).getAgency(getContext(), poim.poi.getAuthority()); JPaths rtsRouteLogo = agency == null ? null : agency.getLogo(getContext()); if (rtsRouteLogo != null) { holder.routeTypeImg.setJSON(rtsRouteLogo); holder.routeTypeImg.setTag(poim.poi.getAuthority()); holder.routeTypeImg.setVisibility(View.VISIBLE); } else { holder.routeTypeImg.setVisibility(View.GONE); } } } else { holder.routeTypeImg.setVisibility(View.GONE); holder.routeShortNameTv.setText(Route.setShortNameSize(route.getShortName())); holder.routeShortNameTv.setVisibility(View.VISIBLE); } holder.routeFL.setVisibility(View.VISIBLE); holder.rtsExtraV.setVisibility(View.VISIBLE); final Long tripId; if (rts.getTrip() == null) { holder.tripHeadingBg.setVisibility(View.GONE); tripId = null; } else { tripId = rts.getTrip().getId(); holder.tripHeadingTv.setText(rts.getTrip().getHeading(getContext()).toUpperCase(Locale.getDefault())); holder.tripHeadingBg.setVisibility(View.VISIBLE); } holder.rtsExtraV.setBackgroundColor(poim.getColor(getContext())); final Integer stopId = rts.getStop() == null ? null : rts.getStop().getId(); holder.rtsExtraV.setOnClickListener(new MTOnClickListener() { @Override public void onClickMT(View view) { Activity activity = POIArrayAdapter.this.activityWR == null ? null : POIArrayAdapter.this.activityWR.get(); if (activity == null || !(activity instanceof MainActivity)) { MTLog.w(POIArrayAdapter.this, "No activity available to open RTS fragment!"); return; } leaving(); ((MainActivity) activity).addFragmentToStack(RTSRouteFragment.newInstance(authority, route.getId(), tripId, stopId, route)); } }); } } } private void updatePOIStatus(CommonStatusViewHolder statusViewHolder, POIStatus status) { if (!this.showStatus || status == null || statusViewHolder == null) { if (statusViewHolder != null) { statusViewHolder.statusV.setVisibility(View.INVISIBLE); } return; } switch (status.getType()) { case POI.ITEM_STATUS_TYPE_NONE: statusViewHolder.statusV.setVisibility(View.INVISIBLE); break; case POI.ITEM_STATUS_TYPE_AVAILABILITY_PERCENT: updateAvailabilityPercent(statusViewHolder, status); break; case POI.ITEM_STATUS_TYPE_SCHEDULE: updateRTSSchedule(statusViewHolder, status); break; case POI.ITEM_STATUS_TYPE_APP: updateAppStatus(statusViewHolder, status); break; default: MTLog.w(this, "Unexpected status type '%s'!", status.getType()); statusViewHolder.statusV.setVisibility(View.INVISIBLE); } } private void updatePOIStatus(CommonStatusViewHolder statusViewHolder, POIManager poim) { if (!this.showStatus || poim == null || statusViewHolder == null) { if (statusViewHolder != null) { statusViewHolder.statusV.setVisibility(View.INVISIBLE); } return; } switch (poim.getStatusType()) { case POI.ITEM_STATUS_TYPE_NONE: statusViewHolder.statusV.setVisibility(View.INVISIBLE); break; case POI.ITEM_STATUS_TYPE_AVAILABILITY_PERCENT: updateAvailabilityPercent(statusViewHolder, poim); break; case POI.ITEM_STATUS_TYPE_SCHEDULE: updateRTSSchedule(statusViewHolder, poim); break; case POI.ITEM_STATUS_TYPE_APP: updateAppStatus(statusViewHolder, poim); break; default: MTLog.w(this, "Unexpected status type '%s'!", poim.getStatusType()); statusViewHolder.statusV.setVisibility(View.INVISIBLE); } } private void updateRTSSchedule(CommonStatusViewHolder statusViewHolder, POIManager poim) { if (this.showStatus && poim != null && statusViewHolder instanceof ScheduleStatusViewHolder) { poim.setStatusLoaderListener(this); updateRTSSchedule(statusViewHolder, poim.getStatus(getContext())); } else { statusViewHolder.statusV.setVisibility(View.INVISIBLE); } if (poim != null) { poim.setServiceUpdateLoaderListener(this); updateServiceUpdate(statusViewHolder, poim.isServiceUpdateWarning(getContext())); } } private void updateRTSSchedule(CommonStatusViewHolder statusViewHolder, POIStatus status) { CharSequence line1CS = null; CharSequence line2CS = null; if (status != null && status instanceof Schedule) { Schedule schedule = (Schedule) status; ArrayList<Pair<CharSequence, CharSequence>> lines = schedule.getStatus(getContext(), getNowToTheMinute(), TimeUnit.MINUTES.toMillis(30), null, 10, null); if (lines != null && lines.size() >= 1) { line1CS = lines.get(0).first; line2CS = lines.get(0).second; } } ScheduleStatusViewHolder scheduleStatusViewHolder = (ScheduleStatusViewHolder) statusViewHolder; scheduleStatusViewHolder.dataNextLine1Tv.setText(line1CS); scheduleStatusViewHolder.dataNextLine2Tv.setText(line2CS); scheduleStatusViewHolder.dataNextLine2Tv.setVisibility(line2CS != null && line2CS.length() > 0 ? View.VISIBLE : View.GONE); statusViewHolder.statusV.setVisibility(line1CS != null && line1CS.length() > 0 ? View.VISIBLE : View.INVISIBLE); } private long getNowToTheMinute() { if (this.nowToTheMinute < 0) { resetNowToTheMinute(); enableTimeChangedReceiver(); } return this.nowToTheMinute; } private void resetNowToTheMinute() { this.nowToTheMinute = TimeUtils.currentTimeToTheMinuteMillis(); notifyDataSetChanged(false); } @Override public void onTimeChanged() { resetNowToTheMinute(); } private final BroadcastReceiver timeChangedReceiver = new TimeUtils.TimeChangedReceiver(this); private void enableTimeChangedReceiver() { if (!this.timeChangedReceiverEnabled) { getContext().registerReceiver(timeChangedReceiver, TimeUtils.TIME_CHANGED_INTENT_FILTER); this.timeChangedReceiverEnabled = true; } } private void disableTimeChangeddReceiver() { if (this.timeChangedReceiverEnabled) { getContext().unregisterReceiver(this.timeChangedReceiver); this.timeChangedReceiverEnabled = false; this.nowToTheMinute = -1l; } } private void updateCommonView(CommonViewHolder holder, POIManager poim) { if (poim == null || poim.poi == null || holder == null) { return; } final POI poi = poim.poi; holder.uuid = poi.getUUID(); if (holder.statusViewHolder != null) { holder.statusViewHolder.uuid = holder.uuid; } if (holder.uuid != null) { this.poiStatusViewHoldersWR.put(holder.uuid, holder.statusViewHolder); } if (holder.compassV != null) { holder.compassV.setLatLng(poim.getLat(), poim.getLng()); this.compassImgsWR.put(holder.compassV, holder.distanceTv); } holder.nameTv.setText(poi.getName()); if (holder.distanceTv != null) { if (!TextUtils.isEmpty(poim.getDistanceString())) { if (!poim.getDistanceString().equals(holder.distanceTv.getText())) { holder.distanceTv.setText(poim.getDistanceString()); } holder.distanceTv.setVisibility(View.VISIBLE); } else { holder.distanceTv.setVisibility(View.GONE); holder.distanceTv.setText(null); } } if (holder.compassV != null) { if (holder.distanceTv != null && holder.distanceTv.getVisibility() == View.VISIBLE) { if (this.location != null && this.lastCompassInDegree >= 0 && this.location.getAccuracy() <= poim.getDistance()) { holder.compassV.generateAndSetHeading(this.location, this.lastCompassInDegree, this.locationDeclination); } else { holder.compassV.resetHeading(); } holder.compassV.setVisibility(View.VISIBLE); } else { holder.compassV.resetHeading(); holder.compassV.setVisibility(View.GONE); } } if (holder.locationTv != null) { if (TextUtils.isEmpty(poim.getLocation())) { holder.locationTv.setVisibility(View.GONE); holder.locationTv.setText(null); } else { holder.locationTv.setText(poim.getLocation()); holder.locationTv.setVisibility(View.VISIBLE); } } if (this.showFavorite && this.favUUIDs != null && this.favUUIDs.contains(poi.getUUID())) { holder.favImg.setVisibility(View.VISIBLE); } else { holder.favImg.setVisibility(View.GONE); } int index; if (this.closestPoiUuids != null && this.closestPoiUuids.contains(poi.getUUID())) { index = 0; } else { index = -1; } switch (index) { case 0: holder.nameTv.setTypeface(Typeface.DEFAULT_BOLD); if (holder.distanceTv != null) { holder.distanceTv.setTypeface(Typeface.DEFAULT_BOLD); } break; default: holder.nameTv.setTypeface(Typeface.DEFAULT); if (holder.distanceTv != null) { holder.distanceTv.setTypeface(Typeface.DEFAULT); } break; } } private MTAsyncTask<Integer, Void, ArrayList<Favorite>> refreshFavoritesTask; public void refreshFavorites() { if (this.refreshFavoritesTask != null && this.refreshFavoritesTask.getStatus() == MTAsyncTask.Status.RUNNING) { return; // skipped, last refresh still in progress so probably good enough } this.refreshFavoritesTask = new MTAsyncTask<Integer, Void, ArrayList<Favorite>>() { @Override public String getLogTag() { return POIArrayAdapter.class.getSimpleName() + ">refreshFavoritesTask"; } @Override protected ArrayList<Favorite> doInBackgroundMT(Integer... params) { return FavoriteManager.findFavorites(POIArrayAdapter.this.getContext()); } @Override protected void onPostExecute(ArrayList<Favorite> result) { setFavorites(result); } }; TaskUtils.execute(this.refreshFavoritesTask); } private void setFavorites(ArrayList<Favorite> favorites) { boolean newFav = false; // don't trigger update if favorites are the same boolean updatedFav = false; // don't trigger it favorites are the same OR were not set if (this.favUUIDs == null) { newFav = true; // favorite never set before updatedFav = false; // never set before so not updated } else if (CollectionUtils.getSize(favorites) != CollectionUtils.getSize(this.favUUIDs)) { newFav = true; // different size => different favorites updatedFav = true; // different size => different favorites } HashSet<String> newFavUUIDs = new HashSet<String>(); HashMap<String, Integer> newfavUUIDsFolderIds = new HashMap<String, Integer>(); if (favorites != null) { for (Favorite favorite : favorites) { String uid = favorite.getFkId(); if (!newFav && ( // (this.favUUIDs != null && !this.favUUIDs.contains(uid)) || // (this.favUUIDsFolderIds != null && this.favUUIDsFolderIds.containsKey(uid) && this.favUUIDsFolderIds.get(uid) != favorite.getFolderId()) // )) { newFav = true; updatedFav = true; } newFavUUIDs.add(uid); newfavUUIDsFolderIds.put(uid, favorite.getFolderId()); } } if (!newFav) { if (this.favUUIDsFolderIds == null) { newFav = true; // favorite never set before updatedFav = false; // never set before so not updated } else { HashSet<Integer> oldFolderIds = new HashSet<Integer>(); for (Integer folderId : this.favUUIDsFolderIds.values()) { oldFolderIds.add(folderId); } HashSet<Integer> newFolderIds = new HashSet<Integer>(); for (Integer folderId : newfavUUIDsFolderIds.values()) { newFolderIds.add(folderId); } if (CollectionUtils.getSize(oldFolderIds) != CollectionUtils.getSize(newFolderIds)) { newFav = true; // different size => different favorites updatedFav = true; // different size => different favorites } } } this.favUUIDs = newFavUUIDs; this.favUUIDsFolderIds = newfavUUIDsFolderIds; if (newFav) { notifyDataSetChanged(true); } if (updatedFav) { if (this.favoriteUpdateListener != null) { this.favoriteUpdateListener.onFavoriteUpdated(); } } } public static class InfiniteLoadingViewHolder { View progressBar; View worldExplored; } public static class ModuleViewHolder extends CommonViewHolder { ImageView moduleExtraTypeImg; } public static class RouteTripStopViewHolder extends CommonViewHolder { TextView routeShortNameTv; View routeFL; View rtsExtraV; MTJPathsView routeTypeImg; TextView tripHeadingTv; View tripHeadingBg; } public static class BasicPOIViewHolder extends CommonViewHolder { } public static class TextViewViewHolder extends CommonViewHolder { } public static class CommonViewHolder { String uuid; View view; TextView nameTv; TextView distanceTv; TextView locationTv; ImageView favImg; MTCompassView compassV; CommonStatusViewHolder statusViewHolder; } public static class AppStatusViewHolder extends CommonStatusViewHolder { TextView textTv; } public static class ScheduleStatusViewHolder extends CommonStatusViewHolder { TextView dataNextLine1Tv; TextView dataNextLine2Tv; } public static class AvailabilityPercentStatusViewHolder extends CommonStatusViewHolder { TextView textTv; MTPieChartPercentView piePercentV; } public static class CommonStatusViewHolder { String uuid; View statusV; ImageView warningImg; } public static class FavoriteFolderHeaderViewHolder { TextView nameTv; View deleteBtn; View renameBtn; } public static class TypeHeaderViewHolder { TextView nameTv; TextView allBtnTv; View allBtn; View nearbyBtn; View moreBtn; } public interface TypeHeaderButtonsClickListener { int BUTTON_MORE = 0; int BUTTON_NEARBY = 1; int BUTTON_ALL = 2; boolean onTypeHeaderButtonClick(int buttonId, DataSourceType type); } }