Refactored countdowns, implemented "Your train" selection

This commit is contained in:
dkeen@dkeen-laptop 2012-07-17 13:43:42 -07:00
parent 8bbdec5328
commit ab7ab0a491
14 changed files with 528 additions and 77 deletions

View File

@ -1,5 +1,6 @@
<?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" >
@ -30,10 +31,12 @@
style="@style/BikeIcon" style="@style/BikeIcon"
android:src="@drawable/bike" /> android:src="@drawable/bike" />
<TextView <com.dougkeen.bart.controls.CountdownTextView
android:id="@+id/countdown" android:id="@+id/countdown"
style="@style/DepartureCountdownText" style="@style/DepartureCountdownText"
android:gravity="right" /> android:gravity="right"
android:width="90dp"
bart:tickInterval="1" />
</LinearLayout> </LinearLayout>
<ImageView <ImageView
@ -43,12 +46,13 @@
android:layout_below="@id/topRow" android:layout_below="@id/topRow"
android:src="@drawable/xfer" /> android:src="@drawable/xfer" />
<TextSwitcher <com.dougkeen.bart.controls.TimedTextSwitcher
android:id="@+id/trainLengthText" android:id="@+id/trainLengthText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/topRow" android:layout_below="@id/topRow"
android:layout_toRightOf="@id/destinationColorBar" /> android:layout_toRightOf="@id/destinationColorBar"
bart:tickInterval="3" />
<TextView <TextView
android:id="@+id/uncertainty" android:id="@+id/uncertainty"

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout 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:orientation="vertical" > android:orientation="vertical" >
@ -13,6 +14,82 @@
android:paddingRight="5dp" android:paddingRight="5dp"
android:textSize="24dp" /> android:textSize="24dp" />
<RelativeLayout
android:id="@+id/yourTrainSection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="#222"
android:padding="10dp"
android:visibility="gone" >
<TextView
android:id="@+id/yourTrainHeader"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:text="@string/your_train"
android:textAllCaps="true"
android:textSize="20dp"
android:textStyle="bold" />
<ImageView
android:id="@+id/yourTrainDestinationColorBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@id/yourTrainHeader"
android:src="@drawable/basic_rectangle" />
<TextView
android:id="@+id/yourTrainDestinationText"
style="@style/DepartureDestinationText"
android:layout_below="@id/yourTrainHeader"
android:layout_toRightOf="@id/yourTrainDestinationColorBar"
android:ellipsize="marquee"
android:singleLine="true" />
<ImageView
android:id="@+id/yourTrainBikeIcon"
style="@style/BikeIcon"
android:layout_below="@id/yourTrainHeader"
android:layout_toRightOf="@id/yourTrainDestinationText"
android:src="@drawable/bike" />
<ImageView
android:id="@+id/yourTrainXferIcon"
style="@style/XferIcon"
android:layout_below="@id/yourTrainBikeIcon"
android:layout_toRightOf="@id/yourTrainDestinationText"
android:src="@drawable/xfer" />
<TextView
android:id="@+id/yourTrainTrainLengthText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/yourTrainDestinationText"
android:layout_toRightOf="@id/yourTrainDestinationColorBar"
android:paddingLeft="5dp" />
<com.dougkeen.bart.controls.CountdownTextView
android:id="@+id/yourTrainDepartureCountdown"
style="@style/DepartureCountdownText"
android:layout_alignLeft="@id/yourTrainSection"
android:layout_alignRight="@id/yourTrainSection"
android:layout_below="@id/yourTrainTrainLengthText"
bart:tickInterval="1" />
<com.dougkeen.bart.controls.CountdownTextView
android:id="@+id/yourTrainArrivalCountdown"
style="@style/DepartureCountdownText"
android:layout_alignLeft="@id/yourTrainSection"
android:layout_alignRight="@id/yourTrainSection"
android:layout_below="@id/yourTrainDepartureCountdown"
android:ellipsize="end"
bart:tickInterval="5" />
</RelativeLayout>
<ListView <ListView
android:id="@android:id/list" android:id="@android:id/list"
android:layout_width="fill_parent" android:layout_width="fill_parent"

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:title="@string/getting_on_this_train" android:id="@+id/boardTrain"></item>
</menu>

