/*
* Copyright (C) 2007-2010 OpenIntents.org
*
* 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.openintents.shopping.ui;
import android.app.AlertDialog;
import android.app.Dialog;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.design.widget.Snackbar;
import android.support.v4.view.ActionProvider;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.SubMenu;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.CursorAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.WrapperListAdapter;
import org.openintents.OpenIntents;
import org.openintents.distribution.DistributionLibraryFragmentActivity;
import org.openintents.distribution.DownloadOIAppDialog;
import org.openintents.intents.GeneralIntents;
import org.openintents.intents.ShoppingListIntents;
import org.openintents.provider.Alert;
import org.openintents.shopping.LogConstants;
import org.openintents.shopping.R;
import org.openintents.shopping.ShoppingApplication;
import org.openintents.shopping.library.provider.ShoppingContract;
import org.openintents.shopping.library.provider.ShoppingContract.Contains;
import org.openintents.shopping.library.provider.ShoppingContract.ContainsFull;
import org.openintents.shopping.library.provider.ShoppingContract.Items;
import org.openintents.shopping.library.provider.ShoppingContract.Lists;
import org.openintents.shopping.library.provider.ShoppingContract.Status;
import org.openintents.shopping.library.provider.ShoppingContract.Stores;
import org.openintents.shopping.library.util.PriceConverter;
import org.openintents.shopping.library.util.ShoppingUtils;
import org.openintents.shopping.ui.dialog.DialogActionListener;
import org.openintents.shopping.ui.dialog.EditItemDialog;
import org.openintents.shopping.ui.dialog.EditItemDialog.FieldType;
import org.openintents.shopping.ui.dialog.EditItemDialog.OnItemChangedListener;
import org.openintents.shopping.ui.dialog.NewListDialog;
import org.openintents.shopping.ui.dialog.RenameListDialog;
import org.openintents.shopping.ui.dialog.ThemeDialog;
import org.openintents.shopping.ui.dialog.ThemeDialog.ThemeDialogListener;
import org.openintents.shopping.ui.widget.QuickSelectMenu;
import org.openintents.shopping.ui.widget.ShoppingItemsView;
import org.openintents.shopping.ui.widget.ShoppingItemsView.ActionBarListener;
import org.openintents.shopping.ui.widget.ShoppingItemsView.DragListener;
import org.openintents.shopping.ui.widget.ShoppingItemsView.DropListener;
import org.openintents.shopping.ui.widget.ShoppingItemsView.OnCustomClickListener;
import org.openintents.shopping.widgets.CheckItemsWidget;
import org.openintents.util.MenuIntentOptionsWithIcons;
import org.openintents.util.ShakeSensorListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
/**
* Displays a shopping list.
*/
public class ShoppingActivity extends DistributionLibraryFragmentActivity
implements ThemeDialogListener, OnCustomClickListener,
ActionBarListener, OnItemChangedListener,
UndoListener { // implements
// AdapterView.OnItemClickListener
// {
/**
* TAG for logging.
*/
private static final String TAG = "ShoppingActivity";
private static final boolean debug = LogConstants.debug;
private ItemsFromExtras mItemsFromExtras = new ItemsFromExtras();
private ToggleBoughtInputMethod toggleBoughtInputMethod;
private static final int SWIPE_MIN_DISTANCE = 120;
private static final int SWIPE_MAX_OFF_PATH = 250;
private static final int SWIPE_THRESHOLD_VELOCITY = 200;
private static final int MENU_NEW_LIST = Menu.FIRST;
private static final int MENU_CLEAN_UP_LIST = Menu.FIRST + 1;
private static final int MENU_DELETE_LIST = Menu.FIRST + 2;
private static final int MENU_SHARE = Menu.FIRST + 3;
private static final int MENU_THEME = Menu.FIRST + 4;
private static final int MENU_ADD_LOCATION_ALERT = Menu.FIRST + 5;
private static final int MENU_RENAME_LIST = Menu.FIRST + 6;
private static final int MENU_MARK_ITEM = Menu.FIRST + 7;
private static final int MENU_EDIT_ITEM = Menu.FIRST + 8; // includes rename
private static final int MENU_DELETE_ITEM = Menu.FIRST + 9;
private static final int MENU_INSERT_FROM_EXTRAS = Menu.FIRST + 10;
private static final int MENU_COPY_ITEM = Menu.FIRST + 11;
private static final int MENU_SORT_LIST = Menu.FIRST + 12;
private static final int MENU_SEARCH_ADD = Menu.FIRST + 13;
// TODO: obsolete pick items button, now in drawer
private static final int MENU_PICK_ITEMS = Menu.FIRST + 14;
// TODO: Implement "select list" action
// that can be called by other programs.
// private static final int MENU_SELECT_LIST = Menu.FIRST + 15; // select a
// shopping list
private static final int MENU_PREFERENCES = Menu.FIRST + 17;
private static final int MENU_SEND = Menu.FIRST + 18;
private static final int MENU_REMOVE_ITEM_FROM_LIST = Menu.FIRST + 19;
private static final int MENU_MOVE_ITEM = Menu.FIRST + 20;
private static final int MENU_MARK_ALL_ITEMS = Menu.FIRST + 21;
private static final int MENU_ITEM_STORES = Menu.FIRST + 22;
private static final int MENU_UNMARK_ALL_ITEMS = Menu.FIRST + 23;
private static final int MENU_SYNC_WEAR = Menu.FIRST + 25;
private static final int MENU_DISTRIBUTION_START = Menu.FIRST + 100; // MUST BE LAST
private static final int DIALOG_ABOUT = 1;
// private static final int DIALOG_TEXT_ENTRY = 2;
private static final int DIALOG_NEW_LIST = 2;
private static final int DIALOG_RENAME_LIST = 3;
private static final int DIALOG_EDIT_ITEM = 4;
private static final int DIALOG_DELETE_ITEM = 5;
private static final int DIALOG_THEME = 6;
public static final int DIALOG_GET_FROM_MARKET = 7;
private static final int DIALOG_DISTRIBUTION_START = 100; // MUST BE LAST
private static final int REQUEST_CODE_CATEGORY_ALTERNATIVE = 1;
private static final int REQUEST_PICK_LIST = 2;
public static final int LOADER_TOTALS = 0;
public static final int LOADER_ITEMS = 1;
/**
* The main activity.
* <p/>
* Displays the shopping list that was used last time.
*/
private static final int STATE_MAIN = 0;
/**
* VIEW action on a item/list URI.
*/
private static final int STATE_VIEW_LIST = 1;
/**
* PICK action on an dir/item URI.
*/
private static final int STATE_PICK_ITEM = 2;
/**
* GET_CONTENT action on an item/item URI.
*/
private static final int STATE_GET_CONTENT_ITEM = 3;
/**
* Current state
*/
private int mState;
/*
* Value of PreferenceActivity.updateCount last time we called fillItems().
*/
private int lastAppliedPrefChange = -1;
/**
* mode: separate dialog to add items from existing list
*/
public static final int MODE_PICK_ITEMS_DLG = 3;
/**
* mode: add items from existing list
*/
public static final int MODE_ADD_ITEMS = 2;
/**
* mode: I am in the shop
*/
public static final int MODE_IN_SHOP = 1;
private boolean mEditingFilter;
/**
* URI of current list
*/
private Uri mListUri;
/**
* URI of selected item
*/
private Uri mItemUri;
/**
* URI of current list and item
*/
private Uri mListItemUri;
/**
* Definition of the requestCode for the subactivity.
*/
static final private int SUBACTIVITY_LIST_SHARE_SETTINGS = 0;
/**
* Definition for message handler:
*/
static final private int MESSAGE_UPDATE_CURSORS = 1;
/**
* Update interval for automatic requires.
* <p/>
* (Workaround since ContentObserver does not work.)
*/
private int mUpdateInterval;
private boolean mUpdating;
/**
* Private members connected to list of shopping lists
*/
// Temp - making it generic for tablet compatibility
private AdapterView mShoppingListsView;
private Cursor mCursorShoppingLists;
private static final String[] mStringListFilter = new String[]{Lists._ID,
Lists.NAME, Lists.IMAGE, Lists.SHARE_NAME, Lists.SHARE_CONTACTS,
Lists.SKIN_BACKGROUND};
private static final int mStringListFilterID = 0;
private static final int mStringListFilterNAME = 1;
private static final int mStringListFilterIMAGE = 2;
private static final int mStringListFilterSHARENAME = 3;
private static final int mStringListFilterSHARECONTACTS = 4;
private static final int mStringListFilterSKINBACKGROUND = 5;
private ShoppingItemsView mItemsView;
private DrawerLayout mDrawerLayout;
private ListView mDrawerListsView;
private ActionBarDrawerToggle mDrawerToggle;
private CharSequence mTitle, mDrawerTitle, mSubTitle;
// private Cursor mCursorItems;
public static final String[] PROJECTION_ITEMS = new String[]{
ContainsFull._ID, ContainsFull.ITEM_NAME, ContainsFull.ITEM_IMAGE,
ContainsFull.ITEM_TAGS, ContainsFull.ITEM_PRICE,
ContainsFull.QUANTITY, ContainsFull.STATUS, ContainsFull.ITEM_ID,
ContainsFull.SHARE_CREATED_BY, ContainsFull.SHARE_MODIFIED_BY,
ContainsFull.PRIORITY, ContainsFull.ITEM_HAS_NOTE,
ContainsFull.ITEM_UNITS};
private static final int mStringItemsCONTAINSID = 0;
public static final int mStringItemsITEMNAME = 1;
private static final int mStringItemsITEMIMAGE = 2;
public static final int mStringItemsITEMTAGS = 3;
public static final int mStringItemsITEMPRICE = 4;
public static final int mStringItemsQUANTITY = 5;
public static final int mStringItemsSTATUS = 6;
public static final int mStringItemsITEMID = 7;
private static final int mStringItemsSHARECREATEDBY = 8;
private static final int mStringItemsSHAREMODIFIEDBY = 9;
public static final int mStringItemsPRIORITY = 10;
public static final int mStringItemsITEMHASNOTE = 11;
public static final int mStringItemsITEMUNITS = 12;
private LinearLayout.LayoutParams mLayoutParamsItems;
private int mAllowedListHeight; // Height for the list allowed in this view.
private AutoCompleteTextView mEditText;
private Button mButton;
private View mAddPanel;
private Button mStoresFilterButton;
private Button mTagsFilterButton;
private Button mShoppingListsFilterButton;
// TODO: Set up state information for onFreeze(), ...
// State data to be stored when freezing:
private final String ORIGINAL_ITEM = "original item";
// private static final String BUNDLE_TEXT_ENTRY_MENU = "text entry menu";
// private static final String BUNDLE_CURSOR_ITEMS_POSITION =
// "cursor items position";
private static final String BUNDLE_ITEM_URI = "item uri";
private static final String BUNDLE_RELATION_URI = "relation_uri";
private static final String BUNDLE_MODE = "mode";
private static final String BUNDLE_MODE_BEFORE_SEARCH = "mode_before_search";
private String mSortOrder;
private ListSortActionProvider mListSortActionProvider;
// Skins --------------------------
/**
* Remember position for screen orientation change.
*/
// int mEditItemPosition = -1;
// public int mPriceVisibility;
// private int mTagsVisibility;
private SensorManager mSensorManager;
private SensorEventListener mMySensorListener = new ShakeSensorListener() {
@Override
public void onShake() {
// Provide some visual feedback.
Animation shake = AnimationUtils.loadAnimation(
ShoppingActivity.this, R.anim.shake);
findViewById(R.id.background).startAnimation(shake);
cleanupList();
}
};
/**
* isActive is true only after onResume() and before onPause().
*/
private boolean mIsActive;
/**
* Whether to use the sensor for shake.
*/
private boolean mUseSensor;
private Uri mRelationUri;
private int mMoveItemPosition;
private EditItemDialog.FieldType mEditItemFocusField = EditItemDialog.FieldType.ITEMNAME;
private GestureDetector mGestureDetector;
private View.OnTouchListener mGestureListener;
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
if (debug) {
Log.d(TAG, "Shopping list onCreate()");
}
mSortOrder = PreferenceActivity.getShoppingListSortOrderFromPrefs(this);
mDistribution.setFirst(MENU_DISTRIBUTION_START,
DIALOG_DISTRIBUTION_START);
// Check whether EULA has been accepted
// or information about new version can be presented.
if (false && mDistribution.showEulaOrNewVersion()) {
return;
}
if (LayoutChoiceActivity.show(this)) {
finish();
}
setContentView(R.layout.activity_shopping);
// mEditItemPosition = -1;
// Automatic requeries (once a second)
mUpdateInterval = 2000;
mUpdating = false;
// General Uris:
mListUri = ShoppingContract.Lists.CONTENT_URI;
mItemUri = ShoppingContract.Items.CONTENT_URI;
mListItemUri = ShoppingContract.Items.CONTENT_URI;
int defaultShoppingList = getLastUsedListFromPrefs();
// Handle the calling intent
final Intent intent = getIntent();
final String type = intent.resolveType(this);
final String action = intent.getAction();
if (action == null) {
// Main action
mState = STATE_MAIN;
mListUri = buildDefaultShoppingListUri(defaultShoppingList);
intent.setData(mListUri);
} else if (Intent.ACTION_MAIN.equals(action)) {
// Main action
mState = STATE_MAIN;
mListUri = buildDefaultShoppingListUri(defaultShoppingList);
intent.setData(mListUri);
} else if (Intent.ACTION_VIEW.equals(action)) {
mState = STATE_VIEW_LIST;
mListUri = setListUriFromIntent(intent.getData(), type);
} else if (Intent.ACTION_INSERT.equals(action)) {
mState = STATE_VIEW_LIST;
mListUri = setListUriFromIntent(intent.getData(), type);
} else if (Intent.ACTION_PICK.equals(action)) {
mState = STATE_PICK_ITEM;
mListUri = buildDefaultShoppingListUri(defaultShoppingList);
} else if (Intent.ACTION_GET_CONTENT.equals(action)) {
mState = STATE_GET_CONTENT_ITEM;
mListUri = buildDefaultShoppingListUri(defaultShoppingList);
} else if (GeneralIntents.ACTION_INSERT_FROM_EXTRAS.equals(action)) {
if (ShoppingListIntents.TYPE_STRING_ARRAYLIST_SHOPPING.equals(type)) {
/*
* Need to insert new items from a string array in the intent
* extras Use main action but add an item to the options menu
* for adding extra items
*/
mItemsFromExtras.getShoppingExtras(intent);
mState = STATE_MAIN;
mListUri = buildDefaultShoppingListUri(defaultShoppingList);
intent.setData(mListUri);
} else if (intent.getDataString().startsWith(
ShoppingContract.Lists.CONTENT_URI.toString())) {
// Somewhat quick fix to pass data from ShoppingListsActivity to
// this activity.
// We received a valid shopping list URI:
mListUri = intent.getData();
mItemsFromExtras.getShoppingExtras(intent);
mState = STATE_MAIN;
intent.setData(mListUri);
}
} else {
// Unknown action.
Log.e(TAG, "Shopping: Unknown action, exiting");
Toast.makeText(this, "Don't know how to handle " + action, Toast.LENGTH_SHORT).show();
finish();
return;
}
// hook up all buttons, lists, edit text:
createView();
// populate the lists
fillListFilter();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
// Get last part of URI:
int selectList;
try {
selectList = Integer.parseInt(mListUri.getLastPathSegment());
} catch (NumberFormatException e) {
selectList = defaultShoppingList;
}
// select the default shopping list at the beginning:
setSelectedListId(selectList);
if (icicle != null) {
String prevText = icicle.getString(ORIGINAL_ITEM);
if (prevText != null) {
mEditText.setTextKeepState(prevText);
}
// mTextEntryMenu = icicle.getInt(BUNDLE_TEXT_ENTRY_MENU);
// mEditItemPosition = icicle.getInt(BUNDLE_CURSOR_ITEMS_POSITION);
mItemUri = Uri.parse(icicle.getString(BUNDLE_ITEM_URI));
List<String> pathSegs = mItemUri.getPathSegments();
int num = pathSegs.size();
mListItemUri = Uri
.withAppendedPath(mListUri, pathSegs.get(num - 1));
if (icicle.containsKey(BUNDLE_RELATION_URI)) {
mRelationUri = Uri.parse(icicle.getString(BUNDLE_RELATION_URI));
}
mItemsView.mMode = icicle.getInt(BUNDLE_MODE);
mItemsView.mModeBeforeSearch = icicle.getInt(BUNDLE_MODE_BEFORE_SEARCH);
}
// set focus to the edit line:
mEditText.requestFocus();
// TODO remove initFromPreferences from onCreate
// we need it in resume to update after settings have changed
initFromPreferences();
// now update title and fill all items
onModeChanged();
mItemsView.setActionBarListener(this);
mItemsView.setUndoListener(this);
toggleBoughtInputMethod = ((ShoppingApplication) getApplication()).dependencies().getToggleBoughtInputMethod(this, mItemsView);
}
private Uri buildDefaultShoppingListUri(final int defaultShoppingList) {
return Uri.withAppendedPath(Lists.CONTENT_URI,
String.valueOf(defaultShoppingList));
}
private Uri setListUriFromIntent(Uri data, String type) {
if (ShoppingContract.ITEM_TYPE.equals(type)) {
return ShoppingUtils.getListForItem(this, data
.getLastPathSegment());
} else if (data != null) {
return data;
}
return null;
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
if (mDrawerToggle != null) {
// Sync the toggle state after onRestoreInstanceState has occurred.
mDrawerToggle.syncState();
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mDrawerToggle != null) {
mDrawerToggle.onConfigurationChanged(newConfig);
}
}
@Override
public void onStop() {
super.onStop();
updateWidgets();
}
private void updateWidgets() {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(this);
appWidgetManager.getAppWidgetIds(new ComponentName(this
.getPackageName(), CheckItemsWidget.class.getName()));
List<AppWidgetProviderInfo> b = appWidgetManager.getInstalledProviders();
for (AppWidgetProviderInfo i : b) {
if (i.provider.getPackageName().equals(this.getPackageName())) {
int a[] = appWidgetManager.getAppWidgetIds(i.provider);
new CheckItemsWidget().onUpdate(this, appWidgetManager, a);
}
}
}
// used at startup, otherwise use getSelectedListId
private int getLastUsedListFromPrefs() {
SharedPreferences sp = getSharedPreferences(
"org.openintents.shopping_preferences", MODE_PRIVATE);
return sp.getInt(PreferenceActivity.PREFS_LASTUSED, 1);
}
private void initFromPreferences() {
SharedPreferences sp = getSharedPreferences(
"org.openintents.shopping_preferences", MODE_PRIVATE);
if (mItemsView != null) {
// UGLY WORKAROUND:
// On screen orientation changes, fillItems() is called twice.
// That is why we have to set the list position twice.
// Nov14: Seems to not be required anymore, so change to 1 to
// avoid unwanted scrolling when marking items.
mItemsView.mUpdateLastListPosition = 1;
mItemsView.mLastListPosition = sp.getInt(
PreferenceActivity.PREFS_LASTLIST_POSITION, 0);
mItemsView.mLastListTop = sp.getInt(
PreferenceActivity.PREFS_LASTLIST_TOP, 0);
if (debug) {
Log.d(TAG, "Load list position: pos: "
+ mItemsView.mLastListPosition + ", top: "
+ mItemsView.mLastListTop);
}
// selected list must be set after changing ordering
String sortOrder = PreferenceActivity
.getShoppingListSortOrderFromPrefs(this);
if (!mSortOrder.equals(sortOrder)) {
mSortOrder = sortOrder;
fillListFilter();
setSelectedListId(getLastUsedListFromPrefs());
} else if (getSelectedListId() == -1) {
setSelectedListId(getLastUsedListFromPrefs());
}
}
if (sp.getBoolean(PreferenceActivity.PREFS_SCREENLOCK,
PreferenceActivity.PREFS_SCREENLOCK_DEFAULT)) {
getWindow()
.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
if (mItemsView != null) {
if (sp.getBoolean(PreferenceActivity.PREFS_SHOW_PRICE,
PreferenceActivity.PREFS_SHOW_PRICE_DEFAULT)) {
mItemsView.mPriceVisibility = View.VISIBLE;
} else {
mItemsView.mPriceVisibility = View.GONE;
}
if (sp.getBoolean(PreferenceActivity.PREFS_SHOW_TAGS,
PreferenceActivity.PREFS_SHOW_TAGS_DEFAULT)) {
mItemsView.mTagsVisibility = View.VISIBLE;
} else {
mItemsView.mTagsVisibility = View.GONE;
}
if (sp.getBoolean(PreferenceActivity.PREFS_SHOW_QUANTITY,
PreferenceActivity.PREFS_SHOW_QUANTITY_DEFAULT)) {
mItemsView.mQuantityVisibility = View.VISIBLE;
} else {
mItemsView.mQuantityVisibility = View.GONE;
}
if (sp.getBoolean(PreferenceActivity.PREFS_SHOW_UNITS,
PreferenceActivity.PREFS_SHOW_UNITS_DEFAULT)) {
mItemsView.mUnitsVisibility = View.VISIBLE;
} else {
mItemsView.mUnitsVisibility = View.GONE;
}
if (sp.getBoolean(PreferenceActivity.PREFS_SHOW_PRIORITY,
PreferenceActivity.PREFS_SHOW_PRIORITY_DEFAULT)) {
mItemsView.mPriorityVisibility = View.VISIBLE;
} else {
mItemsView.mPriorityVisibility = View.GONE;
}
}
mUseSensor = sp.getBoolean(PreferenceActivity.PREFS_SHAKE,
PreferenceActivity.PREFS_SHAKE_DEFAULT);
boolean nowEditingFilter = sp.getBoolean(
PreferenceActivity.PREFS_USE_FILTERS,
PreferenceActivity.PREFS_USE_FILTERS_DEFAULT);
if (mStoresFilterButton != null && mEditingFilter != nowEditingFilter) {
updateFilterWidgets();
fillItems(false);
}
if (PreferenceActivity.getCompletionSettingChanged(this)) {
fillAutoCompleteTextViewAdapter(mEditText);
}
}
private void registerSensor() {
if (!mUseSensor) {
// Don't use sensors
return;
}
if (mItemsView.mMode == MODE_IN_SHOP) {
registerAcceleratorSensor();
}
}
private void registerAcceleratorSensor() {
if (mSensorManager == null) {
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
}
mSensorManager.registerListener(mMySensorListener,
mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
SensorManager.SENSOR_DELAY_UI);
}
private void unregisterSensor() {
if (mSensorManager != null) {
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mSensorManager.unregisterListener(mMySensorListener);
}
}
@Override
protected void onResume() {
super.onResume();
((ShoppingApplication) getApplication()).dependencies().onResumeShoppingActivity(this);
// Reload preferences, in case something changed
initFromPreferences();
mIsActive = true;
this.setRequestedOrientation(PreferenceActivity
.getOrientationFromPrefs(this));
if (getSelectedListId() != -1) {
setListTheme(loadListTheme());
applyListTheme();
updateTitle();
}
mItemsView.onResume();
mEditText
.setKeyListener(PreferenceActivity
.getCapitalizationKeyListenerFromPrefs(getApplicationContext()));
if (!mUpdating) {
mUpdating = true;
// mHandler.sendMessageDelayed(mHandler.obtainMessage(
// MESSAGE_UPDATE_CURSORS), mUpdateInterval);
}
if (mItemsFromExtras.hasItems()) {
mItemsFromExtras.insertInto(this, mItemsView);
}
// Items received through intents are added in
// fillItems().
registerSensor();
if (debug) {
Log.i(TAG, "Shopping list onResume() finished");
}
}
private void updateTitle() {
// Modify our overall title depending on the mode we are running in.
// In most cases, "title" is the name of the current list, and subtitle
// depends on the mode -- shopping vs pick items, for example.
mTitle = getCurrentListName();
mSubTitle = getText(R.string.app_name);
if (mState == STATE_MAIN || mState == STATE_VIEW_LIST) {
if (PreferenceActivity
.getPickItemsInListFromPrefs(getApplicationContext())) {
// 2 different modes
if (mItemsView.mMode == MODE_IN_SHOP) {
mSubTitle = getString(R.string.menu_start_shopping);
registerSensor();
} else {
mSubTitle = getString(R.string.menu_pick_items);
unregisterSensor();
}
}
} else if ((mState == STATE_PICK_ITEM)
|| (mState == STATE_GET_CONTENT_ITEM)) {
mSubTitle = (getText(R.string.pick_item));
setTitleColor(0xFFAAAAFF);
}
getSupportActionBar().setTitle(mTitle);
getSupportActionBar().setSubtitle(mSubTitle);
// also update the button label
updateButton();
}
private void updateButton() {
if (mItemsView.mMode == MODE_ADD_ITEMS) {
String newItem = mEditText.getText().toString();
if (TextUtils.isEmpty(newItem)) {
// If in "add items" mode and the text field is empty,
// set the button text to "Shopping"
mButton.setText(R.string.menu_start_shopping);
} else {
mButton.setText(R.string.add);
}
} else {
mButton.setText(R.string.add);
}
}
private void saveActiveList(boolean savePos) {
SharedPreferences sp = getSharedPreferences(
"org.openintents.shopping_preferences", MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
// need to do something about the fact that the spinner is driving this
// even though it isn't always used. also the long vs int is fishy.
// but for now, just don't overwrite previous setting with -1.
int list_id = new Long(getSelectedListId()).intValue();
if (list_id != -1) {
editor.putInt(PreferenceActivity.PREFS_LASTUSED, list_id);
}
// Save position and pixel position of first visible item
// of current shopping list
int listposition = mItemsView.getFirstVisiblePosition();
if (savePos) {
View v = mItemsView.getChildAt(0);
int listtop = (v == null) ? 0 : v.getTop();
if (debug) {
Log.d(TAG, "Save list position: pos: " + listposition + ", top: "
+ listtop);
}
editor.putInt(PreferenceActivity.PREFS_LASTLIST_POSITION, listposition);
editor.putInt(PreferenceActivity.PREFS_LASTLIST_TOP, listtop);
}
editor.commit();
}
/*
* (non-Javadoc)
*
* @see android.app.Activity#onPause()
*/
@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
if (debug) {
Log.i(TAG, "Shopping list onPause()");
}
if (debug) {
Log.i(TAG, "Spinner: onPause: " + mIsActive);
}
mIsActive = false;
if (debug) {
Log.i(TAG, "Spinner: onPause: " + mIsActive);
}
unregisterSensor();
saveActiveList(true);
// TODO ???
/*
* // Unregister refresh intent receiver
* unregisterReceiver(mIntentReceiver);
*/
mItemsView.onPause();
}
public void onDestroy() {
super.onDestroy();
if (toggleBoughtInputMethod != null) {
toggleBoughtInputMethod.release();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (debug) {
Log.i(TAG, "Shopping list onSaveInstanceState()");
}
// Save original text from edit box
String s = mEditText.getText().toString();
outState.putString(ORIGINAL_ITEM, s);
outState.putString(BUNDLE_ITEM_URI, mItemUri.toString());
if (mRelationUri != null) {
outState.putString(BUNDLE_RELATION_URI, mRelationUri.toString());
}
outState.putInt(BUNDLE_MODE, mItemsView.mMode);
outState.putInt(BUNDLE_MODE_BEFORE_SEARCH, mItemsView.mMode);
mUpdating = false;
// after items have been added through an "insert from extras" the
// action name should be different to avoid duplicate inserts e.g. on
// rotation.
if (mItemsFromExtras.hasBeenInserted()
&& GeneralIntents.ACTION_INSERT_FROM_EXTRAS.equals(getIntent()
.getAction())) {
setIntent(getIntent().setAction(Intent.ACTION_VIEW));
}
}
/**
* Hook up buttons, lists, and edittext with functionality.
*/
private void createView() {
// Temp-create either Spinner or List based upon the Display
createList();
mAddPanel = findViewById(R.id.add_panel);
mEditText = (AutoCompleteTextView) findViewById(R.id.autocomplete_add_item);
fillAutoCompleteTextViewAdapter(mEditText);
mEditText.setThreshold(1);
mEditText.setOnKeyListener(new OnKeyListener() {
private int mLastKeyAction = KeyEvent.ACTION_UP;
public boolean onKey(View v, int keyCode, KeyEvent key) {
// Shortcut: Instead of pressing the button,
// one can also press the "Enter" key.
if (debug) {
Log.i(TAG, "Key action: " + key.getAction());
}
if (debug) {
Log.i(TAG, "Key code: " + keyCode);
}
if (keyCode == KeyEvent.KEYCODE_ENTER) {
if (mEditText.isPopupShowing()) {
mEditText.performCompletion();
}
// long key press might cause call of duplicate onKey events
// with ACTION_DOWN
// this would result in inserting an item and showing the
// pick list
if (key.getAction() == KeyEvent.ACTION_DOWN
&& mLastKeyAction == KeyEvent.ACTION_UP) {
insertNewItem();
}
mLastKeyAction = key.getAction();
return true;
}
return false;
}
});
mEditText.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
if (mItemsView.mMode == MODE_ADD_ITEMS) {
// small optimization: Only care about updating
// the button label on each key pressed if we
// are in "add items" mode.
updateButton();
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
}
});
mButton = (Button) findViewById(R.id.button_add_item);
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
insertNewItem();
}
});
mButton.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (PreferenceActivity
.getAddForBarcode(getApplicationContext()) == false) {
if (debug) {
Log.v(TAG,
"barcode scanner on add button long click disabled");
}
return false;
}
Intent intent = new Intent();
intent.setData(mListUri);
intent.setClassName("org.openintents.barcodescanner",
"org.openintents.barcodescanner.BarcodeScanner");
intent.setAction(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
try {
startActivityForResult(intent,
REQUEST_CODE_CATEGORY_ALTERNATIVE);
} catch (ActivityNotFoundException e) {
if (debug) {
Log.v(TAG, "barcode scanner not found");
}
showDialog(DIALOG_GET_FROM_MARKET);
return false;
}
// Instead of calling the class of barcode
// scanner directly, a more generic approach would
// be to use a general activity picker.
//
// TODO: Implement onActivityResult.
// Problem: User has to pick activity every time.
// Choice should be storeable in Stettings.
// Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
// intent.setData(mListUri);
// intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
//
// Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
// pickIntent.putExtra(Intent.EXTRA_INTENT, intent);
// pickIntent.putExtra(Intent.EXTRA_TITLE,
// getText(R.string.title_select_item_from));
// try {
// startActivityForResult(pickIntent,
// REQUEST_CODE_CATEGORY_ALTERNATIVE);
// } catch (ActivityNotFoundException e) {
// Log.v(TAG, "barcode scanner not found");
// return false;
// }
return true;
}
});
mLayoutParamsItems = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
mItemsView = (ShoppingItemsView) findViewById(R.id.list_items);
mItemsView.setThemedBackground(findViewById(R.id.background));
mItemsView.setCustomClickListener(this);
mItemsView.initTotals();
mItemsView.setItemsCanFocus(true);
mItemsView.setDragListener(new DragListener() {
@Override
public void drag(int from, int to) {
if (debug) {
Log.v("DRAG", "" + from + "/" + to);
}
}
});
mItemsView.setDropListener(new DropListener() {
@Override
public void drop(int from, int to) {
if (debug) {
Log.v("DRAG", "" + from + "/" + to);
}
}
});
mItemsView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView parent, View v, int pos, long id) {
Cursor c = (Cursor) parent.getItemAtPosition(pos);
onCustomClick(c, pos, EditItemDialog.FieldType.ITEMNAME, v);
// DO NOT CLOSE THIS CURSOR - IT IS A MANAGED ONE.
// ---- c.close();
}
});
mItemsView
.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu contextmenu,
View view, ContextMenuInfo info) {
contextmenu.add(0, MENU_EDIT_ITEM, 0,
R.string.menu_edit_item).setShortcut('1', 'e');
contextmenu.add(0, MENU_MARK_ITEM, 0,
R.string.menu_mark_item).setShortcut('2', 'm');
contextmenu.add(0, MENU_ITEM_STORES, 0,
R.string.menu_item_stores)
.setShortcut('3', 's');
contextmenu.add(0, MENU_REMOVE_ITEM_FROM_LIST, 0,
R.string.menu_remove_item)
.setShortcut('4', 'r');
contextmenu.add(0, MENU_COPY_ITEM, 0,
R.string.menu_copy_item).setShortcut('5', 'c');
contextmenu.add(0, MENU_DELETE_ITEM, 0,
R.string.menu_delete_item)
.setShortcut('6', 'd');
contextmenu.add(0, MENU_MOVE_ITEM, 0,
R.string.menu_move_item).setShortcut('7', 'l');
}
});
}
private void createList() {
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
mTitle = mDrawerTitle = getTitle();
if (mDrawerLayout != null) {
mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.drawer_open, R.string.drawer_close) {
/**
* Called when a drawer has settled in a completely closed state.
*/
public void onDrawerClosed(View view) {
getSupportActionBar().setTitle(mTitle);
getSupportActionBar().setSubtitle(mSubTitle);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
/**
* Called when a drawer has settled in a completely open state.
*/
public void onDrawerOpened(View drawerView) {
getSupportActionBar().setTitle(mDrawerTitle);
getSupportActionBar().setSubtitle(null);
invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu()
}
};
// Set the drawer toggle as the DrawerListener
mDrawerLayout.setDrawerListener(mDrawerToggle);
}
mDrawerListsView = (ListView) findViewById(R.id.left_drawer);
mShoppingListsView = (Spinner) findViewById(R.id.spinner_listfilter);
mShoppingListsView
.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView parent, View v,
int position, long id) {
if (debug) {
Log.d(TAG, "Spinner: onItemSelected");
}
// Update list cursor:
getSelectedListId();
// Set the theme based on the selected list:
setListTheme(loadListTheme());
// If it's the same list we had before, requery only
// if a preference has changed since then.
fillItems(id == mItemsView.getListId());
updateTitle();
// Apply the theme after the list has been filled:
applyListTheme();
}
public void onNothingSelected(AdapterView arg0) {
if (debug) {
Log.d(TAG, "Spinner: onNothingSelected: "
+ mIsActive);
}
if (mIsActive) {
fillItems(false);
}
}
});
mShoppingListsFilterButton = (Button) findViewById(R.id.listfilter);
if (mShoppingListsFilterButton != null) {
mShoppingListsFilterButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showListFilter(v);
}
});
}
mStoresFilterButton = (Button) findViewById(R.id.storefilter);
mStoresFilterButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showStoresFilter(v);
}
});
mTagsFilterButton = (Button) findViewById(R.id.tagfilter);
mTagsFilterButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showTagsFilter(v);
}
});
}
protected void showListFilter(final View v) {
QuickSelectMenu popup = new QuickSelectMenu(this, v);
int i_list;
Menu menu = popup.getMenu();
if (menu == null) {
return;
}
// get the list of lists
mCursorShoppingLists.requery();
int count = mCursorShoppingLists.getCount();
mCursorShoppingLists.moveToFirst();
for (i_list = 0; i_list < count; i_list++) {
String name = mCursorShoppingLists.getString(mStringListFilterNAME);
menu.add(0, i_list, Menu.NONE, name);
mCursorShoppingLists.moveToNext();
}
popup.setOnItemSelectedListener(new QuickSelectMenu.OnItemSelectedListener() {
public void onItemSelected(CharSequence name, int pos) {
setSelectedListPos(pos);
}
});
popup.show();
}
protected void showStoresFilter(final View v) {
QuickSelectMenu popup = new QuickSelectMenu(this, v);
Menu menu = popup.getMenu();
if (menu == null) {
return;
}
Cursor c = getContentResolver()
.query(Stores.QUERY_BY_LIST_URI.buildUpon()
.appendPath(this.mListUri.getLastPathSegment()).build(),
new String[]{Stores._ID, Stores.NAME}, null, null,
"stores.name COLLATE NOCASE ASC"
);
int i_store, count = c.getCount();
if (count == 0) {
Toast.makeText(this, R.string.no_stores_available,
Toast.LENGTH_SHORT).show();
}
// prepend the "no filter" option
menu.add(0, -1, Menu.NONE, R.string.unfiltered);
// get the list of stores
c.moveToFirst();
for (i_store = 0; i_store < count; i_store++) {
long id = c.getLong(0);
String name = c.getString(1);
menu.add(0, (int) id, Menu.NONE, name);
c.moveToNext();
}
c.deactivate();
c.close();
popup.setOnItemSelectedListener(new QuickSelectMenu.OnItemSelectedListener() {
public void onItemSelected(CharSequence name, int id) {
// set the selected store filter
// update the filter summary? not until filter region collapsed.
ContentValues values = new ContentValues();
values.put(Lists.STORE_FILTER, (long) id);
getContentResolver().update(mListUri, values, null, null);
if (id == -1) {
((Button) v).setText(R.string.stores);
} else {
((Button) v).setText(name);
}
fillItems(false);
}
});
popup.show();
}
protected void showTagsFilter(final View v) {
QuickSelectMenu popup = new QuickSelectMenu(this, v);
Menu menu = popup.getMenu();
if (menu == null) {
return;
}
String[] tags = getTaglist(mListUri.getLastPathSegment());
int i_tag, count = tags.length;
if (count == 0) {
Toast.makeText(this, R.string.no_tags_available, Toast.LENGTH_SHORT)
.show();
}
// prepend the "no filter" option
menu.add(0, -1, Menu.NONE, R.string.unfiltered);
for (i_tag = 0; i_tag < count; i_tag++) {
menu.add(tags[i_tag]);
}
popup.setOnItemSelectedListener(new QuickSelectMenu.OnItemSelectedListener() {
public void onItemSelected(CharSequence name, int id) {
// set the selected tags filter
ContentValues values = new ContentValues();
values.put(Lists.TAGS_FILTER, id == -1 ? "" : (String) name);
getContentResolver().update(mListUri, values, null, null);
if (id == -1) {
((Button) v).setText(R.string.tags);
} else {
((Button) v).setText(name);
}
fillItems(false);
}
});
popup.show();
}
protected void updateFilterWidgets() {
mEditingFilter = PreferenceActivity.getUsingFiltersFromPrefs(this);
mStoresFilterButton.setVisibility(mEditingFilter ? View.VISIBLE : View.GONE);
mTagsFilterButton.setVisibility(mEditingFilter ? View.VISIBLE : View.GONE);
if (mShoppingListsFilterButton != null) {
mShoppingListsFilterButton
.setVisibility(View.GONE);
}
// spinner goes the opposite way
if (mShoppingListsView != null) {
mShoppingListsView.setVisibility(View.GONE);
}
if (mEditingFilter) {
String storeName = ShoppingUtils.getListFilterStoreName(this,
mListUri);
if (storeName != null) {
mStoresFilterButton.setText(storeName);
} else {
mStoresFilterButton.setText(R.string.stores);
}
String tagFilter = ShoppingUtils.getListTagsFilter(this, mListUri);
if (tagFilter != null) {
mTagsFilterButton.setText(tagFilter);
} else {
mTagsFilterButton.setText(R.string.tags);
}
}
}
public void onCustomClick(Cursor c, int pos,
EditItemDialog.FieldType field, View clicked_view) {
if (mState == STATE_PICK_ITEM) {
pickItem(c);
} else {
if (mItemsView.mShowCheckBox) {
boolean handled = false;
// In default theme, there is an extra check box,
// so clicking on anywhere else means to edit the
// item.
if (field == EditItemDialog.FieldType.PRICE
&& PreferenceActivity
.getUsingPerStorePricesFromPrefs(this))
// should really be a per-list preference
{
editItemStores(pos);
handled = true;
}
if ((field == EditItemDialog.FieldType.PRIORITY || field == EditItemDialog.FieldType.QUANTITY)
&& PreferenceActivity.getQuickEditModeFromPrefs(this)) {
handled = QuickEditFieldPopupMenu(c, pos, field,
clicked_view);
}
if (!handled) {
editItem(pos, field);
}
} else {
// For themes without a checkbox, clicking anywhere means
// to toggle the item.
mItemsView.toggleItemBought(pos);
}
}
}
private boolean QuickEditFieldPopupMenu(final Cursor c, final int pos,
final FieldType field, View v) {
QuickSelectMenu popup = new QuickSelectMenu(this, v);
Menu menu = popup.getMenu();
if (menu == null) {
return false;
}
menu.add("1");
menu.add("2");
menu.add("3");
menu.add("4");
menu.add("5");
if (field == FieldType.QUANTITY) {
menu.add(R.string.otherqty);
}
if (field == FieldType.PRIORITY) {
menu.add(R.string.otherpri);
}
popup.setOnItemSelectedListener(new QuickSelectMenu.OnItemSelectedListener() {
public void onItemSelected(CharSequence name, int id) {
// TODO: use a flavor of menu.add which takes id,
// then identifying the selection becomes easier here.
if (name.length() > 1) {
// Other ... use edit dialog
editItem(pos, field);
} else {
long number = name.charAt(0) - '0';
ContentValues values = new ContentValues();
switch (field) {
case PRIORITY:
values.put(Contains.PRIORITY, number);
break;
case QUANTITY:
values.put(Contains.QUANTITY, number);
break;
default:
break;
}
mItemsView.mCursorItems.moveToPosition(pos);
String containsId = mItemsView.mCursorItems
.getString(mStringItemsCONTAINSID);
Uri uri = Uri.withAppendedPath(
ShoppingContract.Contains.CONTENT_URI, containsId);
getApplicationContext().getContentResolver().update(uri,
values, null, null);
onItemChanged(); // probably overkill
mItemsView.updateTotal();
}
}
});
popup.show();
return true;
}
/**
* Inserts new item from edit box into currently selected shopping list.
*/
private void insertNewItem() {
String newItem = mEditText.getText().toString().trim();
// Only add if there is something to add:
if (newItem.length() > 0) {
long listId = getSelectedListId();
if (listId < 0) {
// No valid list - probably view is not active
// and no item is selected.
return;
}
mItemsView.insertNewItem(this, newItem, null, null, null, null);
mEditText.setText("");
fillAutoCompleteTextViewAdapter(mEditText);
} else {
// Open list to select item from
pickItems();
}
}
/**
* Picks an item and returns to calling activity.
*/
private void pickItem(Cursor c) {
long itemId = c.getLong(mStringItemsITEMID);
Uri url = ContentUris.withAppendedId(
ShoppingContract.Items.CONTENT_URI, itemId);
Intent intent = new Intent();
intent.setData(url);
setResult(RESULT_OK, intent);
finish();
}
// Menu
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
/*
* int MENU_ACTION_WITH_TEXT=0;
*
* //Temp- for backward compatibility with OS 3 features
*
* if(!usingListSpinner()){ try{ //setting the value equivalent to
* desired expression
* //MenuItem.SHOW_AS_ACTION_IF_ROOM|MenuItem.SHOW_AS_ACTION_WITH_TEXT
* java.lang.reflect.Field
* field=MenuItem.class.getDeclaredField("SHOW_AS_ACTION_IF_ROOM");
* MENU_ACTION_WITH_TEXT=field.getInt(MenuItem.class);
* field=MenuItem.class.getDeclaredField("SHOW_AS_ACTION_WITH_TEXT");
* MENU_ACTION_WITH_TEXT|=field.getInt(MenuItem.class); }catch(Exception
* e){ //reset value irrespective of cause MENU_ACTION_WITH_TEXT=0; }
*
* }
*/
// Add menu option for auto adding items from string array in intent
// extra if they exist
if (mItemsFromExtras.hasItems()) {
menu.add(0, MENU_INSERT_FROM_EXTRAS, 0, R.string.menu_auto_add)
.setIcon(android.R.drawable.ic_menu_upload);
}
MenuItem item;
View searchView = mItemsView.getSearchView();
if (searchView != null) {
item = menu.add(0, MENU_SEARCH_ADD, 0, R.string.menu_search_add);
MenuItemCompat.setActionView(item, searchView);
MenuItemCompat.setShowAsAction(item, MenuItem.SHOW_AS_ACTION_ALWAYS);
}
mAddPanel.setVisibility(searchView == null ? View.VISIBLE : View.GONE);
item = menu.add(0, MENU_SORT_LIST, 0, R.string.menu_sort_list)
.setIcon(android.R.drawable.ic_menu_sort_alphabetically);
MenuItemCompat.setShowAsAction(item, MenuItem.SHOW_AS_ACTION_IF_ROOM
| MenuItem.SHOW_AS_ACTION_WITH_TEXT);
if (mListSortActionProvider == null) {
mListSortActionProvider = new ListSortActionProvider(this);
}
MenuItemCompat.setActionProvider(item, mListSortActionProvider);
// Standard menu
// tentatively moved "new list" to drawer
//item = menu.add(0, MENU_NEW_LIST, 0, R.string.new_list)
// .setIcon(R.drawable.ic_menu_add_list).setShortcut('0', 'n');
// MenuCompat.setShowAsAction(item, MenuItem.SHOW_AS_ACTION_IF_ROOM);
item = menu.add(0, MENU_CLEAN_UP_LIST, 0, R.string.clean_up_list)
.setIcon(R.drawable.ic_menu_cleanup).setShortcut('1', 'c');
MenuItemCompat.setShowAsAction(item, MenuItem.SHOW_AS_ACTION_IF_ROOM
| MenuItem.SHOW_AS_ACTION_WITH_TEXT);
menu.add(0, MENU_PICK_ITEMS, 0, R.string.menu_pick_items)
.setIcon(android.R.drawable.ic_menu_add).setShortcut('2', 'p').
// tentatively replaced by buttons in drawer.
setVisible(false);
/*
* menu.add(0, MENU_SHARE, 0, R.string.share)
* .setIcon(R.drawable.contact_share001a) .setShortcut('4', 's');
*/
menu.add(0, MENU_THEME, 0, R.string.theme)
.setIcon(android.R.drawable.ic_menu_manage)
.setShortcut('3', 't');
menu.add(0, MENU_PREFERENCES, 0, R.string.preferences)
.setIcon(android.R.drawable.ic_menu_preferences)
.setShortcut('4', 'p');
menu.add(0, MENU_RENAME_LIST, 0, R.string.rename_list)
.setIcon(android.R.drawable.ic_menu_edit).setShortcut('5', 'r');
menu.add(0, MENU_DELETE_LIST, 0, R.string.delete_list).setIcon(
android.R.drawable.ic_menu_delete);
menu.add(0, MENU_SEND, 0, R.string.send)
.setIcon(android.R.drawable.ic_menu_send).setShortcut('7', 's');
if (addLocationAlertPossible()) {
menu.add(0, MENU_ADD_LOCATION_ALERT, 0, R.string.shopping_add_alert)
.setIcon(android.R.drawable.ic_menu_mylocation)
.setShortcut('8', 'l');
}
menu.add(0, MENU_MARK_ALL_ITEMS, 0, R.string.mark_all_items)
.setIcon(android.R.drawable.ic_menu_agenda)
.setShortcut('9', 'm');
menu.add(0, MENU_UNMARK_ALL_ITEMS, 0, R.string.unmark_all_items);
menu.add(0, MENU_SYNC_WEAR, 0, R.string.sync_wear);
// Add distribution menu items last.
mDistribution.onCreateOptionsMenu(menu);
// NOTE:
// Dynamically added menu items are included in onPrepareOptionsMenu()
// instead of here!
// (Explanation see there.)
return true;
}
/**
* Check whether an application exists that handles the pick activity.
*
* @return
*/
private boolean addLocationAlertPossible() {
// Test whether intent exists for picking a location:
PackageManager pm = getPackageManager();
Intent intent = new Intent(Intent.ACTION_PICK, Uri.parse("geo:"));
List<ResolveInfo> resolve_pick_location = pm.queryIntentActivities(
intent, PackageManager.MATCH_DEFAULT_ONLY);
/*
* for (int i = 0; i < resolve_pick_location.size(); i++) { Log.d(TAG,
* "Activity name: " + resolve_pick_location.get(i).activityInfo.name);
* }
*/
// Check whether adding alerts is possible.
intent = new Intent(Intent.ACTION_VIEW, Alert.Generic.CONTENT_URI);
List<ResolveInfo> resolve_view_alerts = pm.queryIntentActivities(
intent, PackageManager.MATCH_DEFAULT_ONLY);
boolean pick_location_possible = (resolve_pick_location.size() > 0);
boolean view_alerts_possible = (resolve_view_alerts.size() > 0);
if (debug) {
Log.d(TAG, "Pick location possible: " + pick_location_possible);
}
if (debug) {
Log.d(TAG, "View alerts possible: " + view_alerts_possible);
}
if (pick_location_possible && view_alerts_possible) {
return true;
}
return false;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
boolean drawerOpen = mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mDrawerListsView);
boolean holoSearch = PreferenceActivity.getUsingHoloSearchFromPrefs(this);
// Add menu option for auto adding items from string array in intent
// extra if they exist
if (mItemsFromExtras.hasBeenInserted()) {
menu.removeItem(MENU_INSERT_FROM_EXTRAS);
}
// Selected list:
long listId = getSelectedListId();
// set menu title for change mode
MenuItem menuItem = menu.findItem(MENU_PICK_ITEMS);
if (mItemsView.mMode == MODE_ADD_ITEMS) {
menuItem.setTitle(R.string.menu_start_shopping);
menuItem.setIcon(android.R.drawable.ic_menu_myplaces);
} else {
menu.findItem(MENU_PICK_ITEMS).setTitle(R.string.menu_pick_items);
menuItem.setIcon(android.R.drawable.ic_menu_add);
}
menuItem = menu.findItem(MENU_SEARCH_ADD);
if (menuItem != null) {
menuItem.setVisible(holoSearch && !drawerOpen);
if (!holoSearch) {
mAddPanel.setVisibility(View.VISIBLE);
}
View searchView = menuItem.getActionView();
int searchImgId = getResources().getIdentifier("android:id/search_button", null, null);
View imageView = searchView.findViewById(searchImgId);
if (imageView instanceof ImageView) {
((ImageView) imageView).setImageResource(android.R.drawable.ic_menu_add);
}
}
menuItem = menu.findItem(MENU_SYNC_WEAR);
if (menuItem != null) {
menuItem.setVisible(mItemsView.isWearSupportAvailable());
}
menu.findItem(MENU_MARK_ALL_ITEMS).setVisible(mItemsView.mNumUnchecked > 0);
menu.findItem(MENU_UNMARK_ALL_ITEMS).setVisible(mItemsView.mNumChecked > 0);
menu.findItem(MENU_CLEAN_UP_LIST).setEnabled(
mItemsView.mNumChecked > 0).setVisible(!drawerOpen);
// Delete list is possible, if we have more than one list:
// AND
// the current list is not the default list (listId == 0) - issue #105
// TODO: Later, the default list should be user-selectable,
// and not deletable.
// TODO ???
/*
* menu.setItemShown(MENU_DELETE_LIST, mCursorListFilter.count() > 1 &&
* listId != 1); // 1 is hardcoded number of default first list.
*/
// The following code is put from onCreateOptionsMenu to
// onPrepareOptionsMenu,
// because the URI of the shopping list can change if the user switches
// to another list.
// Generate any additional actions that can be performed on the
// overall list. This allows other applications to extend
// our menu with their own actions.
Intent intent = new Intent(null, getIntent().getData());
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
// menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
// new ComponentName(this, NoteEditor.class), null, intent, 0, null);
// Workaround to add icons:
MenuIntentOptionsWithIcons menu2 = new MenuIntentOptionsWithIcons(this,
menu);
menu2.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
new ComponentName(this,
org.openintents.shopping.ShoppingActivity.class), null,
intent, 0, null
);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (debug) {
Log.d(TAG, "onOptionsItemSelected getItemId: " + item.getItemId());
}
Intent intent;
switch (item.getItemId()) {
case MENU_NEW_LIST:
showDialog(DIALOG_NEW_LIST);
return true;
case MENU_CLEAN_UP_LIST:
cleanupList();
return true;
case MENU_RENAME_LIST:
showDialog(DIALOG_RENAME_LIST);
return true;
case MENU_DELETE_LIST:
deleteListConfirm();
return true;
case MENU_PICK_ITEMS:
pickItems();
return true;
case MENU_SHARE:
setShareSettings();
return true;
case MENU_THEME:
setThemeSettings();
return true;
case MENU_ADD_LOCATION_ALERT:
addLocationAlert();
return true;
case MENU_PREFERENCES:
intent = new Intent(this, PreferenceActivity.class);
startActivity(intent);
return true;
case MENU_SEND:
sendList();
return true;
case MENU_INSERT_FROM_EXTRAS:
mItemsFromExtras.insertInto(this, mItemsView);
return true;
case MENU_MARK_ALL_ITEMS:
mItemsView.toggleAllItems(true);
return true;
case MENU_UNMARK_ALL_ITEMS:
mItemsView.toggleAllItems(false);
return true;
case MENU_SYNC_WEAR:
mItemsView.pushItemsToWear();
return true;
default:
break;
}
if (debug) {
Log.d(TAG, "Start intent group id : " + item.getGroupId());
}
if (Menu.CATEGORY_ALTERNATIVE == item.getGroupId()) {
// Start alternative cateogory intents with option to return a
// result.
if (debug) {
Log.d(TAG, "Start alternative intent for : "
+ item.getIntent().getDataString());
}
startActivityForResult(item.getIntent(),
REQUEST_CODE_CATEGORY_ALTERNATIVE);
return true;
}
if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
*
*/
private void pickItems() {
if (PreferenceActivity
.getPickItemsInListFromPrefs(getApplicationContext())) {
if (mItemsView.mMode == MODE_IN_SHOP) {
mItemsView.mMode = MODE_ADD_ITEMS;
} else {
mItemsView.mMode = MODE_IN_SHOP;
}
onModeChanged();
} else {
if (mItemsView.mMode != MODE_IN_SHOP) {
mItemsView.mMode = MODE_IN_SHOP;
onModeChanged();
}
pickItemsUsingDialog();
}
}
private void pickItemsUsingDialog() {
Intent intent;
intent = new Intent(this, PickItemsActivity.class);
intent.setData(mListUri);
startActivity(intent);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
super.onContextItemSelected(item);
AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo) item
.getMenuInfo();
switch (item.getItemId()) {
case MENU_MARK_ITEM:
markItem(menuInfo.position);
break;
case MENU_EDIT_ITEM:
editItem(menuInfo.position, EditItemDialog.FieldType.ITEMNAME);
break;
case MENU_REMOVE_ITEM_FROM_LIST:
removeItemFromList(menuInfo.position);
break;
case MENU_DELETE_ITEM:
deleteItemDialog(menuInfo.position);
break;
case MENU_MOVE_ITEM:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_PICK);
intent.setData(ShoppingContract.Lists.CONTENT_URI);
startActivityForResult(intent, REQUEST_PICK_LIST);
mMoveItemPosition = menuInfo.position;
break;
case MENU_COPY_ITEM:
copyItem(menuInfo.position);
break;
case MENU_ITEM_STORES:
editItemStores(menuInfo.position);
break;
default:
break;
}
return true;
}
// /////////////////////////////////////////////////////
//
// Menu functions
//
/**
* Creates a new list from dialog.
*
* @return true if new list was created. False if new list was not created,
* because user has not given any name.
*/
private boolean createNewList(String name) {
if (name.equals("")) {
// User has not provided any name
Toast.makeText(this, getString(R.string.please_enter_name),
Toast.LENGTH_SHORT).show();
return false;
}
String previousTheme = loadListTheme();
int newId = (int) ShoppingUtils.getList(this, name);
fillListFilter();
setSelectedListId(newId);
// Now set the theme based on the selected list:
saveListTheme(previousTheme);
setListTheme(previousTheme);
applyListTheme();
return true;
}
private void setListTheme(String theme) {
mItemsView.setListTheme(theme);
}
private void applyListTheme() {
mItemsView.applyListTheme();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// In Holo themes, apply the theme text color also to the
// input box and the button, because the background is
// semi-transparent.
mEditText.setTextColor(mItemsView.mTextColor);
if (mStoresFilterButton != null) {
mStoresFilterButton.setTextColor(mItemsView.mTextColor);
}
if (mTagsFilterButton != null) {
mTagsFilterButton.setTextColor(mItemsView.mTextColor);
}
if (mShoppingListsFilterButton != null) {
mShoppingListsFilterButton.setTextColor(mItemsView.mTextColor);
}
if (mShoppingListsView instanceof Spinner) {
View view = mShoppingListsView.getChildAt(0);
setSpinnerTextColorInHoloTheme(view);
}
}
}
/**
* For holo themes with transparent widgets, set font color of the spinner
* using theme color.
*
* @param view
*/
private void setSpinnerTextColorInHoloTheme(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (view instanceof TextView) {
((TextView) view).setTextColor(mItemsView.mTextColor);
}
}
}
/**
* Rename list from dialog.
*
* @return true if new list was renamed. False if new list was not renamed,
* because user has not given any name.
*/
private boolean renameList(String newName) {
if (newName.equals("")) {
// User has not provided any name
Toast.makeText(this, getString(R.string.please_enter_name),
Toast.LENGTH_SHORT).show();
return false;
}
// Rename currently selected list:
long listId = getSelectedListId();
ContentValues values = new ContentValues();
values.put(Lists.NAME, "" + newName);
getContentResolver().update(mListUri, values, null, null
);
//
mCursorShoppingLists.requery();
setSelectedListId((int) listId);
updateTitle();
return true;
}
private void sendList() {
if (mItemsView.getAdapter() instanceof CursorAdapter) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mItemsView.getAdapter().getCount(); i++) {
Cursor item = (Cursor) mItemsView.getAdapter().getItem(i);
if (item.getLong(mStringItemsSTATUS) == ShoppingContract.Status.BOUGHT) {
sb.append("[X] ");
} else {
sb.append("[ ] ");
}
String quantity = item.getString(mStringItemsQUANTITY);
long pricecent = item.getLong(mStringItemsITEMPRICE);
String price = PriceConverter.getStringFromCentPrice(pricecent);
String tags = item.getString(mStringItemsITEMTAGS);
if (!TextUtils.isEmpty(quantity)) {
sb.append(quantity);
sb.append(" ");
}
String units = item.getString(mStringItemsITEMUNITS);
if (!TextUtils.isEmpty(units)) {
sb.append(units);
sb.append(" ");
}
sb.append(item.getString(mStringItemsITEMNAME));
// Put additional info (price, tags) in brackets
boolean p = !TextUtils.isEmpty(price);
boolean t = !TextUtils.isEmpty(tags);
if (p || t) {
sb.append(" (");
if (p) {
sb.append(price);
}
if (p && t) {
sb.append(", ");
}
if (t) {
sb.append(tags);
}
sb.append(")");
}
sb.append("\n");
}
Intent i = new Intent();
i.setAction(Intent.ACTION_SEND);
i.setType("text/plain");
i.putExtra(Intent.EXTRA_SUBJECT, getCurrentListName());
i.putExtra(Intent.EXTRA_TEXT, sb.toString());
try {
startActivity(Intent.createChooser(i, getString(R.string.send)));
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.email_not_available,
Toast.LENGTH_SHORT).show();
Log.e(TAG, "Email client not installed");
}
} else {
Toast.makeText(this, R.string.empty_list_not_sent,
Toast.LENGTH_SHORT).show();
}
}
/**
* Clean up the currently visible shopping list by removing items from list
* that are marked BOUGHT.
*/
private void cleanupList() {
// Remove all items from current list
// which have STATUS = Status.BOUGHT
if (!mItemsView.cleanupList()) {
// Show toast
Toast.makeText(this, R.string.no_items_marked, Toast.LENGTH_SHORT)
.show();
}
}
// TODO: Convert into proper dialog that remains across screen orientation
// changes.
/**
* Confirm 'delete list' command by AlertDialog.
*/
private void deleteListConfirm() {
new AlertDialog.Builder(this)
// .setIcon(R.drawable.alert_dialog_icon)
.setTitle(R.string.confirm_delete_list)
.setPositiveButton(R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
// click Ok
deleteList();
}
}
)
.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
// click Cancel
}
}
)
// .create()
.show();
}
/**
* Deletes currently selected shopping list.
*/
private void deleteList() {
String listId = mCursorShoppingLists.getString(0);
ShoppingUtils.deleteList(this, listId);
// Update view
fillListFilter();
getSelectedListId();
// Set the theme based on the selected list:
setListTheme(loadListTheme());
fillItems(false);
applyListTheme();
updateTitle();
}
/**
* Mark item
*/
void markItem(int position) {
mItemsView.toggleItemBought(position);
}
/**
* Edit item
*
* @param field
*/
void editItem(long itemId, long containsId, EditItemDialog.FieldType field) {
mItemUri = Uri.withAppendedPath(ShoppingContract.Items.CONTENT_URI, Long.toString(itemId));
mListItemUri = Uri.withAppendedPath(mListUri, Long.toString(itemId));
mRelationUri = Uri.withAppendedPath(
ShoppingContract.Contains.CONTENT_URI, Long.toString(containsId));
mEditItemFocusField = field;
showDialog(DIALOG_EDIT_ITEM);
}
/**
* Edit item
*
* @param field
*/
void editItem(int position, EditItemDialog.FieldType field) {
if (debug) {
Log.d(TAG, "EditItems: Position: " + position);
}
mItemsView.mCursorItems.moveToPosition(position);
// mEditItemPosition = position;
long itemId = mItemsView.mCursorItems.getLong(mStringItemsITEMID);
long containsId = mItemsView.mCursorItems
.getLong(mStringItemsCONTAINSID);
editItem(itemId, containsId, field);
}
void editItemStores(int position) {
if (debug) {
Log.d(TAG, "EditItemStores: Position: " + position);
}
mItemsView.mCursorItems.moveToPosition(position);
// mEditItemPosition = position;
long itemId = mItemsView.mCursorItems.getLong(mStringItemsITEMID);
Intent intent;
intent = new Intent(this, ItemStoresActivity.class);
intent.setData(mListUri.buildUpon().appendPath(String.valueOf(itemId))
.build());
startActivity(intent);
}
private int mDeleteItemPosition;
/**
* delete item
*/
void deleteItemDialog(int position) {
if (debug) {
Log.d(TAG, "EditItems: Position: " + position);
}
mItemsView.mCursorItems.moveToPosition(position);
mDeleteItemPosition = position;
showDialog(DIALOG_DELETE_ITEM);
}
/**
* delete item
*/
void deleteItem(int position) {
Cursor c = mItemsView.mCursorItems;
c.moveToPosition(position);
String listId = mListUri.getLastPathSegment();
String itemId = c.getString(mStringItemsITEMID);
ShoppingUtils.deleteItem(this, itemId, listId);
// c.requery();
mItemsView.requery();
fillAutoCompleteTextViewAdapter(mEditText);
}
/**
* move item
*/
void moveItem(int position, int targetListId) {
Cursor c = mItemsView.mCursorItems;
mItemsView.mCursorItems.requery();
c.moveToPosition(position);
long listId = getSelectedListId();
if (false && listId < 0) {
// No valid list - probably view is not active
// and no item is selected.
return;
}
// Attach item to new list, preserving all other fields
String containsId = c.getString(mStringItemsCONTAINSID);
ContentValues cv = new ContentValues(1);
cv.put(Contains.LIST_ID, targetListId);
getContentResolver().update(
Uri.withAppendedPath(Contains.CONTENT_URI, containsId), cv,
null, null);
mItemsView.requery();
}
/**
* copy item
*/
void copyItem(int position) {
Cursor c = mItemsView.mCursorItems;
mItemsView.mCursorItems.requery();
c.moveToPosition(position);
String containsId = c.getString(mStringItemsCONTAINSID);
Long newContainsId;
Long newItemId;
c = getContentResolver().query(
Uri.withAppendedPath(
Uri.withAppendedPath(Contains.CONTENT_URI, "copyof"),
containsId), new String[]{"item_id", "contains_id"},
null, null, null
);
if (c.getCount() != 1) {
return;
}
c.moveToFirst();
newItemId = c.getLong(0);
newContainsId = c.getLong(1);
c.deactivate();
c.close();
editItem(newItemId, newContainsId, FieldType.ITEMNAME);
// mItemsView.requery();
}
/**
* removeItemFromList
*/
void removeItemFromList(int position) {
Cursor c = mItemsView.mCursorItems;
c.moveToPosition(position);
// Remember old values before delete (for share below)
String itemName = c.getString(mStringItemsITEMNAME);
long oldstatus = c.getLong(mStringItemsSTATUS);
// Delete item by changing its state
ContentValues values = new ContentValues();
values.put(Contains.STATUS, Status.REMOVED_FROM_LIST);
if (PreferenceActivity.getResetQuantity(getApplicationContext())) {
values.put(Contains.QUANTITY, "");
}
getContentResolver().update(Contains.CONTENT_URI, values, "_id = ?",
new String[]{c.getString(mStringItemsCONTAINSID)});
// c.requery();
mItemsView.requery();
// If we share items, mark item on other lists:
// TODO ???
/*
* String recipients =
* mCursorListFilter.getString(mStringListFilterSHARECONTACTS); if (!
* recipients.equals("")) { String shareName =
* mCursorListFilter.getString(mStringListFilterSHARENAME); long
* newstatus = Shopping.Status.BOUGHT;
*
* Log.i(TAG, "Update shared item. " + " recipients: " + recipients +
* ", shareName: " + shareName + ", status: " + newstatus);
* mGTalkSender.sendItemUpdate(recipients, shareName, itemName,
* itemName, oldstatus, newstatus); }
*/
}
/**
* Calls the share settings with the currently selected list.
*/
void setShareSettings() {
// Obtain URI of current list
// Call share settings as subactivity
Intent intent = new Intent(OpenIntents.SET_SHARE_SETTINGS_ACTION,
mListUri);
startActivityForResult(intent, SUBACTIVITY_LIST_SHARE_SETTINGS);
}
void setThemeSettings() {
showDialog(DIALOG_THEME);
}
@Override
public String onLoadTheme() {
return loadListTheme();
}
@Override
public void onSaveTheme(String theme) {
saveListTheme(theme);
}
@Override
public void onSetTheme(String theme) {
setListTheme(theme);
applyListTheme();
}
@Override
public void onSetThemeForAll(String theme) {
setThemeForAll(this, theme);
}
/**
* Set theme for all lists.
*
* @param context
* @param theme
*/
public static void setThemeForAll(Context context, String theme) {
ContentValues values = new ContentValues();
values.put(Lists.SKIN_BACKGROUND, theme);
context.getContentResolver().update(Lists.CONTENT_URI, values, null,
null);
}
/**
* Loads the theme settings for the currently selected theme.
* <p/>
* Up to version 1.2.1, only one of 3 hardcoded themes are available. These
* are stored in 'skin_background' as '1', '2', or '3'.
* <p/>
* Starting in 1.2.2, also themes of other packages are allowed.
*
* @return
*/
public String loadListTheme() {
/*
* long listId = getSelectedListId(); if (listId < 0) { // No valid list
* - probably view is not active // and no item is selected. return 1;
* // return default theme }
*/
// Return default theme if something unexpected happens:
if (mCursorShoppingLists == null) {
return "1";
}
if (mCursorShoppingLists.getPosition() < 0) {
return "1";
}
// mCursorListFilter has been set to correct position
// by calling getSelectedListId(),
// so we can read out further elements:
return mCursorShoppingLists
.getString(mStringListFilterSKINBACKGROUND);
}
public void saveListTheme(String theme) {
long listId = getSelectedListId();
if (listId < 0) {
// No valid list - probably view is not active
// and no item is selected.
return; // return default theme
}
ContentValues values = new ContentValues();
values.put(Lists.SKIN_BACKGROUND, theme);
getContentResolver().update(
Uri.withAppendedPath(Lists.CONTENT_URI,
mCursorShoppingLists.getString(0)), values, null, null
);
mCursorShoppingLists.requery();
}
/**
* Calls a dialog for setting the locations alert.
*/
void addLocationAlert() {
// Call dialog as activity
Intent intent = new Intent(OpenIntents.ADD_LOCATION_ALERT_ACTION,
mListUri);
// startSubActivity(intent, SUBACTIVITY_ADD_LOCATION_ALERT);
startActivity(intent);
}
@Override
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_NEW_LIST:
return new NewListDialog(this, new DialogActionListener() {
public void onAction(String name) {
createNewList(name);
}
});
case DIALOG_RENAME_LIST:
return new RenameListDialog(this, getCurrentListName(),
new DialogActionListener() {
public void onAction(String name) {
renameList(name);
}
}
);
case DIALOG_EDIT_ITEM:
return new EditItemDialog(this, mItemUri, mRelationUri,
mListItemUri);
case DIALOG_DELETE_ITEM:
return new AlertDialog.Builder(this)
.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(R.string.menu_delete_item)
.setMessage(R.string.delete_item_confirm)
.setPositiveButton(R.string.delete,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
deleteItem(mDeleteItemPosition);
}
}
)
.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
// Don't do anything
}
}
).create();
case DIALOG_THEME:
return new ThemeDialog(this, this);
case DIALOG_GET_FROM_MARKET:
return new DownloadOIAppDialog(this,
DownloadOIAppDialog.OI_BARCODESCANNER);
default:
break;
}
return super.onCreateDialog(id);
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
switch (id) {
case DIALOG_RENAME_LIST:
((RenameListDialog) dialog).setName(getCurrentListName());
break;
case DIALOG_EDIT_ITEM:
EditItemDialog d = (EditItemDialog) dialog;
d.setItemUri(mItemUri, mListItemUri);
d.setRelationUri(mRelationUri);
d.setFocusField(mEditItemFocusField);
String[] taglist = getTaglist();
d.setTagList(taglist);
d.setOnItemChangedListener(this);
break;
case DIALOG_THEME:
((ThemeDialog) dialog).prepareDialog();
break;
case DIALOG_GET_FROM_MARKET:
DownloadOIAppDialog.onPrepareDialog(this, dialog);
break;
default:
break;
}
}
// /////////////////////////////////////////////////////
//
// Helper functions
//
/**
* Returns the ID of the selected shopping list.
* <p/>
* As a side effect, the item URI is updated. Returns -1 if nothing is
* selected.
*
* @return ID of selected shopping list.
*/
long getSelectedListId() {
int pos = mShoppingListsView.getSelectedItemPosition();
if (pos < 0) {
// nothing selected - probably view is out of focus:
// Do nothing.
return -1;
}
// Obtain Id of currently selected shopping list:
mCursorShoppingLists.moveToPosition(pos);
long listId = mCursorShoppingLists.getLong(mStringListFilterID);
mListUri = Uri.withAppendedPath(ShoppingContract.Lists.CONTENT_URI,
Long.toString(listId));
getIntent().setData(mListUri);
return listId;
}
/**
* sets the selected list to a specific list Id
*/
void setSelectedListId(int id) {
// Is there a nicer way to accomplish the following?
// (we look through all elements to look for the
// one entry that has the same ID as returned by
// getDefaultList()).
//
// unfortunately, a SQL query won't work, as it would
// return 1 row, but I still would not know which
// row in the mCursorListFilter corresponds to that id.
//
// one could use: findViewById() but how will this
// translate to the position in the list?
mCursorShoppingLists.moveToPosition(-1);
while (mCursorShoppingLists.moveToNext()) {
int posId = mCursorShoppingLists.getInt(mStringListFilterID);
if (posId == id) {
int row = mCursorShoppingLists.getPosition();
// if we found the Id, then select this in
// the Spinner:
setSelectedListPos(row);
break;
}
}
}
private void setSelectedListPos(int pos) {
mShoppingListsView.setTag(pos);
mShoppingListsView.setSelection(pos);
long id = getSelectedListId();
// Set the theme based on the selected list:
setListTheme(loadListTheme());
if (id != mItemsView.getListId()) {
fillItems(false);
}
applyListTheme();
updateTitle();
if (mShoppingListsView instanceof ListView) {
((ListView) mShoppingListsView).setItemChecked(pos, true);
}
}
/**
*
*/
private void fillListFilter() {
// Get a cursor with all lists
mCursorShoppingLists = getContentResolver().query(Lists.CONTENT_URI,
mStringListFilter, null, null, mSortOrder);
startManagingCursor(mCursorShoppingLists);
if (mCursorShoppingLists == null) {
Log.e(TAG, "missing shopping provider");
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
R.layout.list_item_shopping_list,
new String[]{getString(R.string.no_shopping_provider)});
setSpinnerAndDrawerListAdapter(adapter);
return;
}
if (mCursorShoppingLists.getCount() < 1) {
// We have to create default shopping list:
long listId = ShoppingUtils.getList(this,
getText(R.string.my_shopping_list).toString());
// Check if insertion really worked. Otherwise
// we may end up in infinite recursion.
if (listId < 0) {
// for some reason insertion did not work.
return;
}
// The insertion should have worked, so let us call ourselves
// to try filling the list again:
fillListFilter();
return;
}
class mListContentObserver extends ContentObserver {
public mListContentObserver(Handler handler) {
super(handler);
if (debug) {
Log.i(TAG, "mListContentObserver: Constructor");
}
}
/*
* (non-Javadoc)
*
* @see android.database.ContentObserver#deliverSelfNotifications()
*/
@Override
public boolean deliverSelfNotifications() {
// TODO Auto-generated method stub
if (debug) {
Log.i(TAG, "mListContentObserver: deliverSelfNotifications");
}
return super.deliverSelfNotifications();
}
/*
* (non-Javadoc)
*
* @see android.database.ContentObserver#onChange(boolean)
*/
@Override
public void onChange(boolean arg0) {
// TODO Auto-generated method stub
if (debug) {
Log.i(TAG, "mListContentObserver: onChange");
}
mCursorShoppingLists.requery();
super.onChange(arg0);
}
}
mListContentObserver observer = new mListContentObserver(new Handler());
mCursorShoppingLists.registerContentObserver(observer);
// Register a ContentObserver, so that a new list can be
// automatically detected.
// mCursor
/*
* ArrayList<String> list = new ArrayList<String>(); // TODO Create
* summary of all lists // list.add(ALL); while
* (mCursorListFilter.next()) {
* list.add(mCursorListFilter.getString(mStringListFilterNAME)); }
* ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
* android.R.layout.simple_spinner_item, list);
* adapter.setDropDownViewResource(
* android.R.layout.simple_spinner_dropdown_item);
* mSpinnerListFilter.setAdapter(adapter);
*/
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
// Use a template that displays a text view
R.layout.list_item_shopping_list,
// Give the cursor to the list adapter
mCursorShoppingLists, new String[]{Lists.NAME},
new int[]{R.id.text1});
setSpinnerAndDrawerListAdapter(adapter);
}
private void onModeChanged() {
if (debug) {
Log.d(TAG, "onModeChanged()");
}
fillItems(false);
invalidateOptionsMenu();
updateTitle();
}
@Override
public void updateActionBar() {
invalidateOptionsMenu();
}
private String getCurrentListName() {
long listId = getSelectedListId();
// calling getSelectedListId also updates mCursorShoppingLists:
if (listId >= 0) {
return mCursorShoppingLists.getString(mStringListFilterNAME);
} else {
return "";
}
}
private void fillItems(boolean onlyIfPrefsChanged) {
if (debug) {
Log.d(TAG, "fillItems()");
}
long listId = getSelectedListId();
if (listId < 0) {
// No valid list - probably view is not active
// and no item is selected.
Log.d(TAG, "fillItems: listId not availalbe");
return;
}
// Insert any pending items received either through intents
// or in onActivityResult:
if (mItemsFromExtras.hasItems()) {
mItemsFromExtras.insertInto(this, mItemsView);
}
updateFilterWidgets();
if (onlyIfPrefsChanged
&& (lastAppliedPrefChange == PreferenceActivity.updateCount)) {
return;
}
if (debug) {
Log.d(TAG, "fillItems() for list " + listId);
}
lastAppliedPrefChange = PreferenceActivity.updateCount;
mItemsView.fillItems(this, listId);
// Also refresh AutoCompleteTextView:
fillAutoCompleteTextViewAdapter(mEditText);
}
/**
* Fill input field (AutoCompleteTextView) with suggestions: Unique item
* names from all lists are collected. The adapter is filled in the
* background.
*/
public void fillAutoCompleteTextViewAdapter(final AutoCompleteTextView textView) {
boolean limit_selections = PreferenceActivity.getCompleteFromCurrentListOnlyFromPrefs(this);
String listId = null;
if (limit_selections) {
listId = mListUri.getLastPathSegment();
}
// TODO: Optimize: This routine is called too often.
if (debug) {
Log.d(TAG, "fill AutoCompleteTextViewAdapter");
}
new AsyncTask<String, Integer, ArrayAdapter<String>>() {
private ArrayAdapter<String> adapter;
@Override
protected ArrayAdapter<String> doInBackground(String... params) {
return fillAutoCompleteAdapter(params[0]);
}
@Override
protected void onPostExecute(ArrayAdapter<String> adapter) {
if (textView != null) {
// use result from doInBackground()
textView.setAdapter(adapter);
}
}
private ArrayAdapter<String> fillAutoCompleteAdapter(String listId) {
// Create list of item names
Uri uri;
String retCol;
if (listId == null) {
uri = Items.CONTENT_URI;
retCol = Items.NAME;
} else {
uri = Uri.parse("content://org.openintents.shopping/containsfull/list").buildUpon().
appendPath(listId).build();
retCol = "items.name";
}
List<String> autocompleteItems = new LinkedList<>();
Cursor c = getContentResolver().query(uri, new String[]{retCol}, null, null, retCol + " asc");
if (c != null) {
String lastitem = "";
while (c.moveToNext()) {
String newitem = c.getString(0);
// Only add items if they have not been added previously
// (list is sorted)
if (!newitem.equals(lastitem)) {
autocompleteItems.add(newitem);
lastitem = newitem;
}
}
c.close();
}
return new ArrayAdapter<>(ShoppingActivity.this,
android.R.layout.simple_dropdown_item_1line,
autocompleteItems);
}
}.execute(listId);
}
/**
* Create list of tags.
* <p/>
* Tags for notes can be comma-separated. Here we create a list of the
* unique tags.
*
* @return
*/
String[] getTaglist() {
return getTaglist(null);
}
/**
* Create list of tags.
* <p/>
* Tags for notes can be comma-separated. Here we create a list of the
* unique tags.
*
* @param listId
* @return
*/
String[] getTaglist(String listId) {
Cursor c;
if (listId == null) {
c = getContentResolver().query(ShoppingContract.Items.CONTENT_URI,
new String[]{ShoppingContract.Items.TAGS}, null, null,
ShoppingContract.Items.DEFAULT_SORT_ORDER);
} else {
Uri uri = Uri.parse("content://org.openintents.shopping/listtags")
.buildUpon().appendPath(listId).build();
c = getContentResolver().query(uri,
new String[]{ShoppingContract.ContainsFull.ITEM_TAGS},
null, null, null);
}
// Create a set of all tags (every tag should only appear once).
HashSet<String> tagset = new HashSet<>();
c.moveToPosition(-1);
while (c.moveToNext()) {
String tags = c.getString(0);
if (tags != null) {
// Split several tags in a line, separated by comma
String[] smalltaglist = tags.split(",");
for (String tag : smalltaglist) {
if (!tag.equals("")) {
tagset.add(tag.trim());
}
}
}
}
c.close();
// Sort the list
// 1. Convert HashSet to String list.
ArrayList<String> list = new ArrayList<>();
list.addAll(tagset);
// 2. Sort the String list
Collections.sort(list);
// 3. Convert it to String array
return list.toArray(new String[list.size()]);
}
/**
* Tests whether the current list is shared via GTalk. (not local sharing!)
*
* @return true if SHARE_CONTACTS contains the '@' character.
*/
boolean isCurrentListShared() {
long listId = getSelectedListId();
if (listId < 0) {
// No valid list - probably view is not active
// and no item is selected.
return false;
}
// mCursorListFilter has been set to correct position
// by calling getSelectedListId(),
// so we can read out further elements:
// String shareName =
// mCursorListFilter.getString(mStringListFilterSHARENAME);
String recipients = mCursorShoppingLists
.getString(mStringListFilterSHARECONTACTS);
// If recipients contains the '@' symbol, it is shared.
return recipients.contains("@");
}
// Handle the process of automatically updating enabled sensors:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == MESSAGE_UPDATE_CURSORS) {
mCursorShoppingLists.requery();
if (mUpdating) {
sendMessageDelayed(obtainMessage(MESSAGE_UPDATE_CURSORS),
mUpdateInterval);
}
}
}
};
/**
* This method is called when the sending activity has finished, with the
* result it supplied.
*
* @param requestCode The original request code as given to startActivity().
* @param resultCode From sending activity as per setResult().
* @param data From sending activity as per setResult().
* @see android.app.Activity#onActivityResult(int, int, android.content.Intent)
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (debug) {
Log.i(TAG, "ShoppingView: onActivityResult. ");
}
if (requestCode == SUBACTIVITY_LIST_SHARE_SETTINGS) {
if (debug) {
Log.i(TAG, "SUBACTIVITY_LIST_SHARE_SETTINGS");
}
if (resultCode == RESULT_OK) {
// Broadcast the intent
Uri uri = data.getData();
if (!mListUri.equals(uri)) {
Log.e(TAG, "Unexpected uri returned: Should be " + mListUri
+ " but was " + uri);
return;
}
// TODO ???
Bundle extras = data.getExtras();
String sharename = extras
.getString(ShoppingContract.Lists.SHARE_NAME);
String contacts = extras
.getString(ShoppingContract.Lists.SHARE_CONTACTS);
if (debug) {
Log.i(TAG, "Received bundle: sharename: " + sharename
+ ", contacts: " + contacts);
}
}
} else if (REQUEST_CODE_CATEGORY_ALTERNATIVE == requestCode) {
if (debug) {
Log.d(TAG, "result received");
}
if (RESULT_OK == resultCode) {
if (debug) {
Log.d(TAG, "result OK");
}
if (data.getExtras() != null) {
if (debug) {
Log.d(TAG, "extras received");
}
mItemsFromExtras.getShoppingExtras(data);
}
}
} else if (REQUEST_PICK_LIST == requestCode) {
if (debug) {
Log.d(TAG, "result received");
}
if (RESULT_OK == resultCode) {
int position = mMoveItemPosition;
if (mMoveItemPosition >= 0) {
moveItem(position, Integer.parseInt(data.getData()
.getLastPathSegment()));
}
}
mMoveItemPosition = -1;
}
}
public void changeList(int value) {
int pos = mShoppingListsView.getSelectedItemPosition();
int newPos;
if (pos < 0) {
// nothing selected - probably view is out of focus:
// Do nothing.
newPos = -1;
} else if (pos == 0) {
newPos = mShoppingListsView.getCount() - 1;
} else if (pos == mShoppingListsView.getCount()) {
newPos = 0;
} else {
newPos = pos + value;
}
setSelectedListPos(newPos);
}
private class DrawerListAdapter implements WrapperListAdapter, OnItemClickListener {
private ListAdapter mAdapter;
private int mNumAboveList = 3;
private int mNumBelowList = 1;
private int mViewTypeNum;
private LayoutInflater mInflater;
public DrawerListAdapter(Context context, ListAdapter adapter) {
mAdapter = adapter;
mViewTypeNum = mAdapter.getViewTypeCount();
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public boolean areAllItemsEnabled() {
return false;
}
@Override
public boolean isEnabled(int position) {
int list_pos, post_pos;
int list_count = mAdapter.getCount();
if (position < mNumAboveList) {
return (position != 2); // not the "Lists" header
} else if ((list_pos = position - mNumAboveList) < list_count) {
// actual list entries can be selected
} else {
post_pos = list_pos - list_count;
// New List button can be selected
}
return true;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return mAdapter.getCount() + mNumAboveList + mNumBelowList;
}
@Override
public Object getItem(int position) {
int list_pos, post_pos;
int list_count = mAdapter.getCount();
if (position < mNumAboveList) {
} else if ((list_pos = position - mNumAboveList) < list_count) {
return mAdapter.getItem(list_pos);
} else {
post_pos = list_pos - list_count;
}
return null;
}
@Override
public long getItemId(int position) {
int list_pos, post_pos;
int list_count = mAdapter.getCount();
if (position < mNumAboveList) {
} else if ((list_pos = position - mNumAboveList) < list_count) {
return mAdapter.getItemId(list_pos);
} else {
post_pos = list_pos - list_count;
}
return -1;
}
@Override
public int getItemViewType(int position) {
int list_pos, post_pos;
int list_count = mAdapter.getCount();
if (position < mNumAboveList) {
} else if ((list_pos = position - mNumAboveList) < list_count) {
return mAdapter.getItemViewType(list_pos);
}
return IGNORE_ITEM_VIEW_TYPE;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
int list_pos;
int list_count = mAdapter.getCount();
View v = null;
View v2;
if (position < mNumAboveList) {
switch (position) {
case 1:
v = mInflater.inflate(R.layout.drawer_item_radio, parent, false);
v2 = v.findViewById(R.id.text1);
((TextView) v2).setText(R.string.menu_pick_items);
v2 = v.findViewById(R.id.mode_radio_button);
v2.setSelected(mItemsView.mMode == MODE_ADD_ITEMS);
break;
case 0:
v = mInflater.inflate(R.layout.drawer_item_radio, parent, false);
v2 = v.findViewById(R.id.text1);
((TextView) v2).setText(R.string.menu_start_shopping);
v2 = v.findViewById(R.id.mode_radio_button);
v2.setSelected(mItemsView.mMode == MODE_IN_SHOP);
break;
case 2:
v = mInflater.inflate(R.layout.drawer_item_header, parent, false);
((TextView) v).setText(R.string.list); // fix me
break;
default:
break;
}
} else if ((list_pos = position - mNumAboveList) < list_count) {
int curListPos = mShoppingListsView.getSelectedItemPosition();
if (list_pos == curListPos) {
mDrawerListsView.setItemChecked(position, true);
}
v = mAdapter.getView(list_pos, convertView, parent);
} else {
v = mInflater.inflate(R.layout.drawer_item_radio, parent, false);
v2 = v.findViewById(R.id.text1);
((TextView) v2).setText(R.string.new_list);
v2 = v.findViewById(R.id.mode_radio_button);
((ImageView) v2).setImageResource(R.drawable.ic_menu_add_list);
}
return v;
}
@Override
public int getViewTypeCount() {
return mViewTypeNum;
}
@Override
public boolean hasStableIds() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
mAdapter.registerDataSetObserver(observer);
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
mAdapter.unregisterDataSetObserver(observer);
}
@Override
public ListAdapter getWrappedAdapter() {
return mAdapter;
}
public void onItemClick(AdapterView parent, View v,
int position, long id) {
if (debug) {
Log.d(TAG, "DrawerListAdapter: onItemClick");
}
int list_pos;
int list_count = mAdapter.getCount();
if (position < mNumAboveList) {
// Pick Items or Shopping selected
closeDrawer();
mItemsView.mMode = (position == 1) ? MODE_ADD_ITEMS : MODE_IN_SHOP;
mDrawerListsView.setItemChecked(position, true);
mDrawerListsView.setItemChecked(1 - position, false);
onModeChanged();
// need to toggle the radio buttons too
} else if ((list_pos = position - mNumAboveList) < list_count) {
// Update list cursor:
mShoppingListsView.setSelection(list_pos);
getSelectedListId();
// Set the theme based on the selected list:
setListTheme(loadListTheme());
// If it's the same list we had before, requery only
// if a preference has changed since then.
fillItems(id == mItemsView.getListId());
// Apply the theme after the list has been filled:
applyListTheme();
updateTitle();
mDrawerListsView.setItemChecked(position, true);
closeDrawer();
} else {
closeDrawer();
showDialog(DIALOG_NEW_LIST);
}
}
}
private void closeDrawer() {
if (mDrawerLayout != null) {
mDrawerLayout.closeDrawer(mDrawerListsView);
}
}
/**
* With the requirement of OS3, making an intermediary decision depending
* upon the widget
*
* @param adapter
*/
private void setSpinnerAndDrawerListAdapter(ListAdapter adapter) {
DrawerListAdapter adapterWrapper = (new DrawerListAdapter(this, adapter));
mDrawerListsView.setAdapter(adapterWrapper);
mDrawerListsView.setOnItemClickListener(adapterWrapper);
mShoppingListsView.setAdapter(adapter);
}
@Override
public void onItemChanged() {
mItemsView.mCursorItems.requery();
fillAutoCompleteTextViewAdapter(mEditText);
}
private class ListSortActionProvider extends ActionProvider implements OnMenuItemClickListener {
private Context mContext;
private String[] mSortLabels;
private String[] mSortVals;
private Integer[] mSortValInts;
public ListSortActionProvider(Context context) {
super(context);
mContext = context;
mSortLabels = mContext.getResources().getStringArray(R.array.preference_sortorder_entries);
mSortVals = mContext.getResources().getStringArray(R.array.preference_sortorder_entryvalues);
mSortValInts = new Integer[mSortVals.length];
for (int i = 0; i < mSortVals.length; i++)
mSortValInts[i] = Integer.parseInt(mSortVals[i]);
}
@Override
public View onCreateActionView() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean hasSubMenu() {
return true;
}
public void onPrepareSubMenu(SubMenu subMenu) {
int i;
saveActiveList(false);
subMenu.clear();
int curSort = PreferenceActivity.getSortOrderIndexFromPrefs(mContext, MODE_IN_SHOP);
for (i = 0; i < mSortLabels.length; i++) {
subMenu.add(1, i, i, mSortLabels[i]).setOnMenuItemClickListener(this)
.setChecked(mSortValInts[i] == curSort);
}
subMenu.setGroupCheckable(1, true, true);
}
public boolean overridesItemVisibility() {
return true;
}
public boolean isVisible() {
boolean vis = true;
if (mItemsView.mMode != MODE_IN_SHOP) {
vis = false;
} else if (!PreferenceActivity.getUsingPerListSortFromPrefs(mContext)) {
vis = false;
}
if (mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mDrawerListsView)) {
vis = false;
}
return vis;
}
public boolean onMenuItemClick(MenuItem item) {
int sortOrderNum = Integer.parseInt(mSortVals[item.getItemId()]);
// String sortOrder = Contains.SORT_ORDERS[sortOrderNum];
ContentValues values = new ContentValues();
values.put(Lists.ITEMS_SORT, sortOrderNum);
getContentResolver().update(mListUri, values, null, null);
fillItems(false);
return true;
}
}
@Override
public void onUndoAvailable(SnackbarUndoOperation undoOp) {
Snackbar snackbar = Snackbar.make(mItemsView, undoOp.getDescription(this),
Snackbar.LENGTH_LONG);
snackbar.setAction(R.string.undo, undoOp);
snackbar.show();
}
}