/** * Copyright (C) 2009 Anders Aagaard <aagaande@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.neuron.trafikanten.views.route; import java.util.ArrayList; import android.app.Activity; import android.app.ListActivity; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.Window; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.google.android.AnalyticsUtils; import com.neuron.trafikanten.HelperFunctions; import com.neuron.trafikanten.R; import com.neuron.trafikanten.dataProviders.IGenericProviderHandler; import com.neuron.trafikanten.dataProviders.trafikanten.TrafikantenRealtime; import com.neuron.trafikanten.dataSets.DeviData; import com.neuron.trafikanten.dataSets.RealtimeData; import com.neuron.trafikanten.dataSets.RouteData; import com.neuron.trafikanten.dataSets.RouteDeviData; import com.neuron.trafikanten.dataSets.RouteProposal; import com.neuron.trafikanten.dataSets.StationData; import com.neuron.trafikanten.tasks.NotificationTask; import com.neuron.trafikanten.tasks.ShowTipsTask; import com.neuron.trafikanten.views.GenericDeviCreator; import com.neuron.trafikanten.views.map.GenericMap; public class DetailedRouteView extends ListActivity { private final static String TAG = "Trafikanten-DetailedRouteView"; private RouteAdapter routeList; private RouteDeviLoader routeDeviLoader; /* * Realtime loaders: */ private RouteRealtimeLoader routeRealtimeLoader = null; /* * Context menu: */ private static final int NOTIFY_ID = Menu.FIRST; private static final int REALTIME_ID = Menu.FIRST + 1; /* * Option menu */ private final static int MAP_ID = Menu.FIRST + 1; /* * Dialogs */ private int selectedId = 0; /* * Saved instance data */ public RouteDeviData deviList; private ArrayList<RouteProposal> routeProposalList; private int proposalPosition; /* * this is a list of departure dates, it's used to store next/previous button clicks. * See the button onClick listeners for how this works. */ public final static String KEY_PROPOSALPOSITION = "proposalPosition"; public static void ShowRoute(Activity activity, ArrayList<RouteProposal> routeProposalList, RouteDeviData deviList, int proposalPosition) { final Intent intent = new Intent(activity, DetailedRouteView.class); final Bundle bundle = new Bundle(); bundle.putParcelableArrayList(RouteProposal.PARCELABLE, routeProposalList); bundle.putInt(KEY_PROPOSALPOSITION, proposalPosition); bundle.putParcelable(RouteDeviData.PARCELABLE, deviList); intent.putExtras(bundle); activity.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); /* * Analytics */ AnalyticsUtils.getInstance(this).trackPageView("/detailedRouteView"); /* * Setup the view */ setContentView(R.layout.route_detailed); routeList = new RouteAdapter(this); /* * Setup the next/previous buttons */ mNextImageView = (ImageView) findViewById(R.id.next_entry); mPrevImageView = (ImageView) findViewById(R.id.prev_entry); OnClickListener switchEntryOnClickListener = new OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { case R.id.next_entry: proposalPosition++; break; case R.id.prev_entry: proposalPosition--; break; } if (proposalPosition < 0) proposalPosition = 0; if (proposalPosition > routeProposalList.size() - 1) proposalPosition = routeProposalList.size() - 1; load(); } }; mNextImageView.setOnClickListener(switchEntryOnClickListener); mPrevImageView.setOnClickListener(switchEntryOnClickListener); /* * Load instance state */ final Bundle bundle = (savedInstanceState != null) ? savedInstanceState : getIntent().getExtras(); routeProposalList = bundle.getParcelableArrayList(RouteProposal.PARCELABLE); proposalPosition = bundle.getInt(KEY_PROPOSALPOSITION); deviList = bundle.getParcelable(RouteDeviData.PARCELABLE); load(); registerForContextMenu(getListView()); loadDevi(); new ShowTipsTask(this, DetailedRouteView.class.getName(), R.string.tipDetailedRoute, 44); } /* * Load devi, this shouldn't be needed, but might be if a station got loaded too fast. */ private void loadDevi() { routeDeviLoader = new RouteDeviLoader(this, deviList, new IGenericProviderHandler<Void>() { @Override public void onData(Void data) {} @Override public void onExtra(int i, Object data) {} @Override public void onPostExecute(Exception e) { Log.e(TAG,"onPostExecute routeDeviLoader " + e); Toast.makeText(DetailedRouteView.this, R.string.trafikantenErrorOther, Toast.LENGTH_SHORT).show(); routeDeviLoader = null; routeList.notifyDataSetChanged(); loadDevi(); } @Override public void onPreExecute() {} }); if (routeDeviLoader.load(routeProposalList.get(proposalPosition))) { setProgressBarIndeterminateVisibility(true); } else { setProgressBarIndeterminateVisibility(false); } } /* * NEXT/PREVIOUS CODE STOLEN FROM ANDROID GALLERY APP STARTS HERE. */ private ImageView mNextImageView; private ImageView mPrevImageView; private boolean mPaused = false; private Handler mHandler = new Handler(); private final Animation mHideNextImageViewAnimation = new AlphaAnimation(1F, 0F); private final Animation mHidePrevImageViewAnimation = new AlphaAnimation(1F, 0F); private final Animation mShowNextImageViewAnimation = new AlphaAnimation(0F, 1F); private final Animation mShowPrevImageViewAnimation = new AlphaAnimation(0F, 1F); @Override public boolean dispatchTouchEvent(MotionEvent ev) { showOnScreenControls(); scheduleDismissOnScreenControls(); return super.dispatchTouchEvent(ev); } private void scheduleDismissOnScreenControls() { mHandler.removeCallbacks(mDismissOnScreenControlRunner); mHandler.postDelayed(mDismissOnScreenControlRunner, 2000); } private final Runnable mDismissOnScreenControlRunner = new Runnable() { public void run() { hideOnScreenControls(); } }; private void hideOnScreenControls() { if (mNextImageView.getVisibility() == View.VISIBLE) { Animation a = mHideNextImageViewAnimation; a.setDuration(500); mNextImageView.startAnimation(a); mNextImageView.setVisibility(View.INVISIBLE); } if (mPrevImageView.getVisibility() == View.VISIBLE) { Animation a = mHidePrevImageViewAnimation; a.setDuration(500); mPrevImageView.startAnimation(a); mPrevImageView.setVisibility(View.INVISIBLE); } } private void showOnScreenControls() { if (mPaused) return; boolean showPrev = proposalPosition > 0; boolean showNext = proposalPosition < routeProposalList.size() - 1; boolean prevIsVisible = mPrevImageView.getVisibility() == View.VISIBLE; boolean nextIsVisible = mNextImageView.getVisibility() == View.VISIBLE; if (showPrev && !prevIsVisible) { Animation a = mShowPrevImageViewAnimation; a.setDuration(500); mPrevImageView.startAnimation(a); mPrevImageView.setVisibility(View.VISIBLE); } else if (!showPrev && prevIsVisible) { Animation a = mHidePrevImageViewAnimation; a.setDuration(500); mPrevImageView.startAnimation(a); mPrevImageView.setVisibility(View.GONE); } if (showNext && !nextIsVisible) { Animation a = mShowNextImageViewAnimation; a.setDuration(500); mNextImageView.startAnimation(a); mNextImageView.setVisibility(View.VISIBLE); } else if (!showNext && nextIsVisible) { Animation a = mHideNextImageViewAnimation; a.setDuration(500); mNextImageView.startAnimation(a); mNextImageView.setVisibility(View.GONE); } } @Override protected void onResume() { super.onResume(); routeList.notifyDataSetChanged(); // force a refresh of the view, to repaint realtime. } @Override protected void onStart() { mPaused = false; super.onStart(); } @Override protected void onStop() { mPaused = true; if (routeRealtimeLoader != null) { routeRealtimeLoader.kill(); } super.onStop(); } /* * NEXT/PREVIOUS CODE STOLEN FROM ANDROID GALLERY APP END HERE. */ /* * Options menu, visible on menu button. * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu) */ @Override public boolean onCreateOptionsMenu(Menu menu) { final MenuItem map = menu.add(0, MAP_ID, 0, R.string.map); map.setIcon(android.R.drawable.ic_menu_mapmode); return true; } /* * Options menu item selected, options menu visible on menu button. * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem) */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()) { case MAP_ID: GenericMap.Show(this, routeProposalList.get(proposalPosition).travelStageList, true, 0); break; } return super.onOptionsItemSelected(item); } /* * Load station data */ private void load() { final ArrayList<RouteData> list = routeProposalList.get(proposalPosition).travelStageList; routeList.setList(list); setListAdapter(routeList); // this forces a redraw of onscreen controls showOnScreenControls(); } /* * onCreate - Context menu is a popup from a longpress on a list item. * @see android.app.Activity#onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo) */ @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); menu.add(0, NOTIFY_ID, 0, R.string.alarm); final RouteData routeData = (RouteData) routeList.getItem(selectedId); if (routeData.fromStation.realtimeStop) { menu.add(0, REALTIME_ID, 0, R.string.realtime); } } /* * Load realtime data for a shown route */ private void loadRealtimeData(RouteData routeData) { if (routeData.canRealtime()) { if (routeRealtimeLoader == null) { routeRealtimeLoader = new RouteRealtimeLoader(this, routeList); } routeRealtimeLoader.load(routeData.fromStation, routeData); } } @Override protected void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); selectedId = position; final RouteData routeData = (RouteData) routeList.getItem(selectedId); if (routeData.realtimeData == null) { loadRealtimeData(routeData); } else { routeData.realtimeData = null; routeList.notifyDataSetChanged(); } } /* * onSelected - Context menu is a popup from a longpress on a list item. * @see android.app.Activity#onContextItemSelected(android.view.MenuItem) */ @Override public boolean onContextItemSelected(MenuItem item) { final AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); selectedId = info.position; final RouteData routeData = (RouteData) routeList.getItem(selectedId); switch(item.getItemId()) { case NOTIFY_ID: final String notifyWith = routeData.lineName.equals(routeData.destination) ? routeData.lineName : routeData.lineName + " " + routeData.destination; new NotificationTask(this, routeProposalList, proposalPosition, deviList, routeData.departure, notifyWith); return true; case REALTIME_ID: loadRealtimeData(routeData); } return super.onContextItemSelected(item); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelableArrayList(RouteProposal.PARCELABLE, routeProposalList); outState.putInt(KEY_PROPOSALPOSITION, proposalPosition); outState.putParcelable(RouteDeviData.PARCELABLE, deviList); } @Override protected void onDestroy() { if (routeDeviLoader != null) { routeDeviLoader.kill(); routeDeviLoader = null; } super.onDestroy(); } } class RouteRealtimeLoader { private static final String TAG = "Trafikanten-RouteRealtimeLoader"; private TrafikantenRealtime realtimeProvider = null; private Activity activity; private RouteAdapter routeList; private int searchLine; private String searchDestination; public RouteRealtimeLoader(Activity activity, RouteAdapter routeList) { this.activity = activity; this.routeList = routeList; } public void load(StationData station, final RouteData routeData) { kill(); /* * Filter out [T-bane] and such */ searchLine = routeData.lineId; int bracket = routeData.destination.indexOf('['); if (bracket > 0) { searchDestination = routeData.destination.subSequence(0, bracket - 1).toString(); } else { searchDestination = routeData.destination; } AnalyticsUtils.getInstance(activity).trackEvent("Data", "Realtime", "Data", 0); realtimeProvider = new TrafikantenRealtime(activity, station.stationId); realtimeProvider.start(new IGenericProviderHandler<RealtimeData>() { @Override public void onExtra(int what, Object obj) { switch (what) { case TrafikantenRealtime.MSG_TIMEDATA: routeData.timeDifference = (Long) obj; break; } } @Override public void onData(RealtimeData item) { //Log.i("DEBUG CODE","Comparing '" + item.line + "' == '" + searchLine + "' and '" + item.destination + "' == '" + searchDestination + "'"); if (item.lineId == searchLine && item.destination.equals(searchDestination)) { if (routeData.realtimeData == null) { routeData.realtimeData = item; routeList.notifyDataSetChanged(); } else { routeData.realtimeData.addDeparture(item); routeList.notifyDataSetChanged(); } } } @Override public void onPostExecute(Exception exception) { activity.setProgressBarIndeterminateVisibility(false); realtimeProvider = null; if (exception != null) { Log.w(TAG,"onException " + exception); if (exception.getClass().getSimpleName().equals("ParseException")) { Toast.makeText(activity,R.string.trafikantenErrorParse, Toast.LENGTH_LONG).show(); } else { Toast.makeText(activity,R.string.trafikantenErrorOther, Toast.LENGTH_LONG).show(); } } else { /* * No exceptions */ if (routeData.realtimeData == null) { /* * Show info text if view is empty */ Toast.makeText(activity,R.string.realtimeEmpty, Toast.LENGTH_LONG).show(); } } routeList.notifyDataSetChanged(); } @Override public void onPreExecute() { activity.setProgressBarIndeterminateVisibility(true); } }); } public void kill() { if (realtimeProvider != null) { realtimeProvider.kill(); } } } class RouteAdapter extends BaseAdapter { private LayoutInflater inflater; private ArrayList<RouteData> items = new ArrayList<RouteData>(); private DetailedRouteView parent; public RouteAdapter(DetailedRouteView parent) { inflater = LayoutInflater.from(parent); this.parent = parent; } /* * Simple functions dealing with adding/setting items. */ public ArrayList<RouteData> getList() { return items; } public void setList(ArrayList<RouteData> items) { this.items = items; } public void clear() { items.clear(); } /* * Standard android.widget.Adapter items, self explanatory. */ @Override public int getCount() { return items.size(); } @Override public Object getItem(int pos) { return items.get(pos); } @Override public long getItemId(int pos) { return pos; } /* * Setup the view * @see android.widget.Adapter#getView(int, android.view.View, android.view.ViewGroup) */ @Override public View getView(int pos, View convertView, ViewGroup arg2) { /* * Setup holder, for performance and readability. */ ViewHolder holder; if (convertView == null) { /* * New view, inflate and setup holder. */ convertView = inflater.inflate(R.layout.route_detailed_list, null); holder = new ViewHolder(); holder.symbol = (ImageView) convertView.findViewById(R.id.symbol); holder.line = (TextView) convertView.findViewById(R.id.line); holder.transportDestination = (TextView) convertView.findViewById(R.id.transportDestination); holder.from = (TextView) convertView.findViewById(R.id.from); holder.fromTime = (TextView) convertView.findViewById(R.id.fromTime); holder.to = (TextView) convertView.findViewById(R.id.to); holder.toTime = (TextView) convertView.findViewById(R.id.toTime); holder.waittime = (TextView) convertView.findViewById(R.id.waittime); holder.devi = (LinearLayout) convertView.findViewById(R.id.devi); holder.realtimeText = (TextView) convertView.findViewById(R.id.realtimeText); holder.departures = (TextView) convertView.findViewById(R.id.departures); //holder.departurePlatform = (TextView) convertView.findViewById(R.id.departurePlatform); convertView.setTag(holder); } else { /* * Old view found, we can reuse that instead of inflating. */ holder = (ViewHolder) convertView.getTag(); } /* * Render data to view. */ final RouteData routeData = items.get(pos); if (routeData.transportType == R.drawable.icon_line_walk) { holder.transportDestination.setText(R.string.walk); holder.line.setVisibility(View.GONE); } else { holder.transportDestination.setText(routeData.destination); holder.line.setVisibility(View.VISIBLE); } holder.line.setText(routeData.lineName); holder.from.setText(routeData.fromStation.stopName); holder.fromTime.setText(HelperFunctions.hourFormater.format(routeData.departure)); holder.to.setText(routeData.toStation.stopName); holder.toTime.setText(HelperFunctions.hourFormater.format(routeData.arrival)); /* * Setup symbol. */ final int symbolImage = routeData.transportType; if (symbolImage > 0) { holder.symbol.setVisibility(View.VISIBLE); holder.symbol.setImageResource(symbolImage); } else { holder.symbol.setVisibility(View.GONE); } /* * Setup realtime button */ if (routeData.canRealtime()) { holder.realtimeText.setVisibility(View.VISIBLE); } else { holder.realtimeText.setVisibility(View.GONE); } /* * Setup realtime view */ if (routeData.realtimeData != null) { routeData.realtimeData.renderDepartures(holder.departures, parent, System.currentTimeMillis() - routeData.timeDifference); holder.departures.setVisibility(View.VISIBLE); } else { holder.departures.setVisibility(View.GONE); //holder.departurePlatform.setVisibility(View.GONE); } /* * Setup waittime */ if (routeData.waitTime > 0) { holder.waittime.setText("" + parent.getText(R.string.waitTime) + " : " + routeData.waitTime + " min"); holder.waittime.setVisibility(View.VISIBLE); } else { holder.waittime.setVisibility(View.GONE); } /* * Setup devi */ final String deviKey = parent.deviList.getDeviKey(routeData.fromStation.stationId, routeData.lineId); final ArrayList<DeviData> deviList = parent.deviList.items.get(deviKey); holder.devi.removeAllViews(); if (deviList != null) { /* * We have devi, add them all to the devi list. */ for (DeviData deviData : deviList) { holder.devi.addView(GenericDeviCreator.createDefaultDeviText(parent, deviData.title, deviData, true), new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT)); } } return convertView; } /* * Class for caching the view. */ static class ViewHolder { ImageView symbol; TextView line; TextView transportDestination; TextView from; TextView fromTime; TextView to; TextView toTime; TextView waittime; TextView realtimeText; TextView departures; //TextView departurePlatform; LinearLayout devi; } }