View File

@ -12,20 +12,27 @@
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<attr name="tickInterval" format="integer" />
<declare-styleable name="AppTheme"> <declare-styleable name="AppTheme">
<attr name="actionbarCompatTitleStyle" format="reference" /> <attr name="actionbarCompatTitleStyle" format="reference" />
<attr name="actionbarCompatItemStyle" format="reference" /> <attr name="actionbarCompatItemStyle" format="reference" />
<attr name="actionbarCompatItemHomeStyle" format="reference" /> <attr name="actionbarCompatItemHomeStyle" format="reference" />
<attr name="actionbarCompatProgressIndicatorStyle" format="reference" /> <attr name="actionbarCompatProgressIndicatorStyle" format="reference" />
</declare-styleable> </declare-styleable>
<declare-styleable name="BezelImageView"> <declare-styleable name="BezelImageView">
<attr name="maskDrawable" format="reference" /> <attr name="maskDrawable" format="reference" />
<attr name="borderDrawable" format="reference" /> <attr name="borderDrawable" format="reference" />
</declare-styleable> </declare-styleable>
<declare-styleable name="CountdownTextView">
<attr name="tickInterval" />
</declare-styleable>
<declare-styleable name="TimedTextSwitcher">
<attr name="tickInterval" />
</declare-styleable>
</resources> </resources>

View File

@ -32,4 +32,7 @@
<string name="departures">Departures</string> <string name="departures">Departures</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="quick_departure_lookup">Quick departure lookup</string> <string name="quick_departure_lookup">Quick departure lookup</string>
<string name="getting_on_this_train">I\'m getting on this train</string>
<string name="departure_options">Departure options</string>
<string name="your_train">Your train</string>
</resources> </resources>

View File

@ -94,7 +94,6 @@
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:textSize">20dp</item> <item name="android:textSize">20dp</item>
<item name="android:singleLine">true</item> <item name="android:singleLine">true</item>
<item name="android:width">90dp</item>
</style> </style>
<style name="DepartureUncertaintyText"> <style name="DepartureUncertaintyText">

View File

