/* * Copyright (c) 2013, Will Szumski * Copyright (c) 2013, Doug Szumski * * This file is part of Cyclismo. * * Cyclismo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Cyclismo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Cyclismo. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright 2008 Google Inc. * * 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.cowboycoders.cyclismo; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.database.Cursor; import android.os.Bundle; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; import android.speech.tts.TextToSpeech; import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.support.v4.widget.ResourceCursorAdapter; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.Button; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import org.cowboycoders.cyclismo.content.Bike; import org.cowboycoders.cyclismo.content.BikeInfoColumns; import org.cowboycoders.cyclismo.content.CyclismoProviderUtils; import org.cowboycoders.cyclismo.content.MyTracksProviderUtils; import org.cowboycoders.cyclismo.content.User; import org.cowboycoders.cyclismo.fragments.ConfirmationDialogFragment; import org.cowboycoders.cyclismo.fragments.ConfirmationDialogFragment.DialogCallback; import org.cowboycoders.cyclismo.util.ApiAdapterFactory; import org.cowboycoders.cyclismo.util.IntentUtils; import org.cowboycoders.cyclismo.util.PreferencesUtils; /** * An activity displaying a list of user. * * @author Will Szumski */ public class BikeListActivity extends FragmentActivity { public static final String TAG = BikeListActivity.class.getSimpleName(); private static final String[] PROJECTION = new String[] { BikeInfoColumns._ID, BikeInfoColumns.NAME }; // protected static final int COURSE_SETUP_RESPONSE_CODE = 0; /* * Note that sharedPreferenceChangeListenr cannot be an anonymous inner class. * Anonymous inner class will get garbage collected. */ private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { if (key != null) { if (key.contentEquals(getString(R.string.settings_select_bike_current_selection_key))) { Log.i(TAG," current bike changed"); } else if (key.equals(getString(R.string.settings_select_user_current_selection_key))) { resourceCursorAdapter.notifyDataSetChanged(); } runOnUiThread(new Runnable() { @Override public void run() { boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; updateMenuItems(isRecording); updateUI(); resourceCursorAdapter.notifyDataSetChanged(); } }); } } }; // Callback when an item is selected in the contextual action mode private final ContextualActionModeCallback contextualActionModeCallback = new ContextualActionModeCallback() { @Override public boolean onClick(int itemId, int position, long id) { return handleContextItem(itemId, position); } }; // The following are set in onCreate private SharedPreferences sharedPreferences; private ListView listView; private ResourceCursorAdapter resourceCursorAdapter; private long recordingTrackId = PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; // Menu items private MenuItem deleteAllMenuItem; private CyclismoProviderUtils providerUtils; private Button cancelButton; private Handler mHandler; private static void updateUser(Context context, long bikeId) { final CyclismoProviderUtils providerUtils = MyTracksProviderUtils.Factory.getCyclimso(context); final long currentUserId = PreferencesUtils.getLong(context, R.string.settings_select_user_current_selection_key); User user = providerUtils.getUser(currentUserId); user.setCurrentlySelectedBike(bikeId); providerUtils.updateUser(user); } @SuppressWarnings("unused") private ContextMenu contextMenu; public static final class DeleteBikeCallback implements DialogCallback, Parcelable { private Bike bike; public DeleteBikeCallback(Parcel in) { bike= in.readParcelable(Bike.class.getClassLoader()); } public DeleteBikeCallback(Bike bike) { this.bike = bike; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(bike,flags); } @Override public void onConfirm(final Context context) { final CyclismoProviderUtils providerUtils = MyTracksProviderUtils.Factory.getCyclimso(context); // don't run on ui thread new Thread() { public void run() { long bikeId = bike.getId(); providerUtils.deleteBike(bike.getId()); long currentBike = PreferencesUtils.getLong(context, R.string.settings_select_bike_current_selection_key); // if deleting current user, set to invalid user if (bikeId == currentBike) { updateUser(context,-1L); PreferencesUtils.setLong(context, R.string.settings_select_bike_current_selection_key, -1L); } } }.start(); } public static final Parcelable.Creator<DeleteBikeCallback> CREATOR = new Parcelable.Creator<DeleteBikeCallback>() { public DeleteBikeCallback createFromParcel(Parcel in) { return new DeleteBikeCallback(in); } public DeleteBikeCallback[] newArray(int size) { return new DeleteBikeCallback[size]; } }; @Override public CharSequence getConfirmationMessage(Context context) { String unformated = context.getString(R.string.bike_list_delete_single_confirmation_message); return String.format(unformated, bike.getName()); } @Override public void onFinish(Context context) { Intent intent = IntentUtils.newIntent(context, BikeListActivity.class); context.startActivity(intent); } }; public static final class DeleteAllBikesCallback implements DialogCallback,Parcelable { public DeleteAllBikesCallback(@SuppressWarnings("unused") Parcel in) { } public DeleteAllBikesCallback() { } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { } @Override public void onConfirm(final Context context) { final CyclismoProviderUtils providerUtils = MyTracksProviderUtils.Factory.getCyclimso(context); // don't run on ui thread new Thread() { public void run() { long userId = PreferencesUtils.getLong(context, R.string.settings_select_user_current_selection_key); providerUtils.deleteAllBikes(userId); long currentBike = PreferencesUtils.getLong(context, R.string.settings_select_bike_current_selection_key); if (providerUtils.getBike(currentBike) == null) { updateUser(context,-1L); PreferencesUtils.setLong(context, R.string.settings_select_bike_current_selection_key, -1L); } } }.start(); } public static final Parcelable.Creator<DialogCallback> CREATOR = new Parcelable.Creator<DialogCallback>() { public DialogCallback createFromParcel(Parcel in) { return new DeleteAllBikesCallback(in); } public DialogCallback[] newArray(int size) { return new DialogCallback[size]; } }; @Override public CharSequence getConfirmationMessage(Context context) { return context.getString(R.string.bike_list_delete_all_confirmation_message); } @Override public void onFinish(Context context) { Intent intent = IntentUtils.newIntent(context, BikeListActivity.class); context.startActivity(intent); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM); setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); setContentView(R.layout.generic_list_select); mHandler = new Handler(); this.providerUtils = MyTracksProviderUtils.Factory.getCyclimso(this); sharedPreferences = getSharedPreferences(Constants.SETTINGS_NAME, Context.MODE_PRIVATE); listView = (ListView) findViewById(R.id.generic_list); this.setTitle(this.getString(R.string.my_tracks_app_name) + " : " + this.getString(R.string.bike_list_title)); TextView userEmptyListText = (TextView) findViewById(R.id.generic_list_empty_view); userEmptyListText.setText(R.string.bike_list_empty_message); this.cancelButton = (Button) this.findViewById(R.id.generic_list_left_button); cancelButton.setText(getString(R.string.cancel)); initCancelButton(); Button addUserButton = (Button) this.findViewById(R.id.generic_list_right_button); addUserButton.setText(getString(R.string.bike_create)); addUserButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { //TODO: make bike edit Intent intent = IntentUtils.newIntent(BikeListActivity.this, BikeEditActivity.class) .putExtra(BikeEditActivity.EXTRA_NEW_BIKE, true); intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); // intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); // intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } }); listView.setEmptyView(userEmptyListText); resourceCursorAdapter = new ResourceCursorAdapter(this, R.layout.user_list_item, null, 0) { @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { View view = super.newView(context, cursor, parent); return view; } @Override public void bindView(View view, Context context, Cursor cursor) { final Bike bike = providerUtils.createBike(cursor, false); TextView userName = (TextView) view.findViewById(R.id.list_item_name); userName.setText(bike.getName()); userName.setEnabled(!isRecording()); userName.setVisibility(View.VISIBLE); view.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; if (isRecording) { onUnableToPerformOperation(); } else { new Thread() { public void run() { // update user first as we count on this being updated on shared change updateUser(BikeListActivity.this,bike.getId()); PreferencesUtils.setLong(BikeListActivity.this, R.string.settings_select_bike_current_selection_key, bike.getId()); } }.start(); String unformated = getString(R.string.bike_list_new_user_selected); Toast.makeText(BikeListActivity.this, String.format(unformated, bike.getName()), Toast.LENGTH_SHORT).show(); BikeListActivity.this.doFinish(); } } }); registerForContextMenu(view); } }; listView.setAdapter(resourceCursorAdapter); ApiAdapterFactory.getApiAdapter().configureListViewContextualMenu(this, listView, contextualActionModeCallback, R.menu.user_list_context_menu); getSupportLoaderManager().initLoader(0, null, new LoaderCallbacks<Cursor>() { @Override public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) { long ownerId = PreferencesUtils.getLong(BikeListActivity.this, R.string.settings_select_user_current_selection_key); String selection = BikeInfoColumns.OWNER + "=? OR " + BikeInfoColumns.SHARED + "=?"; // one true, 0 false String [] args = new String[] {Long.toString(ownerId),Integer.toString(1)}; return new CursorLoader(BikeListActivity.this, BikeInfoColumns.CONTENT_URI, PROJECTION, selection, args, "LOWER(" + BikeInfoColumns.NAME + ")"); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { resourceCursorAdapter.swapCursor(cursor); } @Override public void onLoaderReset(Loader<Cursor> loader) { resourceCursorAdapter.swapCursor(null); } }); updateUI(); this.contextMenu = (ContextMenu) findViewById(R.menu.user_list_context_menu); } private void initCancelButton() { cancelButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { doFinish(); } }); } private void doFinish() { BikeListActivity.this.finish(); } private void updateUI() { } @Override protected void onStart() { super.onStart(); // Register shared preferences listener sharedPreferences.registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); // Update shared preferences sharedPreferenceChangeListener.onSharedPreferenceChanged(null, null); } @Override protected void onResume() { super.onResume(); // Update UI boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; updateMenuItems(isRecording); resourceCursorAdapter.notifyDataSetChanged(); } @Override protected void onPause() { super.onPause(); } @Override protected void onStop() { super.onStop(); // Unregister shared preferences listener sharedPreferences.unregisterOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.user_list, menu); deleteAllMenuItem = menu.findItem(R.id.user_list_delete_all); updateMenuItems(isRecording()); return true; } private boolean isRecording() { return recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; } @Override public boolean onOptionsItemSelected(MenuItem item) { boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; switch (item.getItemId()) { case R.id.user_list_delete_all: if (isRecording) { this.onUnableToPerformOperation(); return true; } ConfirmationDialogFragment.newInstance(new DeleteAllBikesCallback()).show( getSupportFragmentManager(), ConfirmationDialogFragment.TAG); return true; default: return super.onOptionsItemSelected(item); } } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); getMenuInflater().inflate(R.menu.user_list_context_menu, menu); MenuItem deleteMenuItem = menu.findItem(R.id.list_context_menu_delete); deleteMenuItem.setVisible(isRecording()); MenuItem editMenuItem = menu.findItem(R.id.list_context_menu_edit); editMenuItem.setVisible(isRecording()); } @Override public boolean onContextItemSelected(MenuItem item) { // ((AdapterContextMenuInfo) item.getMenuInfo()).position) if (item != null && handleContextItem(item.getItemId(), ((AdapterContextMenuInfo) item.getMenuInfo()).position)) { return true; } return super.onContextItemSelected(item); } /** * Updates the menu items. * * @param isRecording true if recording */ private void updateMenuItems(boolean isRecording) { if (deleteAllMenuItem != null) { deleteAllMenuItem.setVisible(!isRecording); } } /** * Handles a context item selection. * * @param itemId the menu item id * @return true if handled. */ private boolean handleContextItem(int itemId, int position) { Intent intent; //listView.getItemAtPosition(position); boolean isRecording = recordingTrackId != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; Bike bike = providerUtils.createBike((Cursor) listView.getItemAtPosition(position), false); switch (itemId) { case R.id.list_context_menu_edit: intent = IntentUtils.newIntent(this, BikeEditActivity.class).putExtra( BikeEditActivity.EXTRA_BIKE, bike); startActivity(intent); return true; case R.id.list_context_menu_delete: if (isRecording) { this.onUnableToPerformOperation(); return true; } ConfirmationDialogFragment.newInstance(new DeleteBikeCallback(bike)).show( getSupportFragmentManager(), ConfirmationDialogFragment.TAG); return true; default: return false; } } protected void onUnableToPerformOperation() { Context context = getApplicationContext(); CharSequence text = getString(0); int duration = Toast.LENGTH_SHORT; Toast toast = Toast.makeText(context, text, duration); toast.show(); } }