Reorganized code

Implemented countdowns on routes list screen
Implemented offline storage of last boarded departure selection
This commit is contained in:
Doug Keen 2012-09-25 14:31:06 -07:00
parent fae89f4c45
commit f12860d87e
26 changed files with 1647 additions and 1161 deletions

View File

@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8

View File

@ -18,7 +18,8 @@
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme" > android:theme="@style/AppTheme" >
<activity <activity
android:name="RoutesListActivity" android:name=".activities.RoutesListActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name" > android:label="@string/app_name" >
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -43,7 +44,7 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name="ViewDeparturesActivity" android:name=".activities.ViewDeparturesActivity"
android:label="@string/departures" > android:label="@string/departures" >
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -55,7 +56,7 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name="ViewMapActivity" android:name=".activities.ViewMapActivity"
android:label="@string/system_map" > android:label="@string/system_map" >
</activity> </activity>
@ -66,14 +67,14 @@
android:label="BartRunner data provider" /> android:label="BartRunner data provider" />
<service <service
android:name=".NotificationService" android:name=".services.NotificationService"
android:exported="false" /> android:exported="false" />
<service <service
android:name=".EtdService" android:name=".services.EtdService"
android:exported="false" /> android:exported="false" />
<receiver <receiver
android:name=".AlarmBroadcastReceiver" android:name=".receivers.AlarmBroadcastReceiver"
android:exported="false" > android:exported="false" >
<intent-filter> <intent-filter>
<action android:name="com.dougkeen.action.ALARM" /> <action android:name="com.dougkeen.action.ALARM" />

View File

@ -1,23 +1,33 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bart="http://schemas.android.com/apk/res/com.dougkeen.bart"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:paddingLeft="5dp" android:paddingLeft="5dp"
android:paddingRight="5dp" > android:paddingRight="5dp" >
<TextView <com.dougkeen.bart.controls.CountdownTextView
android:id="@+id/fareText" android:id="@+id/countdownText"
style="@style/FareTextView" style="@style/DepartureCountdownText"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_centerVertical="true" android:layout_alignParentTop="true"
android:maxLines="3" /> android:gravity="right"
android:width="90dp"
bart:tickInterval="1" />
<com.dougkeen.bart.controls.TimedTextSwitcher
android:id="@+id/uncertainty"
style="@style/DepartureUncertaintyText"
android:layout_alignParentRight="true"
android:layout_below="@id/countdownText"
bart:tickInterval="1" />
<TextView <TextView
android:id="@+id/originText" android:id="@+id/originText"
style="@style/FavoriteListingTextView" style="@style/FavoriteListingTextView"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_toLeftOf="@id/fareText" android:layout_toLeftOf="@id/countdownText"
android:text="Origin" /> android:text="Origin" />
<TextView <TextView
@ -32,7 +42,7 @@
android:id="@+id/destinationText" android:id="@+id/destinationText"
style="@style/FavoriteListingTextView" style="@style/FavoriteListingTextView"
android:layout_below="@id/originText" android:layout_below="@id/originText"
android:layout_toLeftOf="@id/fareText" android:layout_toLeftOf="@id/countdownText"
android:layout_toRightOf="@id/to" android:layout_toRightOf="@id/to"
android:text="Destination" /> android:text="Destination" />

View File

@ -17,6 +17,14 @@
android:layout_weight="1" android:layout_weight="1"
android:visibility="gone" /> android:visibility="gone" />
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:layout_weight="1000" />
<FrameLayout <FrameLayout
style="ButtonBar" style="ButtonBar"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/DepartureUncertaintyText" />

View File

@ -4,6 +4,7 @@
<string name="app_name">BART Runner</string> <string name="app_name">BART Runner</string>
<string name="favorite_routes">Favorite routes</string> <string name="favorite_routes">Favorite routes</string>
<string name="empty_favorites_list_message">No favorite routes have been added yet</string> <string name="empty_favorites_list_message">No favorite routes have been added yet</string>
<string name="loading_message">Loading, please wait...</string>
<string name="add_route">Add a route</string> <string name="add_route">Add a route</string>
<string name="origin">Origin</string> <string name="origin">Origin</string>
<string name="destination">Destination</string> <string name="destination">Destination</string>

View File