@ -18,12 +18,13 @@ 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.controls.TimedTextSwitcher;
import com.dougkeen.bart.model.Departure; import com.dougkeen.bart.model.Departure;
import com.dougkeen.bart.model.TextProvider;
public class DepartureArrayAdapter extends ArrayAdapter<Departure> { public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
private int refreshCounter = 1;
public DepartureArrayAdapter(Context context, int textViewResourceId, public DepartureArrayAdapter(Context context, int textViewResourceId,
Departure[] objects) { Departure[] objects) {
super(context, textViewResourceId, objects); super(context, textViewResourceId, objects);
@ -52,10 +53,6 @@ public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
public DepartureArrayAdapter(Context context, int textViewResourceId) { public DepartureArrayAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId); super(context, textViewResourceId);
} }
public void incrementRefreshCounter() {
refreshCounter++;
}
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
@ -67,39 +64,46 @@ public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
view = inflater.inflate(R.layout.departure_listing, parent, false); view = inflater.inflate(R.layout.departure_listing, parent, false);
} }
Departure departure = getItem(position); final Departure departure = getItem(position);
((TextView) view.findViewById(R.id.destinationText)).setText(departure ((TextView) view.findViewById(R.id.destinationText)).setText(departure
.getDestination().toString()); .getDestination().toString());
TextSwitcher textSwitcher = (TextSwitcher) view TimedTextSwitcher textSwitcher = (TimedTextSwitcher) view
.findViewById(R.id.trainLengthText); .findViewById(R.id.trainLengthText);
initTextSwitcher(textSwitcher); initTextSwitcher(textSwitcher);
final String estimatedArrivalTimeText = departure textSwitcher.setCurrentText(departure.getTrainLengthText());
.getEstimatedArrivalTimeText(getContext()); textSwitcher.setTextProviders(new TextProvider[] { new TextProvider() {
String arrivalText = "Est. arrival " + estimatedArrivalTimeText; @Override
if (StringUtils.isBlank(estimatedArrivalTimeText)) { public String getText() {
textSwitcher.setCurrentText(departure.getTrainLengthText()); final String estimatedArrivalTimeText = departure
} else if (refreshCounter % 6 < 3) { .getEstimatedArrivalTimeText(getContext());
String trainLengthText = departure.getTrainLengthText(); if (StringUtils.isBlank(estimatedArrivalTimeText)) {
if (refreshCounter % 6 == 0) { return "";
textSwitcher.setText(trainLengthText); } else {
} else { return "Est. arrival " + estimatedArrivalTimeText;
textSwitcher.setCurrentText(trainLengthText); }
} }
} else { }, new TextProvider() {
if (refreshCounter % 6 == 3) { @Override
textSwitcher.setText(arrivalText); public String getText() {
} else { return departure.getTrainLengthText();
textSwitcher.setCurrentText(arrivalText);
} }
} } });
ImageView colorBar = (ImageView) view ImageView colorBar = (ImageView) view
.findViewById(R.id.destinationColorBar); .findViewById(R.id.destinationColorBar);
((GradientDrawable) colorBar.getDrawable()).setColor(Color ((GradientDrawable) colorBar.getDrawable()).setColor(Color
.parseColor(departure.getDestinationColor())); .parseColor(departure.getDestinationColor()));
((TextView) view.findViewById(R.id.countdown)).setText(departure CountdownTextView countdownTextView = (CountdownTextView) view
.getCountdownText()); .findViewById(R.id.countdown);
countdownTextView.setText(departure.getCountdownText());
countdownTextView.setTextProvider(new TextProvider() {
@Override
public String getText() {
return departure.getCountdownText();
}
});
((TextView) view.findViewById(R.id.uncertainty)).setText(departure ((TextView) view.findViewById(R.id.uncertainty)).setText(departure
.getUncertaintyText()); .getUncertaintyText());
if (departure.isBikeAllowed()) { if (departure.isBikeAllowed()) {
@ -130,7 +134,9 @@ public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
}); });
textSwitcher.setInAnimation(AnimationUtils.loadAnimation( textSwitcher.setInAnimation(AnimationUtils.loadAnimation(
getContext(), android.R.anim.fade_in)); getContext(), android.R.anim.slide_in_left));
textSwitcher.setOutAnimation(AnimationUtils.loadAnimation(
getContext(), android.R.anim.slide_out_right));
} }
} }
} }

View File

