/*
* Copyright (C) 2011-2015 Paul Watts (paulcwatts@gmail.com), University of South Florida
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onebusaway.android.ui;
import org.onebusaway.android.R;
import org.onebusaway.android.io.ObaAnalytics;
import org.onebusaway.android.provider.ObaContract;
import org.onebusaway.android.tripservice.TripService;
import org.onebusaway.android.util.FragmentUtils;
import org.onebusaway.android.util.UIUtils;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v7.app.AppCompatActivity;
import android.text.format.DateUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
public class TripInfoActivity extends AppCompatActivity {
private static final String TAG = "TripInfoActivity";
private static final String ROUTE_ID = ".RouteId";
private static final String ROUTE_NAME = ".RouteName";
private static final String STOP_NAME = ".StopName";
private static final String HEADSIGN = ".Headsign";
private static final String DEPARTURE_TIME = ".Depart";
// Save/restore values
private static final String TRIP_NAME = ".TripName";
private static final String REMINDER_TIME = ".ReminderTime";
private static final String REMINDER_DAYS = ".ReminderDays";
public static void start(Context context, String tripId, String stopId) {
Intent myIntent = new Intent(context, TripInfoActivity.class);
myIntent.setData(ObaContract.Trips.buildUri(tripId, stopId));
context.startActivity(myIntent);
}
public static void start(Context context,
String tripId,
String stopId,
String routeId,
String routeName,
String stopName,
long departureTime,
String headsign) {
Intent myIntent = new Intent(context, TripInfoActivity.class);
myIntent.setData(ObaContract.Trips.buildUri(tripId, stopId));
myIntent.putExtra(ROUTE_ID, routeId);
myIntent.putExtra(ROUTE_NAME, routeName);
myIntent.putExtra(STOP_NAME, stopName);
myIntent.putExtra(DEPARTURE_TIME, departureTime);
myIntent.putExtra(HEADSIGN, headsign);
context.startActivity(myIntent);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
UIUtils.setupActionBar(this);
FragmentManager fm = getSupportFragmentManager();
// Create the list fragment and add it as our sole content.
if (fm.findFragmentById(android.R.id.content) == null) {
TripInfoFragment content = new TripInfoFragment();
content.setArguments(FragmentUtils.getIntentArgs(getIntent()));
fm.beginTransaction().add(android.R.id.content, content).commit();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
//UIHelp.goHome(this);
// Since this screen isn't part of a defined heirarchy, we always
// go up from here.
finish();
return true;
}
return false;
}
TripInfoFragment getTripInfoFragment() {
FragmentManager fm = getSupportFragmentManager();
return (TripInfoFragment) fm.findFragmentById(android.R.id.content);
}
public static final class TripInfoFragment extends Fragment
implements LoaderManager.LoaderCallbacks<Cursor> {
private static final String TAG_DELETE_DIALOG = ".DeleteDialog";
private static final String[] PROJECTION = {
ObaContract.Trips.NAME,
ObaContract.Trips.REMINDER,
ObaContract.Trips.DAYS,
ObaContract.Trips.ROUTE_ID,
ObaContract.Trips.HEADSIGN,
ObaContract.Trips.DEPARTURE
};
private static final int COL_NAME = 0;
private static final int COL_REMINDER = 1;
private static final int COL_DAYS = 2;
private static final int COL_ROUTE_ID = 3;
private static final int COL_HEADSIGN = 4;
private static final int COL_DEPARTURE = 5;
private Uri mTripUri;
private String mTripId;
private String mRouteId;
private String mRouteName;
private String mStopId;
private String mStopName;
private String mHeadsign;
private String mTripName;
private long mDepartTime;
private int mReminderTime; // DB Value, not selection value
private int mReminderDays;
private boolean mNewTrip = true;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setHasOptionsMenu(true);
if (savedInstanceState != null) {
initFromBundle(savedInstanceState);
initForm();
} else if (initFromBundle(getArguments())) {
getLoaderManager().initLoader(0, null, this);
} else {
Log.e(TAG, "Information missing from intent");
return;
}
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup root, Bundle savedInstanceState) {
if (root == null) {
// Currently in a layout without a container, so no
// reason to create our view.
return null;
}
return inflater.inflate(R.layout.trip_info, null);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(getActivity(), mTripUri,
PROJECTION, null, null, null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mNewTrip = !initFromCursor(data);
initForm();
getActivity().supportInvalidateOptionsMenu();
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
@Override
public void onStart() {
ObaAnalytics.reportActivityStart(getActivity());
super.onStart();
}
private boolean initFromBundle(Bundle bundle) {
final Uri data = bundle.getParcelable(FragmentUtils.URI);
if (data == null) {
return false;
}
List<String> segments = data.getPathSegments();
mTripId = segments.get(1);
mStopId = segments.get(2);
mTripUri = data;
if (mTripId == null || mStopId == null) {
return false;
}
mRouteId = bundle.getString(ROUTE_ID);
mHeadsign = bundle.getString(HEADSIGN);
mDepartTime = bundle.getLong(DEPARTURE_TIME);
mStopName = bundle.getString(STOP_NAME);
mRouteName = bundle.getString(ROUTE_NAME);
// If we get this, update it in the DB.
if (mRouteName != null) {
ContentValues values = new ContentValues();
values.put(ObaContract.Routes.SHORTNAME, mRouteName);
ObaContract.Routes
.insertOrUpdate(getActivity(), mRouteId, values, false);
}
String name = bundle.getString(TRIP_NAME);
if (name != null) {
mTripName = name;
}
mReminderTime = bundle.getInt(REMINDER_TIME, mReminderTime);
mReminderDays = bundle.getInt(REMINDER_DAYS, mReminderDays);
return true;
}
private boolean initFromCursor(Cursor cursor) {
if (cursor == null || cursor.getCount() < 1) {
// Reminder defaults to 10 in the UI
mReminderTime = 10;
return false;
}
cursor.moveToFirst();
mTripName = cursor.getString(COL_NAME);
mReminderTime = cursor.getInt(COL_REMINDER);
mReminderDays = cursor.getInt(COL_DAYS);
// If some values weren't set in the bundle, assign them the
// values in the db.
if (mRouteId == null) {
mRouteId = cursor.getString(COL_ROUTE_ID);
}
if (mHeadsign == null) {
mHeadsign = cursor.getString(COL_HEADSIGN);
}
if (mDepartTime == 0) {
mDepartTime = ObaContract.Trips.convertDBToTime(cursor
.getInt(COL_DEPARTURE));
}
// If we don't have the route name, look it up in the DB
if (mRouteName == null) {
mRouteName = TripService.getRouteShortName(getActivity(), mRouteId);
}
if (mStopName == null) {
mStopName = UIUtils.stringForQuery(getActivity(), Uri.withAppendedPath(
ObaContract.Stops.CONTENT_URI, mStopId),
ObaContract.Stops.NAME
);
}
return true;
}
private void initForm() {
View view = getView();
final Spinner reminder = (Spinner) view.findViewById(R.id.trip_info_reminder_time);
ArrayAdapter<?> adapter = ArrayAdapter.createFromResource(
getActivity(), R.array.reminder_time, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
reminder.setAdapter(adapter);
//
// Static (header values)
//
final TextView stopName = (TextView) view.findViewById(R.id.stop_name);
stopName.setText(UIUtils.formatDisplayText(mStopName));
final TextView routeName = (TextView) view.findViewById(R.id.route_name);
routeName.setText(
UIUtils.formatDisplayText(getString(R.string.trip_info_route, mRouteName)));
final TextView headsign = (TextView) view.findViewById(R.id.headsign);
headsign.setText(UIUtils.formatDisplayText(mHeadsign));
final TextView departText = (TextView) view.findViewById(R.id.departure_time);
departText.setText(getDepartureTime(getActivity(), mDepartTime));
final TextView tripName = (TextView) view.findViewById(R.id.name);
tripName.setText(mTripName);
reminder.setSelection(reminderToSelection(mReminderTime));
final Button repeats = (Button) view.findViewById(R.id.trip_info_reminder_days);
repeats.setText(getRepeatText(getActivity(), mReminderDays));
//
// Buttons
//
repeats.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
showReminderDaysDialog();
}
});
}
void finish() {
// TODO: We want to be a better citizen, we should not finish our parents
// activity in the case we have some form of dual pane mode for larger screens.
// But for now, the retains original, pre-fragment functionality.
getActivity().finish();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(FragmentUtils.URI, mTripUri);
outState.putString(ROUTE_ID, mRouteId);
outState.putString(ROUTE_NAME, mRouteName);
outState.putString(STOP_NAME, mStopName);
outState.putString(HEADSIGN, mHeadsign);
outState.putLong(DEPARTURE_TIME, mDepartTime);
View view = getView();
Spinner reminderView = (Spinner) view.findViewById(R.id.trip_info_reminder_time);
TextView nameView = (TextView) view.findViewById(R.id.name);
final int reminder = selectionToReminder(reminderView
.getSelectedItemPosition());
outState.putString(TRIP_NAME, nameView.getText().toString());
outState.putInt(REMINDER_TIME, reminder);
outState.putInt(REMINDER_DAYS, mReminderDays);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.trip_info_options, menu);
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.trip_info_delete).setVisible(!mNewTrip);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.trip_info_save) {
saveTrip();
} else if (id == R.id.trip_info_delete) {
Bundle args = new Bundle();
args.putParcelable("uri", mTripUri);
DeleteDialog dialog = new DeleteDialog();
dialog.setArguments(args);
dialog.show(getActivity().getSupportFragmentManager(), TAG_DELETE_DIALOG);
} else if (id == R.id.show_route) {
RouteInfoActivity.start(getActivity(), mRouteId);
return true;
} else if (id == R.id.show_stop) {
new ArrivalsListActivity.Builder(getActivity(), mStopId)
.setStopName(mStopName)
.start();
return true;
}
return false;
}
public void saveTrip() {
// Things that need updating:
// Any constant values (trip info not editable by user)
// Trip name
// Reminder time
// Repeats
//
View view = getView();
final Spinner reminderView = (Spinner) view.findViewById(R.id.trip_info_reminder_time);
final TextView nameView = (TextView) view.findViewById(R.id.name);
final int reminder = selectionToReminder(reminderView
.getSelectedItemPosition());
ContentValues values = new ContentValues();
values.put(ObaContract.Trips.ROUTE_ID, mRouteId);
values.put(ObaContract.Trips.DEPARTURE, ObaContract.Trips
.convertTimeToDB(mDepartTime));
values.put(ObaContract.Trips.HEADSIGN, mHeadsign);
values.put(ObaContract.Trips.NAME, nameView.getText().toString());
values.put(ObaContract.Trips.REMINDER, reminder);
values.put(ObaContract.Trips.DAYS, mReminderDays);
// Insert or update?
ContentResolver cr = getActivity().getContentResolver();
Cursor c = cr.query(mTripUri, new String[]{ObaContract.Trips._ID},
null, null, null);
if (c != null && c.getCount() > 0) {
// Update
cr.update(mTripUri, values, null, null);
} else {
values.put(ObaContract.Trips._ID, mTripId);
values.put(ObaContract.Trips.STOP_ID, mStopId);
cr.insert(ObaContract.Trips.CONTENT_URI, values);
}
if (c != null) {
c.close();
}
TripService.scheduleAll(getActivity());
Toast.makeText(getActivity(), R.string.trip_info_saved, Toast.LENGTH_SHORT)
.show();
finish();
}
void showReminderDaysDialog() {
final boolean[] checks = ObaContract.Trips.daysToArray(mReminderDays);
Bundle args = new Bundle();
args.putBooleanArray(ReminderDaysDialog.CHECKS, checks);
ReminderDaysDialog frag = new ReminderDaysDialog();
frag.setArguments(args);
frag.show(getActivity().getSupportFragmentManager(), ".ReminderDaysDialog");
}
public static class ReminderDaysDialog extends DialogFragment
implements DialogInterface.OnMultiChoiceClickListener,
DialogInterface.OnClickListener {
static final String CHECKS = ".checks";
private boolean[] mChecks;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
mChecks = args.getBooleanArray(CHECKS);
if (savedInstanceState != null) {
mChecks = args.getBooleanArray(CHECKS);
}
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
return builder.setTitle(R.string.trip_info_reminder_repeat)
.setMultiChoiceItems(R.array.reminder_days, mChecks, this)
.setPositiveButton(R.string.trip_info_save, this)
.setNegativeButton(R.string.trip_info_dismiss, null)
.create();
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putBooleanArray(CHECKS, mChecks);
}
@Override
public void onClick(DialogInterface dialog, int which) {
TripInfoActivity act = (TripInfoActivity) getActivity();
// Get the fragment we want...
TripInfoFragment frag = act.getTripInfoFragment();
frag.setReminderDays(mChecks);
dialog.dismiss();
}
@Override
public void onClick(DialogInterface arg0, int which, boolean isChecked) {
mChecks[which] = isChecked;
}
}
private void setReminderDays(boolean[] checks) {
View view = getView();
mReminderDays = ObaContract.Trips.arrayToDays(checks);
final Button repeats = (Button) view.findViewById(R.id.trip_info_reminder_days);
repeats.setText(getRepeatText(getActivity(), mReminderDays));
}
public static class DeleteDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
final Uri tripUri = args.getParcelable("uri");
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder
.setMessage(R.string.trip_info_delete_trip)
.setTitle(R.string.trip_info_delete)
.setIcon(android.R.drawable.ic_dialog_alert)
.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
ContentResolver cr = getActivity().getContentResolver();
cr.delete(tripUri, null, null);
TripService.scheduleAll(getActivity());
getActivity().finish();
}
}
)
.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}
);
return builder.create();
}
}
// This converts what's in the database to what can be displayed in the spinner.
private static int reminderToSelection(int reminder) {
switch (reminder) {
case 0:
return 0;
case 1:
return 1;
case 3:
return 2;
case 5:
return 3;
case 10:
return 4;
case 15:
return 5;
case 20:
return 6;
case 25:
return 7;
case 30:
return 8;
default:
Log.e(TAG, "Invalid reminder value in DB: " + reminder);
return 0;
}
}
private static int selectionToReminder(int selection) {
switch (selection) {
case 0:
return 0;
case 1:
return 1;
case 2:
return 3;
case 3:
return 5;
case 4:
return 10;
case 5:
return 15;
case 6:
return 20;
case 7:
return 25;
case 8:
return 30;
default:
Log.e(TAG, "Invalid selection: " + selection);
return 0;
}
}
}
static String getDepartureTime(Context ctx, long departure) {
return ctx.getString(R.string.trip_info_depart,
DateUtils.formatDateTime(ctx,
departure,
DateUtils.FORMAT_SHOW_TIME |
DateUtils.FORMAT_NO_NOON |
DateUtils.FORMAT_NO_MIDNIGHT
)
);
}
static String getRepeatText(Context ctx, int days) {
final Resources res = ctx.getResources();
if ((days & ObaContract.Trips.DAY_ALL) == ObaContract.Trips.DAY_ALL) {
return res.getString(R.string.trip_info_repeat_everyday);
}
if (((days & ObaContract.Trips.DAY_WEEKDAY) == ObaContract.Trips.DAY_WEEKDAY)
&& (days & ~ObaContract.Trips.DAY_WEEKDAY) == 0) {
return res.getString(R.string.trip_info_repeat_weekdays);
}
if (days == 0) {
return res.getString(R.string.trip_info_repeat_norepeat);
}
// Otherwise, it's not normal -- format a string
final boolean[] array = ObaContract.Trips.daysToArray(days);
final String[] dayNames = res.getStringArray(R.array.reminder_days);
StringBuffer buf = new StringBuffer();
// Find the first day
int rangeStart = 0;
while (rangeStart < 7) {
for (; rangeStart < 7 && !array[rangeStart]; ++rangeStart) {
}
if (rangeStart == 7) {
break;
}
int rangeEnd = rangeStart + 1;
for (; rangeEnd < 7 && array[rangeEnd]; ++rangeEnd) {
}
if (buf.length() != 0) {
// TODO: Move to string table
buf.append(", ");
}
// Single day?
if ((rangeEnd - rangeStart) == 1) {
buf.append(dayNames[rangeStart]);
} else {
buf.append(dayNames[rangeStart]);
// TODO: Move to string table
buf.append(" - ");
buf.append(dayNames[rangeEnd - 1]);
}
rangeStart = rangeEnd;
}
return res.getString(R.string.trip_info_repeat_every, buf.toString());
}
}