@ -1,12 +1,27 @@
package com.dougkeen.bart; package com.dougkeen.bart;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import android.app.Application; import android.app.Application;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.os.Parcel;
import android.util.Log;
import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Departure; import com.dougkeen.bart.model.Departure;
import com.dougkeen.util.Observable; import com.dougkeen.util.Observable;
public class BartRunnerApplication extends Application { public class BartRunnerApplication extends Application {
private static final int FIVE_MINUTES = 5 * 60 * 1000;
private static final String CACHE_FILE_NAME = "lastBoardedDeparture";
private Departure mBoardedDeparture; private Departure mBoardedDeparture;
private Observable<Boolean> mAlarmPending = new Observable<Boolean>(false); private Observable<Boolean> mAlarmPending = new Observable<Boolean>(false);
@ -26,11 +41,73 @@ public class BartRunnerApplication extends Application {
} }
public Departure getBoardedDeparture() { public Departure getBoardedDeparture() {
if (mBoardedDeparture == null) {
// see if there's a saved one
File cachedDepartureFile = new File(getCacheDir(), CACHE_FILE_NAME);
if (cachedDepartureFile.exists()) {
InputStream inputStream = null;
try {
inputStream = new FileInputStream(cachedDepartureFile);
byte[] byteArray = IOUtils.toByteArray(inputStream);
Parcel parcel = Parcel.obtain();
parcel.unmarshall(byteArray, 0, byteArray.length);
parcel.setDataPosition(0);
Departure lastBoardedDeparture = Departure.CREATOR
.createFromParcel(parcel);
/*
* Check if the cached one is relatively recent. If so,
* restore that to the application context
*/
long now = System.currentTimeMillis();
if (lastBoardedDeparture.getEstimatedArrivalTime() >= now
- FIVE_MINUTES
|| lastBoardedDeparture.getMeanEstimate() >= now
- 2 * FIVE_MINUTES) {
mBoardedDeparture = lastBoardedDeparture;
}
} catch (Exception e) {
Log.w(Constants.TAG,
"Couldn't read or unmarshal lastBoardedDeparture file",
e);
try {
cachedDepartureFile.delete();
} catch (SecurityException anotherException) {
Log.w(Constants.TAG,
"Couldn't delete lastBoardedDeparture file",
anotherException);
}
} finally {
IOUtils.closeQuietly(inputStream);
}
}
}
return mBoardedDeparture; return mBoardedDeparture;
} }
public void setBoardedDeparture(Departure boardedDeparture) { public void setBoardedDeparture(Departure boardedDeparture) {
this.mBoardedDeparture = boardedDeparture; if (!ObjectUtils.equals(boardedDeparture, mBoardedDeparture)
|| ObjectUtils.compare(mBoardedDeparture, boardedDeparture) != 0) {
this.mBoardedDeparture = boardedDeparture;
if (mBoardedDeparture != null) {
File cachedDepartureFile = new File(getCacheDir(),
CACHE_FILE_NAME);
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(cachedDepartureFile);
Parcel parcel = Parcel.obtain();
mBoardedDeparture.writeToParcel(parcel, 0);
fileOutputStream.write(parcel.marshall());
} catch (Exception e) {
Log.w(Constants.TAG,
"Couldn't write last boarded departure cache file",
e);
} finally {
IOUtils.closeQuietly(fileOutputStream);
}
}
}
} }
public boolean isAlarmSounding() { public boolean isAlarmSounding() {

View File

@ -1,131 +1,132 @@
package com.dougkeen.bart; package com.dougkeen.bart.activities;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor; import android.content.SharedPreferences.Editor;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.Toast; import android.widget.Toast;
import com.WazaBe.HoloEverywhere.AlertDialog; import com.WazaBe.HoloEverywhere.AlertDialog;
import com.dougkeen.bart.model.Station; import com.dougkeen.bart.R;
import com.dougkeen.bart.model.Station;
public abstract class AbstractRouteSelectionFragment extends DialogFragment {
public abstract class AbstractRouteSelectionFragment extends DialogFragment {
private static final String KEY_LAST_SELECTED_DESTINATION = "lastSelectedDestination";
private static final String KEY_LAST_SELECTED_ORIGIN = "lastSelectedOrigin"; private static final String KEY_LAST_SELECTED_DESTINATION = "lastSelectedDestination";
protected String mTitle; private static final String KEY_LAST_SELECTED_ORIGIN = "lastSelectedOrigin";
protected String mTitle;
public AbstractRouteSelectionFragment(String title) {
super(); public AbstractRouteSelectionFragment(String title) {
mTitle = title; super();
} mTitle = title;
}
@Override
public void onStart() { @Override
super.onStart(); public void onStart() {
super.onStart();
SharedPreferences preferences = getActivity().getPreferences(
Context.MODE_PRIVATE); SharedPreferences preferences = getActivity().getPreferences(
Context.MODE_PRIVATE);
final int lastSelectedOriginPosition = preferences.getInt(
KEY_LAST_SELECTED_ORIGIN, 0); final int lastSelectedOriginPosition = preferences.getInt(
final int lastSelectedDestinationPosition = preferences.getInt( KEY_LAST_SELECTED_ORIGIN, 0);
KEY_LAST_SELECTED_DESTINATION, 1); final int lastSelectedDestinationPosition = preferences.getInt(
KEY_LAST_SELECTED_DESTINATION, 1);
final Dialog dialog = getDialog();
final FragmentActivity activity = getActivity(); final Dialog dialog = getDialog();
ArrayAdapter<Station> originSpinnerAdapter = new ArrayAdapter<Station>( final FragmentActivity activity = getActivity();
activity, android.R.layout.simple_spinner_item, ArrayAdapter<Station> originSpinnerAdapter = new ArrayAdapter<Station>(
Station.getStationList()); activity, android.R.layout.simple_spinner_item,
originSpinnerAdapter Station.getStationList());
.setDropDownViewResource(R.layout.simple_spinner_dropdown_item); originSpinnerAdapter
final Spinner originSpinner = (Spinner) dialog .setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
.findViewById(R.id.origin_spinner); final Spinner originSpinner = (Spinner) dialog
originSpinner.setAdapter(originSpinnerAdapter); .findViewById(R.id.origin_spinner);
originSpinner.setSelection(lastSelectedOriginPosition); originSpinner.setAdapter(originSpinnerAdapter);
originSpinner.setSelection(lastSelectedOriginPosition);
ArrayAdapter<Station> destinationSpinnerAdapter = new ArrayAdapter<Station>(
activity, android.R.layout.simple_spinner_item, ArrayAdapter<Station> destinationSpinnerAdapter = new ArrayAdapter<Station>(
Station.getStationList()); activity, android.R.layout.simple_spinner_item,
destinationSpinnerAdapter Station.getStationList());
.setDropDownViewResource(R.layout.simple_spinner_dropdown_item); destinationSpinnerAdapter
.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
final Spinner destinationSpinner = (Spinner) dialog
.findViewById(R.id.destination_spinner); final Spinner destinationSpinner = (Spinner) dialog
destinationSpinner.setAdapter(destinationSpinnerAdapter); .findViewById(R.id.destination_spinner);
destinationSpinner.setSelection(lastSelectedDestinationPosition); destinationSpinner.setAdapter(destinationSpinnerAdapter);
} destinationSpinner.setSelection(lastSelectedDestinationPosition);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) { @Override
final FragmentActivity activity = getActivity(); public Dialog onCreateDialog(Bundle savedInstanceState) {
final FragmentActivity activity = getActivity();
return new AlertDialog.Builder(activity)
.setTitle(mTitle) return new AlertDialog.Builder(activity)
.setCancelable(true) .setTitle(mTitle)
.setView(R.layout.route_form) .setCancelable(true)
.setPositiveButton(R.string.ok, .setView(R.layout.route_form)
new DialogInterface.OnClickListener() { .setPositiveButton(R.string.ok,
@Override new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, @Override
int which) { public void onClick(DialogInterface dialog,
handleOkClick(); int which) {
} handleOkClick();
}) }
.setNegativeButton(R.string.cancel, })
new DialogInterface.OnClickListener() { .setNegativeButton(R.string.cancel,
public void onClick(DialogInterface dialog, new DialogInterface.OnClickListener() {
int whichButton) { public void onClick(DialogInterface dialog,
dialog.cancel(); int whichButton) {
} dialog.cancel();
}).create(); }
} }).create();
}
protected void handleOkClick() {
final Dialog dialog = getDialog(); protected void handleOkClick() {
final Spinner originSpinner = (Spinner) dialog final Dialog dialog = getDialog();
.findViewById(R.id.origin_spinner); final Spinner originSpinner = (Spinner) dialog
final Spinner destinationSpinner = (Spinner) dialog .findViewById(R.id.origin_spinner);
.findViewById(R.id.destination_spinner); final Spinner destinationSpinner = (Spinner) dialog
.findViewById(R.id.destination_spinner);
Station origin = (Station) originSpinner.getSelectedItem();
Station destination = (Station) destinationSpinner.getSelectedItem(); Station origin = (Station) originSpinner.getSelectedItem();
if (origin == null) { Station destination = (Station) destinationSpinner.getSelectedItem();
Toast.makeText(dialog.getContext(), if (origin == null) {
com.dougkeen.bart.R.string.error_null_origin, Toast.makeText(dialog.getContext(),
Toast.LENGTH_LONG).show(); com.dougkeen.bart.R.string.error_null_origin,
return; Toast.LENGTH_LONG).show();
} return;
if (destination == null) { }
Toast.makeText(dialog.getContext(), if (destination == null) {
com.dougkeen.bart.R.string.error_null_destination, Toast.makeText(dialog.getContext(),
Toast.LENGTH_LONG).show(); com.dougkeen.bart.R.string.error_null_destination,
return; Toast.LENGTH_LONG).show();
} return;
if (origin.equals(destination)) { }
Toast.makeText( if (origin.equals(destination)) {
dialog.getContext(), Toast.makeText(
com.dougkeen.bart.R.string.error_matching_origin_and_destination, dialog.getContext(),
Toast.LENGTH_LONG).show(); com.dougkeen.bart.R.string.error_matching_origin_and_destination,
return; Toast.LENGTH_LONG).show();
} return;
}
final Editor prefsEditor = getActivity().getPreferences(
Context.MODE_PRIVATE).edit(); final Editor prefsEditor = getActivity().getPreferences(
prefsEditor.putInt(KEY_LAST_SELECTED_ORIGIN, Context.MODE_PRIVATE).edit();
originSpinner.getSelectedItemPosition()); prefsEditor.putInt(KEY_LAST_SELECTED_ORIGIN,
prefsEditor.putInt(KEY_LAST_SELECTED_DESTINATION, originSpinner.getSelectedItemPosition());
destinationSpinner.getSelectedItemPosition()); prefsEditor.putInt(KEY_LAST_SELECTED_DESTINATION,
prefsEditor.commit(); destinationSpinner.getSelectedItemPosition());
prefsEditor.commit();
onOkButtonClick(origin, destination);
} onOkButtonClick(origin, destination);
}
abstract protected void onOkButtonClick(Station origin, Station destination);
abstract protected void onOkButtonClick(Station origin, Station destination);
} }

View File