@ -8,6 +8,8 @@ import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
@ -16,14 +18,20 @@ import android.os.PowerManager;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.text.util.Linkify; import android.text.util.Linkify;
import android.util.Log; import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.dougkeen.bart.actionbarcompat.ActionBarListActivity; import com.dougkeen.bart.actionbarcompat.ActionBarListActivity;
import com.dougkeen.bart.controls.CountdownTextView;
import com.dougkeen.bart.controls.Ticker;
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;
@ -32,6 +40,7 @@ import com.dougkeen.bart.model.ScheduleInformation;
import com.dougkeen.bart.model.ScheduleItem; import com.dougkeen.bart.model.ScheduleItem;
import com.dougkeen.bart.model.Station; import com.dougkeen.bart.model.Station;
import com.dougkeen.bart.model.StationPair; import com.dougkeen.bart.model.StationPair;
import com.dougkeen.bart.model.TextProvider;
import com.dougkeen.bart.networktasks.GetRealTimeDeparturesTask; import com.dougkeen.bart.networktasks.GetRealTimeDeparturesTask;
import com.dougkeen.bart.networktasks.GetScheduleInformationTask; import com.dougkeen.bart.networktasks.GetScheduleInformationTask;
@ -46,6 +55,9 @@ public class ViewDeparturesActivity extends ActionBarListActivity {
private int mAverageTripLength; private int mAverageTripLength;
private int mAverageTripSampleCount; private int mAverageTripSampleCount;
private Departure mSelectedDeparture;
private Departure mBoardedDeparture;
private DepartureArrayAdapter mDeparturesAdapter; private DepartureArrayAdapter mDeparturesAdapter;
private ScheduleInformation mLatestScheduleInfo; private ScheduleInformation mLatestScheduleInfo;
@ -55,14 +67,6 @@ public class ViewDeparturesActivity extends ActionBarListActivity {
private AsyncTask<StationPair, Integer, RealTimeDepartures> mGetDeparturesTask; private AsyncTask<StationPair, Integer, RealTimeDepartures> mGetDeparturesTask;
private AsyncTask<StationPair, Integer, ScheduleInformation> mGetScheduleInformationTask; private AsyncTask<StationPair, Integer, ScheduleInformation> mGetScheduleInformationTask;
private boolean mIsAutoUpdating = false;
private final Runnable AUTO_UPDATE_RUNNABLE = new Runnable() {
public void run() {
runAutoUpdate();
}
};
private PowerManager.WakeLock mWakeLock; private PowerManager.WakeLock mWakeLock;
private boolean mDepartureFetchIsPending; private boolean mDepartureFetchIsPending;
@ -104,21 +108,32 @@ public class ViewDeparturesActivity extends ActionBarListActivity {
mDeparturesAdapter = new DepartureArrayAdapter(this, mDeparturesAdapter = new DepartureArrayAdapter(this,
R.layout.departure_listing); R.layout.departure_listing);
if (savedInstanceState != null if (savedInstanceState != null) {
&& savedInstanceState.containsKey("departures")) {
for (Parcelable departure : savedInstanceState if (savedInstanceState.containsKey("departures")) {
.getParcelableArray("departures")) { for (Parcelable departure : savedInstanceState
mDeparturesAdapter.add((Departure) departure); .getParcelableArray("departures")) {
mDeparturesAdapter.add((Departure) departure);
}
}
if (savedInstanceState.containsKey("boardedDeparture")) {
mBoardedDeparture = (Departure) savedInstanceState
.getParcelable("boardedDeparture");
} }
} }
setListAdapter(mDeparturesAdapter); setListAdapter(mDeparturesAdapter);
registerForContextMenu(getListView());
findViewById(R.id.missingDepartureText).setVisibility(View.VISIBLE); findViewById(R.id.missingDepartureText).setVisibility(View.VISIBLE);
refreshBoardedDeparture();
} }
@Override @Override
protected void onPause() { protected void onPause() {
cancelDataFetch(); cancelDataFetch();
Ticker.getInstance().stopTicking();
super.onPause(); super.onPause();
} }
@ -147,6 +162,13 @@ public class ViewDeparturesActivity extends ActionBarListActivity {
departures[i] = mDeparturesAdapter.getItem(i); departures[i] = mDeparturesAdapter.getItem(i);
} }
outState.putParcelableArray("departures", departures); outState.putParcelableArray("departures", departures);
outState.putParcelable("boardedDeparture", mBoardedDeparture);
}
@Override
protected void onResume() {
super.onResume();
Ticker.getInstance().startTicking();
} }
@Override @Override
@ -161,10 +183,7 @@ public class ViewDeparturesActivity extends ActionBarListActivity {
.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, .newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK,
"ViewDeparturesActivity"); "ViewDeparturesActivity");
mWakeLock.acquire(); mWakeLock.acquire();
if (mDeparturesAdapter != null && !mDeparturesAdapter.isEmpty()) { refreshBoardedDeparture();
mIsAutoUpdating = true;
}
runAutoUpdate();
} else if (mWakeLock != null) { } else if (mWakeLock != null) {
mWakeLock.release(); mWakeLock.release();
} }
@ -336,11 +355,6 @@ public class ViewDeparturesActivity extends ActionBarListActivity {
scheduleDepartureFetch(interval); scheduleDepartureFetch(interval);
} }
if (!mIsAutoUpdating) {
mIsAutoUpdating = true;
}
} else {
mIsAutoUpdating = false;
} }
} }
@ -506,25 +520,6 @@ public class ViewDeparturesActivity extends ActionBarListActivity {
} }
} }
private long mLastAutoUpdate = 0;
private void runAutoUpdate() {
long now = System.currentTimeMillis();
if (now - mLastAutoUpdate < 950) {
return;
}
if (mIsAutoUpdating && mDeparturesAdapter != null) {
mDeparturesAdapter.incrementRefreshCounter();
mDeparturesAdapter.notifyDataSetChanged();
}
mLastAutoUpdate = now;
if (hasWindowFocus()) {
postDelayed(AUTO_UPDATE_RUNNABLE, 1000);
} else {
mIsAutoUpdating = false;
}
}
private boolean postDelayed(Runnable runnable, long delayMillis) { private boolean postDelayed(Runnable runnable, long delayMillis) {
return mEmptyView.postDelayed(runnable, delayMillis); return mEmptyView.postDelayed(runnable, delayMillis);
} }
@ -557,4 +552,84 @@ public class ViewDeparturesActivity extends ActionBarListActivity {
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
} }
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.departure_context_menu, menu);
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
mSelectedDeparture = (Departure) getListAdapter()
.getItem(info.position);
menu.setHeaderTitle(R.string.departure_options);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
if (item.getItemId() == R.id.boardTrain) {
mBoardedDeparture = mSelectedDeparture;
refreshBoardedDeparture();
return true;
}
return super.onContextItemSelected(item);
}
private void refreshBoardedDeparture() {
if (mBoardedDeparture == null)
return;
final Departure departure = mBoardedDeparture;
findViewById(R.id.yourTrainSection).setVisibility(View.VISIBLE);
((TextView) findViewById(R.id.yourTrainDestinationText))
.setText(departure.getDestination().toString());
((TextView) findViewById(R.id.yourTrainTrainLengthText))
.setText(departure.getTrainLengthText());
ImageView colorBar = (ImageView) findViewById(R.id.yourTrainDestinationColorBar);
((GradientDrawable) colorBar.getDrawable()).setColor(Color
.parseColor(departure.getDestinationColor()));
if (departure.isBikeAllowed()) {
((ImageView) findViewById(R.id.yourTrainBikeIcon))
.setVisibility(View.VISIBLE);
} else {
((ImageView) findViewById(R.id.yourTrainBikeIcon))
.setVisibility(View.INVISIBLE);
}
if (departure.getRequiresTransfer()) {
((ImageView) findViewById(R.id.yourTrainXferIcon))
.setVisibility(View.VISIBLE);
} else {
((ImageView) findViewById(R.id.yourTrainXferIcon))
.setVisibility(View.INVISIBLE);
}
CountdownTextView departureCountdown = (CountdownTextView) findViewById(R.id.yourTrainDepartureCountdown);
CountdownTextView arrivalCountdown = (CountdownTextView) findViewById(R.id.yourTrainArrivalCountdown);
departureCountdown.setText("Leaves in " + departure.getCountdownText()
+ " " + departure.getUncertaintyText());
departureCountdown.setTextProvider(new TextProvider() {
@Override
public String getText() {
if (departure.hasDeparted()) {
return "Departed";
} else {
return "Leaves in " + departure.getCountdownText() + " "
+ departure.getUncertaintyText();
}
}
});
arrivalCountdown
.setText(departure.getEstimatedArrivalMinutesLeftText());
arrivalCountdown.setTextProvider(new TextProvider() {
@Override
public String getText() {
return departure.getEstimatedArrivalMinutesLeftText();
}
});
}
} }

