/*
* Copyright (C) 2009 Android Shuffle Open Source Project
*
* 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.dodgybits.shuffle.android.list.activity.expandable;
import org.dodgybits.android.shuffle.R;
import org.dodgybits.shuffle.android.core.activity.flurry.FlurryEnabledExpandableListActivity;
import org.dodgybits.shuffle.android.core.model.Entity;
import org.dodgybits.shuffle.android.core.model.Project;
import org.dodgybits.shuffle.android.core.model.persistence.EntityCache;
import org.dodgybits.shuffle.android.core.model.persistence.EntityPersister;
import org.dodgybits.shuffle.android.core.model.persistence.TaskPersister;
import org.dodgybits.shuffle.android.core.view.AlertUtils;
import org.dodgybits.shuffle.android.core.view.MenuUtils;
import org.dodgybits.shuffle.android.list.config.ExpandableListConfig;
import org.dodgybits.shuffle.android.list.view.SwipeListItemListener;
import org.dodgybits.shuffle.android.list.view.SwipeListItemWrapper;
import org.dodgybits.shuffle.android.preference.model.Preferences;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnClickListener;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.ExpandableListAdapter;
import android.widget.ExpandableListView;
import android.widget.SimpleCursorTreeAdapter;
import android.widget.Toast;
import com.google.inject.Inject;
public abstract class AbstractExpandableActivity<G extends Entity> extends FlurryEnabledExpandableListActivity
implements SwipeListItemListener {
private static final String cTag = "AbstractExpandableActivity";
protected ExpandableListAdapter mAdapter;
@Inject protected EntityCache<org.dodgybits.shuffle.android.core.model.Context> mContextCache;
@Inject protected EntityCache<Project> mProjectCache;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(getListConfig().getContentViewResId());
setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
// Inform the list we provide context menus for items
getExpandableListView().setOnCreateContextMenuListener(this);
Cursor groupCursor = createGroupQuery();
// Set up our adapter
mAdapter = createExpandableListAdapter(groupCursor);
setListAdapter(mAdapter);
// register self as swipe listener
SwipeListItemWrapper wrapper = (SwipeListItemWrapper) findViewById(R.id.swipe_wrapper);
wrapper.setSwipeListItemListener(this);
}
@Override
protected void onResume() {
super.onResume();
refreshChildCount();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_N:
// go to previous view
int prevView = getListConfig().getCurrentViewMenuId() - 1;
if (prevView < MenuUtils.INBOX_ID) {
prevView = MenuUtils.CONTEXT_ID;
}
MenuUtils.checkCommonItemsSelected(prevView, this, getListConfig().getCurrentViewMenuId());
return true;
case KeyEvent.KEYCODE_M:
// go to previous view
int nextView = getListConfig().getCurrentViewMenuId() + 1;
if (nextView > MenuUtils.CONTEXT_ID) {
nextView = MenuUtils.INBOX_ID;
}
MenuUtils.checkCommonItemsSelected(nextView, this, getListConfig().getCurrentViewMenuId());
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuItem item = menu.findItem(MenuUtils.SYNC_ID);
if (item != null) {
item.setVisible(Preferences.validateTracksSettings(this));
}
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuUtils.addExpandableInsertMenuItems(menu, getListConfig().getGroupName(this),
getListConfig().getChildName(this), this);
MenuUtils.addViewMenuItems(menu, getListConfig().getCurrentViewMenuId());
MenuUtils.addPrefsHelpMenuItems(this, menu);
MenuUtils.addSearchMenuItem(this, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final EntityPersister<G> groupPersister = getListConfig().getGroupPersister();
final TaskPersister childPersister = getListConfig().getChildPersister();
switch (item.getItemId()) {
case MenuUtils.INSERT_CHILD_ID:
long packedPosition = getSelectedPosition();
int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition);
if (groupPosition > -1)
{
Cursor cursor = (Cursor) getExpandableListAdapter().getGroup(groupPosition);
G group = groupPersister.read(cursor);
insertItem(childPersister.getContentUri(), group);
}
else
{
insertItem(childPersister.getContentUri());
}
return true;
case MenuUtils.INSERT_GROUP_ID:
insertItem(groupPersister.getContentUri());
return true;
}
if (MenuUtils.checkCommonItemsSelected(item, this, getListConfig().getCurrentViewMenuId())) return true;
return super.onOptionsItemSelected(item);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
ExpandableListView.ExpandableListContextMenuInfo info;
try {
info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo;
} catch (ClassCastException e) {
Log.e(cTag, "bad menuInfo", e);
return;
}
long packedPosition = info.packedPosition;
int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition);
int childPosition = ExpandableListView.getPackedPositionChild(packedPosition);
boolean isChild = isChild(packedPosition);
Cursor cursor;
if (isChild) {
cursor = (Cursor)(getExpandableListAdapter().getChild(groupPosition, childPosition));
} else {
cursor = (Cursor)(getExpandableListAdapter().getGroup(groupPosition));
}
if (cursor == null) {
// For some reason the requested item isn't available, do nothing
return;
}
// Setup the menu header
menu.setHeaderTitle(cursor.getString(1));
if (isChild)
{
long childId = getExpandableListAdapter().getChildId(groupPosition, childPosition);
Uri selectedUri = ContentUris.withAppendedId(getListConfig().getChildPersister().getContentUri(), childId);
MenuUtils.addSelectedAlternativeMenuItems(menu, selectedUri, false);
MenuUtils.addCompleteMenuItem(menu);
}
else
{
long groupId = getExpandableListAdapter().getGroupId(groupPosition);
Uri selectedUri = ContentUris.withAppendedId(getListConfig().getGroupPersister().getContentUri(), groupId);
MenuUtils.addSelectedAlternativeMenuItems(menu, selectedUri, false);
MenuUtils.addInsertMenuItems(menu, getListConfig().getChildName(this), true, this);
}
// ... and ends with the delete command.
MenuUtils.addDeleteMenuItem(menu);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
ExpandableListView.ExpandableListContextMenuInfo info;
try {
info = (ExpandableListView.ExpandableListContextMenuInfo) item.getMenuInfo();
} catch (ClassCastException e) {
Log.e(cTag, "bad menuInfo", e);
return false;
}
switch (item.getItemId()) {
case MenuUtils.COMPLETE_ID:
toggleComplete(info.packedPosition, info.id);
return true;
case MenuUtils.DELETE_ID:
// Delete the item that the context menu is for
deleteItem(info.packedPosition);
return true;
case MenuUtils.INSERT_ID:
int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition);
final Uri childContentUri = getListConfig().getChildPersister().getContentUri();
if (groupPosition > -1)
{
Cursor cursor = (Cursor) getExpandableListAdapter().getGroup(groupPosition);
G group = getListConfig().getGroupPersister().read(cursor);
insertItem(childContentUri, group);
}
else
{
insertItem(childContentUri);
}
return true;
}
return false;
}
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
Uri url = ContentUris.withAppendedId(getListConfig().getChildPersister().getContentUri(), id);
// Launch activity to view/edit the currently selected item
startActivity(getClickIntent(url));
return true;
}
public class MyExpandableListAdapter extends SimpleCursorTreeAdapter {
public MyExpandableListAdapter(Context context, Cursor cursor, int groupLayout,
int childLayout, String[] groupFrom, int[] groupTo, String[] childrenFrom,
int[] childrenTo) {
super(context, cursor, groupLayout, groupFrom, groupTo, childLayout, childrenFrom,
childrenTo);
}
@Override
protected Cursor getChildrenCursor(Cursor groupCursor) {
long groupId = groupCursor.getLong(getGroupIdColumnIndex());
Log.d(cTag, "getChildrenCursor for groupId " + groupId);
return createChildQuery(groupId);
}
}
public void onListItemSwiped(int position) {
long packedPosition = getExpandableListView().getExpandableListPosition(position);
if (isChild(packedPosition)) {
int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition);
int childPosition = ExpandableListView.getPackedPositionChild(packedPosition);
long id = getExpandableListAdapter().getChildId(groupPosition, childPosition);
toggleComplete(packedPosition, id);
}
}
protected final void toggleComplete(long packedPosition, long id) {
int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition);
int childPosition = ExpandableListView.getPackedPositionChild(packedPosition);
Cursor c = (Cursor) getExpandableListAdapter().getChild(groupPosition, childPosition);
getListConfig().getChildPersister().toggleTaskComplete(c);
}
protected Boolean isChildSelected() {
long packed = this.getSelectedPosition();
return isChild(packed);
}
protected Boolean isChild(long packedPosition) {
int type = ExpandableListView.getPackedPositionType(packedPosition);
Boolean isChild = null;
switch (type) {
case ExpandableListView.PACKED_POSITION_TYPE_CHILD:
isChild = Boolean.TRUE;
break;
case ExpandableListView.PACKED_POSITION_TYPE_GROUP:
isChild = Boolean.FALSE;
}
return isChild;
}
protected Uri getSelectedContentUri() {
Uri selectedUri = null;
Boolean childSelected = isChildSelected();
if (childSelected != null) {
selectedUri = childSelected ? getListConfig().getChildPersister().getContentUri() : getListConfig().getGroupPersister().getContentUri();
}
return selectedUri;
}
/**
* Return the intent generated when a list item is clicked.
*
* @param url type of data selected
*/
protected Intent getClickIntent(Uri uri) {
return new Intent(Intent.ACTION_EDIT, uri);
}
/**
* Permanently delete the selected item.
*/
protected final void deleteItem() {
deleteItem(getSelectedPosition());
}
protected final void deleteItem(final long packedPosition) {
final int type = ExpandableListView.getPackedPositionType(packedPosition);
final int childPosition = ExpandableListView.getPackedPositionChild(packedPosition);
final int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition);
final EntityPersister<G> groupPersister = getListConfig().getGroupPersister();
final TaskPersister childPersister = getListConfig().getChildPersister();
switch (type) {
case ExpandableListView.PACKED_POSITION_TYPE_CHILD:
Log.d(cTag, "Deleting child at position " + groupPosition + "," + childPosition);
final long childId = getExpandableListAdapter().getChildId(groupPosition, childPosition);
Log.i(cTag, "Deleting child id " + childId);
Uri childUri = ContentUris.withAppendedId(childPersister.getContentUri(), childId);
getContentResolver().delete(childUri, null, null);
showCancelToast(false);
refreshChildCount();
getExpandableListView().invalidate();
break;
case ExpandableListView.PACKED_POSITION_TYPE_GROUP:
Log.d(cTag, "Deleting parent at position " + groupPosition);
// first check if there's any children...
int childCount = getExpandableListAdapter().getChildrenCount(groupPosition);
if (childCount > 0) {
OnClickListener buttonListener = new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON1) {
final long groupId = getExpandableListAdapter().getGroupId(groupPosition);
Log.i(cTag, "Deleting group id " + groupId);
Uri uri = ContentUris.withAppendedId(groupPersister.getContentUri(), groupId);
getContentResolver().delete(uri, null, null);
Log.i(cTag, "Deleting all child for group id " + groupId);
getContentResolver().delete(childPersister.getContentUri(),
getListConfig().getGroupIdColumnName() + " = ?",
new String[] {String.valueOf(groupId)});
showCancelToast(true);
} else {
Log.d(cTag, "Hit Cancel button. Do nothing.");
}
}
};
AlertUtils.showDeleteGroupWarning(this, getListConfig().getGroupName(this),
getListConfig().getChildName(this), childCount, buttonListener);
} else {
Log.i(cTag, "Deleting childless group at position " + groupPosition);
final long groupId = getExpandableListAdapter().getGroupId(groupPosition);
Log.i(cTag, "Deleting group id " + groupId);
Uri groupUri = ContentUris.withAppendedId(groupPersister.getContentUri(), groupId);
getContentResolver().delete(groupUri, null, null);
showCancelToast(true);
}
break;
}
}
private final void showCancelToast(boolean isGroup) {
String name = isGroup ? getListConfig().getGroupName(this)
: getListConfig().getChildName(this);
String text = getResources().getString(
R.string.itemDeletedToast, name );
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
private final void insertItem(Uri uri, G group) {
Intent intent = new Intent(Intent.ACTION_INSERT, uri);
Bundle extras = intent.getExtras();
if (extras == null) extras = new Bundle();
updateInsertExtras(extras, group);
intent.putExtras(extras);
startActivity(intent);
}
private final void insertItem(Uri uri) {
// Launch activity to insert a new item
Intent intent = new Intent(Intent.ACTION_INSERT, uri);
startActivity(intent);
}
abstract protected void updateInsertExtras(Bundle extras, G group);
abstract void refreshChildCount();
abstract ExpandableListAdapter createExpandableListAdapter(Cursor cursor);
/**
* @return a cursor selecting the child items to display for a selected top level group item.
*/
abstract Cursor createChildQuery(long groupId);
/**
* @return a cursor selecting the top levels items to display in the list.
*/
abstract Cursor createGroupQuery();
/**
* @return index of group id column in group cursor
*/
abstract int getGroupIdColumnIndex();
/**
* @return index of child id column in group cursor
*/
abstract int getChildIdColumnIndex();
// custom helper methods
abstract protected ExpandableListConfig<G> getListConfig();
}