@ -1,55 +1,56 @@
package com.dougkeen.bart; package com.dougkeen.bart.activities;
import android.content.ContentValues; import android.content.ContentValues;
import android.view.View; import android.view.View;
import android.widget.CheckBox; import android.widget.CheckBox;
import com.dougkeen.bart.data.RoutesColumns; import com.dougkeen.bart.R;
import com.dougkeen.bart.model.Constants; import com.dougkeen.bart.data.RoutesColumns;
import com.dougkeen.bart.model.Station; import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Station;
public class AddRouteDialogFragment extends AbstractRouteSelectionFragment {
public AddRouteDialogFragment(String title) { public class AddRouteDialogFragment extends AbstractRouteSelectionFragment {
super(title); public AddRouteDialogFragment(String title) {
} super(title);
}
@Override
public void onStart() { @Override
super.onStart(); public void onStart() {
final View checkboxText = getDialog().findViewById( super.onStart();
R.id.return_checkbox_text); final View checkboxText = getDialog().findViewById(
final View checkbox = getDialog().findViewById(R.id.return_checkbox); R.id.return_checkbox_text);
checkboxText.setVisibility(View.VISIBLE); final View checkbox = getDialog().findViewById(R.id.return_checkbox);
checkbox.setVisibility(View.VISIBLE); checkboxText.setVisibility(View.VISIBLE);
checkboxText.setOnClickListener(new View.OnClickListener() { checkbox.setVisibility(View.VISIBLE);
@Override checkboxText.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) { @Override
checkbox.performClick(); public void onClick(View view) {
} checkbox.performClick();
}); }
} });
}
@Override
protected void onOkButtonClick(Station origin, Station destination) { @Override
ContentValues values = new ContentValues(); protected void onOkButtonClick(Station origin, Station destination) {
values.put(RoutesColumns.FROM_STATION.string, origin.abbreviation); ContentValues values = new ContentValues();
values.put(RoutesColumns.TO_STATION.string, destination.abbreviation); values.put(RoutesColumns.FROM_STATION.string, origin.abbreviation);
values.put(RoutesColumns.TO_STATION.string, destination.abbreviation);
getActivity().getContentResolver().insert(
Constants.FAVORITE_CONTENT_URI, values); getActivity().getContentResolver().insert(
Constants.FAVORITE_CONTENT_URI, values);
if (((CheckBox) getDialog().findViewById(R.id.return_checkbox))
.isChecked()) { if (((CheckBox) getDialog().findViewById(R.id.return_checkbox))
values = new ContentValues(); .isChecked()) {
values.put(RoutesColumns.FROM_STATION.string, values = new ContentValues();
destination.abbreviation); values.put(RoutesColumns.FROM_STATION.string,
values.put(RoutesColumns.TO_STATION.string, origin.abbreviation); destination.abbreviation);
values.put(RoutesColumns.TO_STATION.string, origin.abbreviation);
getActivity().getContentResolver().insert(
Constants.FAVORITE_CONTENT_URI, values); getActivity().getContentResolver().insert(
} Constants.FAVORITE_CONTENT_URI, values);
}
dismiss();
} dismiss();
}
}
}

View File