View File

@ -0,0 +1,57 @@
package com.dougkeen.bart.controls;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;
import com.dougkeen.bart.model.TextProvider;
public class CountdownTextView extends TextView implements
Ticker.TickSubscriber {
private TextProvider mTextProvider;
private int mTickInterval;
public CountdownTextView(Context context) {
super(context);
}
public CountdownTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setInstanceVarsFromAttrs(attrs);
}
public CountdownTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setInstanceVarsFromAttrs(attrs);
}
private void setInstanceVarsFromAttrs(AttributeSet attrs) {
int tickInterval = attrs.getAttributeIntValue(
"http://schemas.android.com/apk/res/com.dougkeen.bart",
"tickInterval", 0);
if (tickInterval > 0) {
setTickInterval(tickInterval);
}
}
public void setTextProvider(TextProvider provider) {
mTextProvider = provider;
Ticker.getInstance().addSubscriber(this);
}
@Override
public int getTickInterval() {
return mTickInterval;
}
public void setTickInterval(int tickInterval) {
this.mTickInterval = tickInterval;
}
@Override
public void onTick() {
setText(mTextProvider.getText());
}
}

View File

@ -0,0 +1,110 @@
package com.dougkeen.bart.controls;
import java.util.Iterator;
import java.util.WeakHashMap;
import android.os.Handler;
import android.util.Log;
public class Ticker {
public static interface TickSubscriber {
int getTickInterval();
void onTick();
}
private static Ticker sInstance;
private WeakHashMap<TickSubscriber, Object> mSubscribers;
private TickerEngine mEngine;
private static class TickerEngine implements Runnable {
private static final int TICK_INTERVAL_MILLIS = 1000;
private Ticker publisher;
private Handler mHandler;
private boolean mPendingRequest = false;
private boolean mForceStop = false;
private long mTickCount = 0;
public TickerEngine(Ticker publisher) {
this.publisher = publisher;
this.mHandler = new Handler();
}
@Override
public void run() {
mPendingRequest = false;
if (mForceStop) {
mForceStop = false;
return;
}
Log.w("Ticker", "Tick #: " + mTickCount);
long startTimeNanos = System.nanoTime();
Iterator<TickSubscriber> iterator = publisher.mSubscribers.keySet()
.iterator();
boolean stillHasListeners = false;
while (iterator.hasNext()) {
TickSubscriber subscriber = iterator.next();
if (subscriber == null) {
continue;
}
stillHasListeners = true;
if (subscriber.getTickInterval() > 0
&& mTickCount % subscriber.getTickInterval() == 0)
subscriber.onTick();
}
long endTimeNanos = System.nanoTime();
if (stillHasListeners && !mPendingRequest) {
mHandler.postDelayed(this, TICK_INTERVAL_MILLIS
- ((endTimeNanos - startTimeNanos) / 1000000));
mPendingRequest = true;
mTickCount++;
} else {
mPendingRequest = false;
}
}
public boolean isOn() {
return mPendingRequest;
}
public void stop() {
mForceStop = true;
}
};
public synchronized static Ticker getInstance() {
if (sInstance == null) {
sInstance = new Ticker();
}
return sInstance;
}
public void addSubscriber(TickSubscriber subscriber) {
if (!mSubscribers.containsKey(subscriber) && subscriber != null) {
mSubscribers.put(subscriber, null);
startTicking();
}
}
private Ticker() {
mSubscribers = new WeakHashMap<TickSubscriber, Object>();
mEngine = new TickerEngine(this);
}
public void startTicking() {
if (!mEngine.isOn())
mEngine.run();
}
public void stopTicking() {
if (mEngine.isOn())
mEngine.stop();
}
}