@ -1,21 +1,21 @@
package com.dougkeen.bart; package com.dougkeen.bart.activities;
import android.content.Intent; import android.content.Intent;
import com.dougkeen.bart.model.Constants; import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Station; import com.dougkeen.bart.model.Station;
public class QuickRouteDialogFragment extends AbstractRouteSelectionFragment { public class QuickRouteDialogFragment extends AbstractRouteSelectionFragment {
public QuickRouteDialogFragment(String title) { public QuickRouteDialogFragment(String title) {
super(title); super(title);
} }
@Override @Override
protected void onOkButtonClick(Station origin, Station destination) { protected void onOkButtonClick(Station origin, Station destination) {
startActivity(new Intent(Intent.ACTION_VIEW, startActivity(new Intent(Intent.ACTION_VIEW,
Constants.ARBITRARY_ROUTE_CONTENT_URI_ROOT.buildUpon() Constants.ARBITRARY_ROUTE_CONTENT_URI_ROOT.buildUpon()
.appendPath(origin.abbreviation) .appendPath(origin.abbreviation)
.appendPath(destination.abbreviation).build())); .appendPath(destination.abbreviation).build()));
} }
} }

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart; package com.dougkeen.bart.activities;
import java.util.Calendar; import java.util.Calendar;
import java.util.TimeZone; import java.util.TimeZone;
@ -8,17 +8,16 @@ import android.content.ContentValues;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.database.CursorWrapper;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.view.View; import android.view.View;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.Button; import android.widget.Button;
import android.widget.CursorAdapter;
import android.widget.ListAdapter; import android.widget.ListAdapter;
import android.widget.SimpleCursorAdapter;
import android.widget.SimpleCursorAdapter.ViewBinder;
import android.widget.TextView; import android.widget.TextView;
import com.WazaBe.HoloEverywhere.AlertDialog; import com.WazaBe.HoloEverywhere.AlertDialog;
@ -27,18 +26,23 @@ import com.actionbarsherlock.view.ActionMode;
import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem; import com.actionbarsherlock.view.MenuItem;
import com.dougkeen.bart.R;
import com.dougkeen.bart.controls.Ticker;
import com.dougkeen.bart.data.CursorUtils; import com.dougkeen.bart.data.CursorUtils;
import com.dougkeen.bart.data.FavoritesArrayAdapter;
import com.dougkeen.bart.data.RoutesColumns; import com.dougkeen.bart.data.RoutesColumns;
import com.dougkeen.bart.model.Constants; import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Station; import com.dougkeen.bart.model.Station;
import com.dougkeen.bart.model.StationPair;
import com.dougkeen.bart.networktasks.GetRouteFareTask; import com.dougkeen.bart.networktasks.GetRouteFareTask;
public class RoutesListActivity extends SherlockFragmentActivity { public class RoutesListActivity extends SherlockFragmentActivity implements
LoaderCallbacks<Cursor> {
private static final int FAVORITES_LOADER_ID = 0;
private static final TimeZone PACIFIC_TIME = TimeZone private static final TimeZone PACIFIC_TIME = TimeZone
.getTimeZone("America/Los_Angeles"); .getTimeZone("America/Los_Angeles");
protected Cursor mQuery;
private Uri mCurrentlySelectedUri; private Uri mCurrentlySelectedUri;
private Station mCurrentlySelectedOrigin; private Station mCurrentlySelectedOrigin;
@ -46,6 +50,8 @@ public class RoutesListActivity extends SherlockFragmentActivity {
private ActionMode mActionMode; private ActionMode mActionMode;
private FavoritesArrayAdapter mRoutesAdapter;
/** Called when the activity is first created. */ /** Called when the activity is first created. */
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -53,51 +59,24 @@ public class RoutesListActivity extends SherlockFragmentActivity {
setContentView(R.layout.main); setContentView(R.layout.main);
setTitle(R.string.favorite_routes); setTitle(R.string.favorite_routes);
mQuery = managedQuery(Constants.FAVORITE_CONTENT_URI, new String[] { mRoutesAdapter = new FavoritesArrayAdapter(this,
RoutesColumns._ID.string, RoutesColumns.FROM_STATION.string, R.layout.favorite_listing);
RoutesColumns.TO_STATION.string, RoutesColumns.FARE.string,
RoutesColumns.FARE_LAST_UPDATED.string,
RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string,
RoutesColumns.AVERAGE_TRIP_LENGTH.string }, null, null,
RoutesColumns._ID.string);
refreshFares(); getSupportLoaderManager().initLoader(FAVORITES_LOADER_ID, null, this);
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, setListAdapter(mRoutesAdapter);
R.layout.favorite_listing, mQuery, new String[] {
RoutesColumns.FROM_STATION.string,
RoutesColumns.TO_STATION.string,
RoutesColumns.FARE.string }, new int[] {
R.id.originText, R.id.destinationText, R.id.fareText });
adapter.setViewBinder(new ViewBinder() {
public boolean setViewValue(View view, Cursor cursor,
int columnIndex) {
if (view.getId() == R.id.fareText) {
String fare = cursor.getString(columnIndex);
if (fare != null) {
((TextView) view).setSingleLine(false);
((TextView) view).setText("Fare:\n" + fare);
}
} else {
((TextView) view).setText(Station.getByAbbreviation(cursor
.getString(columnIndex)).name);
}
return true;
}
});
setListAdapter(adapter);
getListView().setOnItemClickListener( getListView().setOnItemClickListener(
new AdapterView.OnItemClickListener() { new AdapterView.OnItemClickListener() {
@Override @Override
public void onItemClick(AdapterView<?> l, View v, public void onItemClick(AdapterView<?> l, View v,
int position, long id) { int position, long id) {
;
startActivity(new Intent(Intent.ACTION_VIEW, startActivity(new Intent(Intent.ACTION_VIEW,
ContentUris.withAppendedId( ContentUris.withAppendedId(
Constants.FAVORITE_CONTENT_URI, id))); Constants.FAVORITE_CONTENT_URI,
getListAdapter().getItem(position)
.getId())));
} }
}); });
getListView().setEmptyView(findViewById(android.R.id.empty)); getListView().setEmptyView(findViewById(android.R.id.empty));
getListView().setOnItemLongClickListener( getListView().setOnItemLongClickListener(
@ -109,17 +88,13 @@ public class RoutesListActivity extends SherlockFragmentActivity {
mActionMode.finish(); mActionMode.finish();
} }
mCurrentlySelectedUri = ContentUris.withAppendedId( StationPair item = getListAdapter().getItem(position);
Constants.FAVORITE_CONTENT_URI, id);
CursorWrapper item = (CursorWrapper) getListAdapter() mCurrentlySelectedUri = ContentUris.withAppendedId(
.getItem(position); Constants.FAVORITE_CONTENT_URI, item.getId());
Station orig = Station.getByAbbreviation(CursorUtils
.getString(item, RoutesColumns.FROM_STATION)); mCurrentlySelectedOrigin = item.getOrigin();
Station dest = Station.getByAbbreviation(CursorUtils mCurrentlySelectedDestination = item.getDestination();
.getString(item, RoutesColumns.TO_STATION));
mCurrentlySelectedOrigin = orig;
mCurrentlySelectedDestination = dest;
startContextualActionMode(); startContextualActionMode();
return true; return true;
@ -157,31 +132,58 @@ public class RoutesListActivity extends SherlockFragmentActivity {
} }
} }
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(this, Constants.FAVORITE_CONTENT_URI,
new String[] { RoutesColumns._ID.string,
RoutesColumns.FROM_STATION.string,
RoutesColumns.TO_STATION.string,
RoutesColumns.FARE.string,
RoutesColumns.FARE_LAST_UPDATED.string,
RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string,
RoutesColumns.AVERAGE_TRIP_LENGTH.string }, null, null,
RoutesColumns._ID.string);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if (cursor.getCount() == 0) {
((TextView) findViewById(android.R.id.empty))
.setText(R.string.empty_favorites_list_message);
}
mRoutesAdapter.updateFromCursor(cursor);
refreshFares(cursor);
findViewById(R.id.progress).setVisibility(View.GONE);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// Nothing to do
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private AdapterView<ListAdapter> getListView() { private AdapterView<ListAdapter> getListView() {
return (AdapterView<ListAdapter>) findViewById(android.R.id.list); return (AdapterView<ListAdapter>) findViewById(android.R.id.list);
} }
private CursorAdapter mListAdapter; protected FavoritesArrayAdapter getListAdapter() {
return mRoutesAdapter;
protected CursorAdapter getListAdapter() {
return mListAdapter;
} }
protected void setListAdapter(SimpleCursorAdapter adapter) { protected void setListAdapter(FavoritesArrayAdapter adapter) {
mListAdapter = adapter; mRoutesAdapter = adapter;
getListView().setAdapter(mListAdapter); getListView().setAdapter(mRoutesAdapter);
} }
private void refreshFares() { private void refreshFares(Cursor cursor) {
if (mQuery.moveToFirst()) { if (cursor.moveToFirst()) {
do { do {
final Station orig = Station.getByAbbreviation(CursorUtils final Station orig = Station.getByAbbreviation(CursorUtils
.getString(mQuery, RoutesColumns.FROM_STATION)); .getString(cursor, RoutesColumns.FROM_STATION));
final Station dest = Station.getByAbbreviation(CursorUtils final Station dest = Station.getByAbbreviation(CursorUtils
.getString(mQuery, RoutesColumns.TO_STATION)); .getString(cursor, RoutesColumns.TO_STATION));
final Long id = CursorUtils.getLong(mQuery, RoutesColumns._ID); final Long id = CursorUtils.getLong(cursor, RoutesColumns._ID);
final Long lastUpdateMillis = CursorUtils.getLong(mQuery, final Long lastUpdateMillis = CursorUtils.getLong(cursor,
RoutesColumns.FARE_LAST_UPDATED); RoutesColumns.FARE_LAST_UPDATED);
Calendar now = Calendar.getInstance(); Calendar now = Calendar.getInstance();
@ -215,7 +217,7 @@ public class RoutesListActivity extends SherlockFragmentActivity {
}; };
fareTask.execute(new GetRouteFareTask.Params(orig, dest)); fareTask.execute(new GetRouteFareTask.Params(orig, dest));
} }
} while (mQuery.moveToNext()); } while (cursor.moveToNext());
} }
} }
@ -235,9 +237,41 @@ public class RoutesListActivity extends SherlockFragmentActivity {
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
((TextView) findViewById(android.R.id.empty)) Ticker.getInstance().startTicking(this);
.setText(R.string.empty_favorites_list_message); if (mRoutesAdapter != null && !mRoutesAdapter.isEmpty()
refreshFares(); && !mRoutesAdapter.areEtdListenersActive()) {
mRoutesAdapter.setUpEtdListeners();
}
}
@Override
protected void onPause() {
super.onPause();
if (mRoutesAdapter != null && mRoutesAdapter.areEtdListenersActive()) {
mRoutesAdapter.clearEtdListeners();
}
}
@Override
protected void onStop() {
super.onStop();
Ticker.getInstance().stopTicking(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mRoutesAdapter != null) {
mRoutesAdapter.close();
}
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
Ticker.getInstance().startTicking(this);
}
} }
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart; package com.dougkeen.bart.activities;
import net.simonvt.widget.NumberPicker; import net.simonvt.widget.NumberPicker;
import android.app.Dialog; import android.app.Dialog;
@ -12,6 +12,9 @@ import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentActivity;
import com.WazaBe.HoloEverywhere.AlertDialog; import com.WazaBe.HoloEverywhere.AlertDialog;
import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.R;
import com.dougkeen.bart.services.NotificationService;
public class TrainAlertDialogFragment extends DialogFragment { public class TrainAlertDialogFragment extends DialogFragment {

View File

@ -1,45 +1,46 @@
package com.dougkeen.bart; package com.dougkeen.bart.activities;
import android.os.Bundle; import android.os.Bundle;
import android.webkit.WebView; import android.webkit.WebView;
import com.actionbarsherlock.app.SherlockActivity; import com.actionbarsherlock.app.SherlockActivity;
import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem; import com.actionbarsherlock.view.MenuItem;
import com.dougkeen.bart.R;
public class ViewMapActivity extends SherlockActivity {
public class ViewMapActivity extends SherlockActivity {
@Override
protected void onCreate(Bundle savedInstanceState) { @Override
super.onCreate(savedInstanceState); protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WebView webview = new WebView(this);
setContentView(webview); WebView webview = new WebView(this);
setContentView(webview);
webview.getSettings().setBuiltInZoomControls(true);
webview.getSettings().setSupportZoom(true); webview.getSettings().setBuiltInZoomControls(true);
webview.getSettings().setSupportZoom(true);
webview.loadUrl("file:///android_res/drawable/map.png");
webview.loadUrl("file:///android_res/drawable/map.png");
getSupportActionBar().setHomeButtonEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeButtonEnabled(true);
} getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) { @Override
MenuInflater inflater = getSupportMenuInflater(); public boolean onCreateOptionsMenu(Menu menu) {
inflater.inflate(R.menu.system_map_menu, menu); MenuInflater inflater = getSupportMenuInflater();
return true; inflater.inflate(R.menu.system_map_menu, menu);
} return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) { @Override
if (item.getItemId() == android.R.id.home) { public boolean onOptionsItemSelected(MenuItem item) {
finish(); if (item.getItemId() == android.R.id.home) {
return true; finish();
} return true;
return super.onOptionsItemSelected(item); }
} return super.onOptionsItemSelected(item);
}
}
}

View File

@ -37,7 +37,7 @@ public class CountdownTextView extends TextView implements
public void setTextProvider(TextProvider provider) { public void setTextProvider(TextProvider provider) {
mTextProvider = provider; mTextProvider = provider;
Ticker.getInstance().addSubscriber(this); Ticker.getInstance().addSubscriber(this, getContext());
} }
@Override @Override

View File

@ -3,6 +3,7 @@ package com.dougkeen.bart.controls;
import java.util.Iterator; import java.util.Iterator;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import android.content.Context;
import android.os.Handler; import android.os.Handler;
public class Ticker { public class Ticker {
@ -16,6 +17,8 @@ public class Ticker {
private WeakHashMap<TickSubscriber, Object> mSubscribers; private WeakHashMap<TickSubscriber, Object> mSubscribers;
private WeakHashMap<Context, Object> mTickerHosts;
private TickerEngine mEngine; private TickerEngine mEngine;
private static class TickerEngine implements Runnable { private static class TickerEngine implements Runnable {
@ -84,26 +87,28 @@ public class Ticker {
return sInstance; return sInstance;
} }
public void addSubscriber(TickSubscriber subscriber) { public void addSubscriber(TickSubscriber subscriber, Context host) {
if (!mSubscribers.containsKey(subscriber) && subscriber != null) { if (!mSubscribers.containsKey(subscriber) && subscriber != null) {
mSubscribers.put(subscriber, null); mSubscribers.put(subscriber, null);
startTicking(); startTicking(host);
} }
} }
private Ticker() { private Ticker() {
mSubscribers = new WeakHashMap<TickSubscriber, Object>(); mSubscribers = new WeakHashMap<TickSubscriber, Object>();
mTickerHosts = new WeakHashMap<Context, Object>();
mEngine = new TickerEngine(this); mEngine = new TickerEngine(this);
} }
public void startTicking() { public void startTicking(Context host) {
mTickerHosts.put(host, true);
if (!mEngine.isOn()) if (!mEngine.isOn())
mEngine.run(); mEngine.run();
} }
public void stopTicking() { public void stopTicking(Context host) {
if (mEngine.isOn()) mTickerHosts.remove(host);
if (mEngine.isOn() && mTickerHosts.isEmpty())
mEngine.stop(); mEngine.stop();
} }
} }

View File

@ -43,7 +43,7 @@ public class TimedTextSwitcher extends TextSwitcher implements
public void setTextProvider(TextProvider textProvider) { public void setTextProvider(TextProvider textProvider) {
mTextProvider = textProvider; mTextProvider = textProvider;
Ticker.getInstance().addSubscriber(this); Ticker.getInstance().addSubscriber(this, getContext());
} }
private CharSequence mLastText; private CharSequence mLastText;

View File

@ -1,148 +1,149 @@
package com.dougkeen.bart; package com.dougkeen.bart.data;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import android.content.Context; import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.GradientDrawable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextSwitcher; import android.widget.TextSwitcher;
import android.widget.TextView; import android.widget.TextView;
import android.widget.ViewSwitcher.ViewFactory; import android.widget.ViewSwitcher.ViewFactory;
import com.dougkeen.bart.controls.CountdownTextView; import com.dougkeen.bart.R;
import com.dougkeen.bart.controls.TimedTextSwitcher; import com.dougkeen.bart.controls.CountdownTextView;
import com.dougkeen.bart.model.Departure; import com.dougkeen.bart.controls.TimedTextSwitcher;
import com.dougkeen.bart.model.TextProvider; import com.dougkeen.bart.model.Departure;
import com.dougkeen.bart.model.TextProvider;
public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
public DepartureArrayAdapter(Context context, int textViewResourceId,
Departure[] objects) { public DepartureArrayAdapter(Context context, int textViewResourceId,
super(context, textViewResourceId, objects); Departure[] objects) {
} super(context, textViewResourceId, objects);
}
public DepartureArrayAdapter(Context context, int resource,
int textViewResourceId, Departure[] objects) { public DepartureArrayAdapter(Context context, int resource,
super(context, resource, textViewResourceId, objects); int textViewResourceId, Departure[] objects) {
} super(context, resource, textViewResourceId, objects);
}
public DepartureArrayAdapter(Context context, int resource,
int textViewResourceId, List<Departure> objects) { public DepartureArrayAdapter(Context context, int resource,
super(context, resource, textViewResourceId, objects); int textViewResourceId, List<Departure> objects) {
} super(context, resource, textViewResourceId, objects);
}
public DepartureArrayAdapter(Context context, int resource,
int textViewResourceId) { public DepartureArrayAdapter(Context context, int resource,
super(context, resource, textViewResourceId); int textViewResourceId) {
} super(context, resource, textViewResourceId);
}
public DepartureArrayAdapter(Context context, int textViewResourceId,
List<Departure> objects) { public DepartureArrayAdapter(Context context, int textViewResourceId,
super(context, textViewResourceId, objects); List<Departure> objects) {
} super(context, textViewResourceId, objects);
}
public DepartureArrayAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId); public DepartureArrayAdapter(Context context, int textViewResourceId) {
} super(context, textViewResourceId);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) { @Override
View view; public View getView(int position, View convertView, ViewGroup parent) {
if (convertView != null && convertView instanceof RelativeLayout) { View view;
view = convertView; if (convertView != null && convertView instanceof RelativeLayout) {
} else { view = convertView;
LayoutInflater inflater = LayoutInflater.from(getContext()); } else {
view = inflater.inflate(R.layout.departure_listing, parent, false); LayoutInflater inflater = LayoutInflater.from(getContext());
} view = inflater.inflate(R.layout.departure_listing, parent, false);
}
final Departure departure = getItem(position);
((TextView) view.findViewById(R.id.destinationText)).setText(departure final Departure departure = getItem(position);
.getTrainDestination().toString()); ((TextView) view.findViewById(R.id.destinationText)).setText(departure
.getTrainDestination().toString());
TimedTextSwitcher textSwitcher = (TimedTextSwitcher) view
.findViewById(R.id.trainLengthText); TimedTextSwitcher textSwitcher = (TimedTextSwitcher) view
initTextSwitcher(textSwitcher); .findViewById(R.id.trainLengthText);
initTextSwitcher(textSwitcher);
final String estimatedArrivalTimeText = departure
.getEstimatedArrivalTimeText(getContext()); final String estimatedArrivalTimeText = departure
if (!StringUtils.isBlank(estimatedArrivalTimeText)) { .getEstimatedArrivalTimeText(getContext());
textSwitcher.setCurrentText("Est. arrival " if (!StringUtils.isBlank(estimatedArrivalTimeText)) {
+ estimatedArrivalTimeText); textSwitcher.setCurrentText("Est. arrival "
} else { + estimatedArrivalTimeText);
textSwitcher.setCurrentText(departure.getTrainLengthText()); } else {
} textSwitcher.setCurrentText(departure.getTrainLengthText());
textSwitcher.setTextProvider(new TextProvider() { }
@Override textSwitcher.setTextProvider(new TextProvider() {
public String getText(long tickNumber) { @Override
if (tickNumber % 4 == 0) { public String getText(long tickNumber) {
return departure.getTrainLengthText(); if (tickNumber % 4 == 0) {
} else { return departure.getTrainLengthText();
final String estimatedArrivalTimeText = departure } else {
.getEstimatedArrivalTimeText(getContext()); final String estimatedArrivalTimeText = departure
if (StringUtils.isBlank(estimatedArrivalTimeText)) { .getEstimatedArrivalTimeText(getContext());
return ""; if (StringUtils.isBlank(estimatedArrivalTimeText)) {
} else { return "";
return "Est. arrival " + estimatedArrivalTimeText; } else {
} return "Est. arrival " + estimatedArrivalTimeText;
} }
} }
}); }
});
ImageView colorBar = (ImageView) view
.findViewById(R.id.destinationColorBar); ImageView colorBar = (ImageView) view
((GradientDrawable) colorBar.getDrawable()).setColor(Color .findViewById(R.id.destinationColorBar);
.parseColor(departure.getTrainDestinationColor())); ((GradientDrawable) colorBar.getDrawable()).setColor(Color
CountdownTextView countdownTextView = (CountdownTextView) view .parseColor(departure.getTrainDestinationColor()));
.findViewById(R.id.countdown); CountdownTextView countdownTextView = (CountdownTextView) view
countdownTextView.setText(departure.getCountdownText()); .findViewById(R.id.countdown);
countdownTextView.setTextProvider(new TextProvider() { countdownTextView.setText(departure.getCountdownText());
@Override countdownTextView.setTextProvider(new TextProvider() {
public String getText(long tickNumber) { @Override
return departure.getCountdownText(); public String getText(long tickNumber) {
} return departure.getCountdownText();
}); }
((TextView) view.findViewById(R.id.uncertainty)).setText(departure });
.getUncertaintyText()); ((TextView) view.findViewById(R.id.uncertainty)).setText(departure
if (departure.isBikeAllowed()) { .getUncertaintyText());
((ImageView) view.findViewById(R.id.bikeIcon)) if (departure.isBikeAllowed()) {
.setVisibility(View.VISIBLE); ((ImageView) view.findViewById(R.id.bikeIcon))
} else { .setVisibility(View.VISIBLE);
((ImageView) view.findViewById(R.id.bikeIcon)) } else {
.setVisibility(View.INVISIBLE); ((ImageView) view.findViewById(R.id.bikeIcon))
} .setVisibility(View.INVISIBLE);
if (departure.getRequiresTransfer()) { }
((ImageView) view.findViewById(R.id.xferIcon)) if (departure.getRequiresTransfer()) {
.setVisibility(View.VISIBLE); ((ImageView) view.findViewById(R.id.xferIcon))
} else { .setVisibility(View.VISIBLE);
((ImageView) view.findViewById(R.id.xferIcon)) } else {
.setVisibility(View.INVISIBLE); ((ImageView) view.findViewById(R.id.xferIcon))
} .setVisibility(View.INVISIBLE);
}
return view;
} return view;
}
private void initTextSwitcher(TextSwitcher textSwitcher) {
if (textSwitcher.getInAnimation() == null) { private void initTextSwitcher(TextSwitcher textSwitcher) {
textSwitcher.setFactory(new ViewFactory() { if (textSwitcher.getInAnimation() == null) {
public View makeView() { textSwitcher.setFactory(new ViewFactory() {
return LayoutInflater.from(getContext()).inflate( public View makeView() {
R.layout.train_length_arrival_textview, null); return LayoutInflater.from(getContext()).inflate(
} R.layout.train_length_arrival_textview, null);
}); }
});
textSwitcher.setInAnimation(AnimationUtils.loadAnimation(
getContext(), android.R.anim.slide_in_left)); textSwitcher.setInAnimation(AnimationUtils.loadAnimation(
textSwitcher.setOutAnimation(AnimationUtils.loadAnimation( getContext(), android.R.anim.slide_in_left));
getContext(), android.R.anim.slide_out_right)); textSwitcher.setOutAnimation(AnimationUtils.loadAnimation(
} getContext(), android.R.anim.slide_out_right));
} }
} }
}

View File

@ -0,0 +1,276 @@
package com.dougkeen.bart.data;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.Cursor;
import android.os.IBinder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.ArrayAdapter;
import android.widget.RelativeLayout;
import android.widget.TextSwitcher;
import android.widget.TextView;
import android.widget.ViewSwitcher.ViewFactory;
import com.dougkeen.bart.R;
import com.dougkeen.bart.controls.CountdownTextView;
import com.dougkeen.bart.controls.TimedTextSwitcher;
import com.dougkeen.bart.model.Departure;
import com.dougkeen.bart.model.StationPair;
import com.dougkeen.bart.model.TextProvider;
import com.dougkeen.bart.services.EtdService;
import com.dougkeen.bart.services.EtdService.EtdServiceBinder;
import com.dougkeen.bart.services.EtdService.EtdServiceListener;
public class FavoritesArrayAdapter extends ArrayAdapter<StationPair> {
private boolean mBound = false;
private EtdService mEtdService;
private Activity mHostActivity;
private Map<StationPair, EtdListener> mEtdListeners = new HashMap<StationPair, EtdListener>();
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
mEtdService = null;
mBound = false;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mEtdService = ((EtdServiceBinder) service).getService();
mBound = true;
if (!isEmpty()) {
setUpEtdListeners();
}
}
};
public void setUpEtdListeners() {
if (mBound && mEtdService != null) {
for (int i = getCount() - 1; i >= 0; i--) {
final StationPair item = getItem(i);
mEtdListeners.put(item, new EtdListener(item, mEtdService));
}
}
}
public void clearEtdListeners() {
if (mBound && mEtdService != null) {
for (EtdListener listener : mEtdListeners.values()) {
listener.close(mEtdService);
}
mEtdListeners.clear();
}
}
public boolean areEtdListenersActive() {
return !mEtdListeners.isEmpty();
}
public FavoritesArrayAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId);
mHostActivity = (Activity) context;
mHostActivity.bindService(new Intent(mHostActivity, EtdService.class),
mConnection, Context.BIND_AUTO_CREATE);
}
public void close() {
if (mBound) {
mHostActivity.unbindService(mConnection);
}
}
@Override
public void add(StationPair object) {
super.add(object);
if (mEtdService != null && mBound) {
mEtdListeners.put(object, new EtdListener(object, mEtdService));
}
}
@Override
public void remove(StationPair object) {
super.remove(object);
if (mEtdListeners.containsKey(object) && mEtdService != null & mBound) {
mEtdListeners.get(object).close(mEtdService);
mEtdListeners.remove(object);
}
}
@Override
public void clear() {
super.clear();
clearEtdListeners();
}
public void updateFromCursor(Cursor cursor) {
if (!cursor.moveToFirst()) {
clear();
}
for (int i = 0; i < getCount(); i++) {
StationPair adapterItem = getItem(i);
StationPair cursorItem = StationPair.createFromCursor(cursor);
while (!cursorItem.equals(adapterItem)) {
remove(adapterItem);
if (i < getCount()) {
adapterItem = getItem(i);
} else {
break;
}
}
if (cursorItem.equals(adapterItem)
&& !cursorItem.fareEquals(adapterItem)) {
adapterItem.setFare(cursorItem.getFare());
adapterItem.setFareLastUpdated(cursorItem.getFareLastUpdated());
notifyDataSetChanged();
}
cursor.moveToNext();
}
while (!cursor.isAfterLast()) {
add(StationPair.createFromCursor(cursor));
cursor.moveToNext();
}
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView != null && convertView instanceof RelativeLayout) {
view = convertView;
} else {
LayoutInflater inflater = LayoutInflater.from(getContext());
view = inflater.inflate(R.layout.favorite_listing, parent, false);
}
final StationPair pair = getItem(position);
final EtdListener etdListener = mEtdListeners.get(pair);
final TimedTextSwitcher uncertaintyTextSwitcher = (TimedTextSwitcher) view
.findViewById(R.id.uncertainty);
initTextSwitcher(uncertaintyTextSwitcher);
if (etdListener == null || etdListener.getFirstDeparture() == null) {
uncertaintyTextSwitcher.setCurrentText(pair.getFare());
} else {
CountdownTextView countdownTextView = (CountdownTextView) view
.findViewById(R.id.countdownText);
countdownTextView.setText(etdListener.getFirstDeparture()
.getCountdownText());
countdownTextView.setTextProvider(new TextProvider() {
@Override
public String getText(long tickNumber) {
return etdListener.getFirstDeparture().getCountdownText();
}
});
final String uncertaintyText = etdListener.getFirstDeparture()
.getUncertaintyText();
if (!StringUtils.isBlank(uncertaintyText)) {
uncertaintyTextSwitcher.setCurrentText(uncertaintyText);
} else {
uncertaintyTextSwitcher.setCurrentText(pair.getFare());
}
uncertaintyTextSwitcher.setTextProvider(new TextProvider() {
@Override
public String getText(long tickNumber) {
if (tickNumber % 4 <= 1) {
return pair.getFare();
} else {
return etdListener.getFirstDeparture()
.getUncertaintyText();
}
}
});
}
((TextView) view.findViewById(R.id.originText)).setText(pair
.getOrigin().name);
((TextView) view.findViewById(R.id.destinationText)).setText(pair
.getDestination().name);
return view;
}
private void initTextSwitcher(TextSwitcher textSwitcher) {
if (textSwitcher.getInAnimation() == null) {
textSwitcher.setFactory(new ViewFactory() {
public View makeView() {
return LayoutInflater.from(getContext()).inflate(
R.layout.uncertainty_textview, null);
}
});
textSwitcher.setInAnimation(AnimationUtils.loadAnimation(
getContext(), android.R.anim.slide_in_left));
textSwitcher.setOutAnimation(AnimationUtils.loadAnimation(
getContext(), android.R.anim.slide_out_right));
}
}
private class EtdListener implements EtdServiceListener {
private final StationPair mStationPair;
private Departure firstDeparture;
protected EtdListener(StationPair mStationPair, EtdService etdService) {
super();
this.mStationPair = mStationPair;
etdService.registerListener(this, true);
}
protected void close(EtdService etdService) {
etdService.unregisterListener(this);
}
@Override
public void onETDChanged(List<Departure> departures) {
for (Departure departure : departures) {
if (!departure.hasDeparted()) {
if (!departure.equals(firstDeparture)) {
firstDeparture = departure;
FavoritesArrayAdapter.this.notifyDataSetChanged();
}
return;
}
}
}
@Override
public void onError(String errorMessage) {
}
@Override
public void onRequestStarted() {
}
@Override
public void onRequestEnded() {
}
@Override
public StationPair getStationPair() {
return mStationPair;
}
public Departure getFirstDeparture() {
return firstDeparture;
}
}
}