View File

@ -0,0 +1,63 @@
package com.dougkeen.bart.controls;
import org.apache.commons.lang3.StringUtils;
import com.dougkeen.bart.model.TextProvider;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextSwitcher;
public class TimedTextSwitcher extends TextSwitcher implements
Ticker.TickSubscriber {
public TimedTextSwitcher(Context context, AttributeSet attrs) {
super(context, attrs);
setInstanceVarsFromAttrs(attrs);
}
public TimedTextSwitcher(Context context) {
super(context);
}
private void setInstanceVarsFromAttrs(AttributeSet attrs) {
int tickInterval = attrs.getAttributeIntValue(
"http://schemas.android.com/apk/res/com.dougkeen.bart",
"tickInterval", 0);
if (tickInterval > 0) {
setTickInterval(tickInterval);
}
}
private int mTickInterval;
private TextProvider[] mTextProviderArray;
private int mCurrentIndex = 0;
@Override
public int getTickInterval() {
return mTickInterval;
}
public void setTickInterval(int tickInterval) {
this.mTickInterval = tickInterval;
}
public void setTextProviders(TextProvider[] textProviders) {
mTextProviderArray = textProviders;
Ticker.getInstance().addSubscriber(this);
}
private String mLastText;
@Override
public void onTick() {
String text = mTextProviderArray[mCurrentIndex].getText();
if (StringUtils.isNotBlank(text)
&& !StringUtils.equalsIgnoreCase(text, mLastText)) {
mLastText = text;
setText(text);
}
mCurrentIndex = (mCurrentIndex + 1) % mTextProviderArray.length;
}
}