View File

@ -496,8 +496,10 @@ public class Departure implements Parcelable, Comparable<Departure> {
dest.writeLong(arrivalTimeOverride); dest.writeLong(arrivalTimeOverride);
dest.writeInt(estimatedTripTime); dest.writeInt(estimatedTripTime);
dest.writeInt(line.ordinal()); dest.writeInt(line.ordinal());
dest.writeBooleanArray(new boolean[] { beganAsDeparted, bikeAllowed, dest.writeByte(beganAsDeparted ? (byte) 1 : (byte) 0);
requiresTransfer, transferScheduled }); dest.writeByte(bikeAllowed ? (byte) 1 : (byte) 0);
dest.writeByte(requiresTransfer ? (byte) 1 : (byte) 0);
dest.writeByte(transferScheduled ? (byte) 1 : (byte) 0);
} }
private void readFromParcel(Parcel in) { private void readFromParcel(Parcel in) {
@ -516,12 +518,10 @@ public class Departure implements Parcelable, Comparable<Departure> {
arrivalTimeOverride = in.readLong(); arrivalTimeOverride = in.readLong();
estimatedTripTime = in.readInt(); estimatedTripTime = in.readInt();
line = Line.values()[in.readInt()]; line = Line.values()[in.readInt()];
boolean[] bools = new boolean[4]; beganAsDeparted = in.readByte() == (byte) 1;
in.readBooleanArray(bools); bikeAllowed = in.readByte() == (byte) 1;
beganAsDeparted = bools[0]; requiresTransfer = in.readByte() == (byte) 1;
bikeAllowed = bools[1]; transferScheduled = in.readByte() == (byte) 1;
requiresTransfer = bools[2];
transferScheduled = bools[3];
} }
public static final Parcelable.Creator<Departure> CREATOR = new Parcelable.Creator<Departure>() { public static final Parcelable.Creator<Departure> CREATOR = new Parcelable.Creator<Departure>() {

View File

@ -10,8 +10,6 @@ public class Route {
private boolean requiresTransfer; private boolean requiresTransfer;
private Station transferStation; private Station transferStation;
private String direction; private String direction;
private String fare;
private Long fareLastUpdated;
public Station getOrigin() { public Station getOrigin() {
return origin; return origin;
@ -69,22 +67,6 @@ public class Route {
this.direction = direction; this.direction = direction;
} }
public String getFare() {
return fare;
}
public void setFare(String fare) {
this.fare = fare;
}
public Long getFareLastUpdated() {
return fareLastUpdated;
}
public void setFareLastUpdated(Long fareLastUpdated) {
this.fareLastUpdated = fareLastUpdated;
}
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
@ -100,10 +82,6 @@ public class Route {
builder.append(transferStation); builder.append(transferStation);
builder.append(", direction="); builder.append(", direction=");
builder.append(direction); builder.append(direction);
builder.append(", fare=");
builder.append(fare);
builder.append(", fareLastUpdated=");
builder.append(fareLastUpdated);
builder.append("]"); builder.append("]");
return builder.toString(); return builder.toString();
} }

View File

@ -1,5 +1,11 @@
package com.dougkeen.bart.model; package com.dougkeen.bart.model;
import org.apache.commons.lang3.ObjectUtils;
import com.dougkeen.bart.data.CursorUtils;
import com.dougkeen.bart.data.RoutesColumns;
import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
@ -11,12 +17,39 @@ public class StationPair implements Parcelable {
this.destination = destination; this.destination = destination;
} }
public StationPair(Long id, Station origin, Station destination) {
super();
this.origin = origin;
this.destination = destination;
this.id = id;
}
public StationPair(Parcel in) { public StationPair(Parcel in) {
readFromParcel(in); readFromParcel(in);
} }
public static StationPair createFromCursor(Cursor cursor) {
StationPair pair = new StationPair(
Station.getByAbbreviation(CursorUtils.getString(cursor,
RoutesColumns.FROM_STATION)),
Station.getByAbbreviation(CursorUtils.getString(cursor,
RoutesColumns.TO_STATION)));
pair.id = CursorUtils.getLong(cursor, RoutesColumns._ID);
pair.fare = CursorUtils.getString(cursor, RoutesColumns.FARE);
pair.fareLastUpdated = CursorUtils.getLong(cursor,
RoutesColumns.FARE_LAST_UPDATED);
return pair;
}
private Long id;
private Station origin; private Station origin;
private Station destination; private Station destination;
private String fare;
private Long fareLastUpdated;
public Long getId() {
return id;
}
public Station getOrigin() { public Station getOrigin() {
return origin; return origin;
@ -26,6 +59,22 @@ public class StationPair implements Parcelable {
return destination; return destination;
} }
public String getFare() {
return fare;
}
public void setFare(String fare) {
this.fare = fare;
}
public Long getFareLastUpdated() {
return fareLastUpdated;
}
public void setFareLastUpdated(Long fareLastUpdated) {
this.fareLastUpdated = fareLastUpdated;
}
public boolean isBetweenStations(Station station1, Station station2) { public boolean isBetweenStations(Station station1, Station station2) {
return (origin.equals(station1) && destination.equals(station2)) return (origin.equals(station1) && destination.equals(station2))
|| (origin.equals(station2) && destination.equals(station1)); || (origin.equals(station2) && destination.equals(station1));
@ -51,6 +100,14 @@ public class StationPair implements Parcelable {
return result; return result;
} }
public boolean fareEquals(StationPair other) {
if (other == null)
return false;
return ObjectUtils.equals(getFare(), other.getFare())
&& ObjectUtils.equals(getFareLastUpdated(),
other.getFareLastUpdated());
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) if (this == obj)

View File

@ -1,4 +1,7 @@
package com.dougkeen.bart; package com.dougkeen.bart.receivers;
import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.util.WakeLocker;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart; package com.dougkeen.bart.services;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -21,6 +21,8 @@ import android.os.IBinder;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
import android.util.Log; import android.util.Log;
import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.R;
import com.dougkeen.bart.data.RoutesColumns; import com.dougkeen.bart.data.RoutesColumns;
import com.dougkeen.bart.model.Constants; import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Departure; import com.dougkeen.bart.model.Departure;
@ -44,7 +46,8 @@ public class EtdService extends Service {
mServiceEngineMap = new HashMap<StationPair, EtdServiceEngine>(); mServiceEngineMap = new HashMap<StationPair, EtdServiceEngine>();
} }
public void registerListener(EtdServiceListener listener) { public void registerListener(EtdServiceListener listener,
boolean limitToFirstNonDeparted) {
StationPair stationPair = getStationPairFromListener(listener); StationPair stationPair = getStationPairFromListener(listener);
if (stationPair == null) if (stationPair == null)
return; return;
@ -53,7 +56,8 @@ public class EtdService extends Service {
mServiceEngineMap.put(stationPair, mServiceEngineMap.put(stationPair,
new EtdServiceEngine(stationPair)); new EtdServiceEngine(stationPair));
} }
mServiceEngineMap.get(stationPair).registerListener(listener); mServiceEngineMap.get(stationPair).registerListener(listener,
limitToFirstNonDeparted);
} }
private StationPair getStationPairFromListener(EtdServiceListener listener) { private StationPair getStationPairFromListener(EtdServiceListener listener) {
@ -116,6 +120,8 @@ public class EtdService extends Service {
// We'll only use the keys // We'll only use the keys
private WeakHashMap<EtdServiceListener, Boolean> mListeners; private WeakHashMap<EtdServiceListener, Boolean> mListeners;
private boolean mLimitToFirstNonDeparted = true;
private List<Departure> mLatestDepartures; private List<Departure> mLatestDepartures;
private ScheduleInformation mLatestScheduleInfo; private ScheduleInformation mLatestScheduleInfo;
@ -148,8 +154,11 @@ public class EtdService extends Service {
cursor.close(); cursor.close();
} }
protected void registerListener(EtdServiceListener listener) { protected void registerListener(EtdServiceListener listener,
boolean limitToFirstNonDeparted) {
mListeners.put(listener, true); mListeners.put(listener, true);
if (!limitToFirstNonDeparted)
mLimitToFirstNonDeparted = false;
if (!mPendingEtdRequest) { if (!mPendingEtdRequest) {
mStarted = true; mStarted = true;
fetchLatestDepartures(); fetchLatestDepartures();
@ -439,6 +448,9 @@ public class EtdService extends Service {
if (departure.equals(boardedDeparture)) { if (departure.equals(boardedDeparture)) {
boardedDeparture.mergeEstimate(departure); boardedDeparture.mergeEstimate(departure);
} }
if (!departure.hasDeparted() && mLimitToFirstNonDeparted) {
break;
}
} }
/* /*
@ -504,6 +516,10 @@ public class EtdService extends Service {
if (departure.equals(boardedDeparture)) { if (departure.equals(boardedDeparture)) {
boardedDeparture.mergeEstimate(departure); boardedDeparture.mergeEstimate(departure);
} }
if (!departure.hasDeparted() && mLimitToFirstNonDeparted) {
break;
}
} }
} }
Collections.sort(mLatestDepartures); Collections.sort(mLatestDepartures);

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart; package com.dougkeen.bart.services;
import java.util.List; import java.util.List;
@ -18,17 +18,19 @@ import android.os.HandlerThread;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.os.Parcelable;
import android.os.SystemClock; import android.os.SystemClock;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.app.NotificationCompat.Builder;
import android.util.Log; import android.util.Log;
import android.util.TimeFormatException;
import com.dougkeen.bart.EtdService.EtdServiceBinder; import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.EtdService.EtdServiceListener; import com.dougkeen.bart.R;
import com.dougkeen.bart.model.Constants; import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Departure; import com.dougkeen.bart.model.Departure;
import com.dougkeen.bart.model.StationPair; import com.dougkeen.bart.model.StationPair;
import com.dougkeen.bart.services.EtdService.EtdServiceBinder;
import com.dougkeen.bart.services.EtdService.EtdServiceListener;
public class NotificationService extends Service implements EtdServiceListener { public class NotificationService extends Service implements EtdServiceListener {
@ -74,7 +76,7 @@ public class NotificationService extends Service implements EtdServiceListener {
public void onServiceConnected(ComponentName name, IBinder service) { public void onServiceConnected(ComponentName name, IBinder service) {
mEtdService = ((EtdServiceBinder) service).getService(); mEtdService = ((EtdServiceBinder) service).getService();
if (getStationPair() != null) { if (getStationPair() != null) {
mEtdService.registerListener(NotificationService.this); mEtdService.registerListener(NotificationService.this, false);
} }
mBound = true; mBound = true;
} }
@ -142,7 +144,7 @@ public class NotificationService extends Service implements EtdServiceListener {
} }
if (getStationPair() != null && mEtdService != null) { if (getStationPair() != null && mEtdService != null) {
mEtdService.registerListener(this); mEtdService.registerListener(this, false);
} }
Intent targetIntent = new Intent(Intent.ACTION_VIEW, Intent targetIntent = new Intent(Intent.ACTION_VIEW,
@ -163,7 +165,7 @@ public class NotificationService extends Service implements EtdServiceListener {
getStationPair().getUri()); getStationPair().getUri());
final Departure boardedDeparture = ((BartRunnerApplication) getApplication()) final Departure boardedDeparture = ((BartRunnerApplication) getApplication())
.getBoardedDeparture(); .getBoardedDeparture();
alarmIntent.putExtra("departure", boardedDeparture); alarmIntent.putExtra("departure", (Parcelable) boardedDeparture);
mAlarmPendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, mAlarmPendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
} }
@ -317,7 +319,7 @@ public class NotificationService extends Service implements EtdServiceListener {
mStationPair.getOrigin().shortName + " to " mStationPair.getOrigin().shortName + " to "
+ mStationPair.getDestination().shortName) + mStationPair.getDestination().shortName)
.setContentIntent(mNotificationIntent).setWhen(0); .setContentIntent(mNotificationIntent).setWhen(0);
if (android.os.Build.VERSION.SDK_INT > 16) { if (android.os.Build.VERSION.SDK_INT >= 16) {
notificationBuilder notificationBuilder
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)
.addAction( .addAction(

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart; package com.dougkeen.util;
import android.content.Context; import android.content.Context;
import android.os.PowerManager; import android.os.PowerManager;