View File

@ -219,6 +219,30 @@ public class Departure implements Parcelable, Comparable<Departure> {
return getMeanEstimate() + getEstimatedTripTime(); return getMeanEstimate() + getEstimatedTripTime();
} }
public long getEstimatedArrivalMinutesLeft() {
long millisLeft = getEstimatedArrivalTime()
- System.currentTimeMillis();
if (millisLeft < 0) {
return -1;
} else {
// Add ~30s to emulate rounding
return (millisLeft + 29999) / (60 * 1000);
}
}
public String getEstimatedArrivalMinutesLeftText() {
long minutesLeft = getEstimatedArrivalMinutesLeft();
if (minutesLeft < 0) {
return "Arrived";
} else if (minutesLeft == 0) {
return "Estimated arrival in < 1 min.";
} else if (minutesLeft == 1) {
return "Estimated arrival in 1 min.";
} else {
return "Estimated arrival in " + minutesLeft + " mins.";
}
}
public String getEstimatedArrivalTimeText(Context context) { public String getEstimatedArrivalTimeText(Context context) {
if (getEstimatedTripTime() > 0 || arrivalTimeOverride > 0) { if (getEstimatedTripTime() > 0 || arrivalTimeOverride > 0) {
return DateFormat.getTimeFormat(context).format( return DateFormat.getTimeFormat(context).format(

View File

@ -2,7 +2,9 @@ package com.dougkeen.bart.model;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import android.util.Log; import android.util.Log;
@ -199,6 +201,19 @@ public enum Station {
public List<Route> getTransferRoutes(Station dest) { public List<Route> getTransferRoutes(Station dest) {
List<Route> returnList = new ArrayList<Route>(); List<Route> returnList = new ArrayList<Route>();
/**
* Kind of gimpy logic... no transfers from lake or 12th if headed to
* somewhere between woak and daly
*/
if (this.equals(LAKE) || this.equals(_12TH)) {
int destIndex = Line.BLUE.stations.indexOf(dest);
if (destIndex >= Line.BLUE.stations.indexOf(DALY)
&& destIndex <= Line.BLUE.stations.indexOf(WOAK)) {
return returnList;
}
}
if (dest.getInboundTransferStation() != null) { if (dest.getInboundTransferStation() != null) {
// Try getting to the destination's inbound xfer station first // Try getting to the destination's inbound xfer station first
returnList.addAll(getDirectRoutesForDestination(this, returnList.addAll(getDirectRoutesForDestination(this,

View File

@ -0,0 +1,7 @@
package com.dougkeen.bart.model;
public interface TextProvider {
String getText();
}