diff --git a/api/src/com/todoroo/astrid/data/TagData.java b/api/src/com/todoroo/astrid/data/TagData.java index a32017f0e..bdd8bab79 100644 --- a/api/src/com/todoroo/astrid/data/TagData.java +++ b/api/src/com/todoroo/astrid/data/TagData.java @@ -128,6 +128,9 @@ public final class TagData extends RemoteModel { /** whether tag is emergent */ public static final int FLAG_EMERGENT = 1 << 2; + /** whether tag represents a featured list */ + public static final int FLAG_FEATURED = 1 << 3; + // --- defaults /** Default values container */ diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index 449664d82..1d1769d3d 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -341,7 +341,16 @@ - + + + + + + + diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/TagViewFragment.java b/astrid/plugin-src/com/todoroo/astrid/actfm/TagViewFragment.java index bd00b20b5..f8d4d6376 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/TagViewFragment.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/TagViewFragment.java @@ -103,8 +103,6 @@ public class TagViewFragment extends TaskListFragment { private boolean justDeleted = false; - //private ImageAdapter galleryAdapter; - // --- UI initialization @Override @@ -149,7 +147,7 @@ public class TagViewFragment extends TaskListFragment { */ @Override protected View getListBody(ViewGroup root) { - ViewGroup parent = (ViewGroup) getActivity().getLayoutInflater().inflate(R.layout.task_list_body_tag, root, false); + ViewGroup parent = (ViewGroup) getActivity().getLayoutInflater().inflate(getTaskListBodyLayout(), root, false); taskListView = super.getListBody(parent); parent.addView(taskListView); @@ -157,6 +155,10 @@ public class TagViewFragment extends TaskListFragment { return parent; } + protected int getTaskListBodyLayout() { + return R.layout.task_list_body_tag; + } + private void showListSettingsPopover() { if (!AstridPreferences.canShowPopover()) return; @@ -302,7 +304,7 @@ public class TagViewFragment extends TaskListFragment { Preferences.setLong(LAST_FETCH_KEY + tagData.getId(), DateUtilities.now()); } - private void setUpMembersGallery() { + protected void setUpMembersGallery() { LinearLayout membersView = (LinearLayout)getView().findViewById(R.id.shared_with); membersView.setOnClickListener(settingsListener); try { @@ -537,7 +539,7 @@ public class TagViewFragment extends TaskListFragment { @Override protected boolean hasDraggableOption() { - return true; + return tagData != null && !tagData.getFlag(TagData.FLAGS, TagData.FLAG_FEATURED); } @Override diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmDataService.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmDataService.java index c70a562b3..c57b88aea 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmDataService.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmDataService.java @@ -19,6 +19,7 @@ import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.Functions; import com.todoroo.andlib.sql.Join; import com.todoroo.andlib.sql.Query; import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; @@ -204,6 +205,27 @@ public final class ActFmDataService { } } + @SuppressWarnings("nls") + public void saveFeaturedList(JSONObject featObject) throws JSONException { + TodorooCursor cursor = tagDataService.query(Query.select(TagData.PROPERTIES).where( + Criterion.and(Functions.bitwiseAnd(TagData.FLAGS, TagData.FLAG_FEATURED).gt(0), TagData.REMOTE_ID.eq(featObject.get("id"))))); + try { + cursor.moveToNext(); + TagData tagData = new TagData(); + if (!cursor.isAfterLast()) { + tagData.readFromCursor(cursor); + if(!tagData.getValue(TagData.NAME).equals(featObject.getString("name"))) + TagService.getInstance().rename(tagData.getValue(TagData.NAME), featObject.getString("name")); + cursor.moveToNext(); + } + ActFmSyncService.JsonHelper.featuredListFromJson(featObject, tagData); + tagDataService.save(tagData); + + } finally { + cursor.close(); + } + } + /** * Save / Merge JSON user * @param userObject diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java index 18a12c9fc..ea1a7b186 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java @@ -42,6 +42,7 @@ import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.Functions; import com.todoroo.andlib.sql.Join; import com.todoroo.andlib.sql.Order; import com.todoroo.andlib.sql.Query; @@ -836,7 +837,26 @@ public final class ActFmSyncService { if(serverTime == 0) { Long[] remoteIdArray = remoteIds.toArray(new Long[remoteIds.size()]); - tagDataService.deleteWhere(Criterion.not(TagData.REMOTE_ID.in(remoteIdArray))); + tagDataService.deleteWhere(Criterion.and( + Criterion.not(Functions.bitwiseAnd(TagData.FLAGS, TagData.FLAG_FEATURED).gt(0)), + Criterion.not(TagData.REMOTE_ID.in(remoteIdArray)))); + } + + return result.optInt("time", 0); + } + + public int fetchFeaturedLists(int serverTime) throws JSONException, IOException { + if (!checkForToken()) + return 0; + JSONObject result = actFmInvoker.invoke("featured_lists", + "token", token, "modified_after", serverTime); + JSONArray featuredLists = result.getJSONArray("list"); + if (featuredLists.length() > 0) + Preferences.setBoolean(R.string.p_show_featured_lists, true); + + for (int i = 0; i < featuredLists.length(); i++) { + JSONObject featObject = featuredLists.getJSONObject(i); + actFmDataService.saveFeaturedList(featObject); } return result.optInt("time", 0); @@ -1196,6 +1216,7 @@ public final class ActFmSyncService { } catch (SQLiteConstraintException e) { taskDao.handleSQLiteConstraintException(remote); } + ids.add(remote.getId()); metadataService.synchronizeMetadata(remote.getId(), metadata, MetadataCriteria.withKey(TagService.KEY)); synchronizeAttachments(item, remote); @@ -1428,10 +1449,23 @@ public final class ActFmSyncService { * @throws JSONException */ public static void tagFromJson(JSONObject json, TagData model) throws JSONException { + parseTagDataFromJson(json, model, false); + } + + public static void featuredListFromJson(JSONObject json, TagData model) throws JSONException { + parseTagDataFromJson(json, model, true); + } + + private static void parseTagDataFromJson(JSONObject json, TagData model, boolean featuredList) throws JSONException { model.clearValue(TagData.REMOTE_ID); model.setValue(TagData.REMOTE_ID, json.getLong("id")); model.setValue(TagData.NAME, json.getString("name")); - readUser(json.getJSONObject("user"), model, TagData.USER_ID, TagData.USER); + + if (!featuredList) + readUser(json.getJSONObject("user"), model, TagData.USER_ID, TagData.USER); + + if (featuredList) + model.setFlag(TagData.FLAGS, TagData.FLAG_FEATURED, true); if(json.has("picture")) model.setValue(TagData.PICTURE, json.optString("picture", "")); diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java index 80a432253..12b0f5da1 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java @@ -21,6 +21,7 @@ import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.Functions; import com.todoroo.andlib.sql.Join; import com.todoroo.andlib.sql.Query; import com.todoroo.andlib.utility.Preferences; @@ -134,6 +135,8 @@ public class ActFmSyncV2Provider extends SyncV2Provider { private static final String LAST_TAG_FETCH_TIME = "actfm_lastTag"; //$NON-NLS-1$ + private static final String LAST_FEATURED_TAG_FETCH_TIME = "actfm_last_featuredTag"; //$NON-NLS-1$ + private static final String LAST_USERS_FETCH_TIME = "actfm_lastUsers"; //$NON-NLS-1$ // --- synchronize active tasks @@ -145,9 +148,9 @@ public class ActFmSyncV2Provider extends SyncV2Provider { new Thread(new Runnable() { public void run() { callback.started(); - callback.incrementMax(140); + callback.incrementMax(160); - final AtomicInteger finisher = new AtomicInteger(4); + final AtomicInteger finisher = new AtomicInteger(5); actFmPreferenceService.recordSyncStart(); updateUserStatus(); @@ -158,6 +161,8 @@ public class ActFmSyncV2Provider extends SyncV2Provider { startUpdatesFetcher(manual, callback, finisher); + startFeaturedListFetcher(callback, finisher); + actFmSyncService.waitUntilEmpty(); startTaskFetcher(manual, callback, finisher); @@ -254,7 +259,30 @@ public class ActFmSyncV2Provider extends SyncV2Provider { } } }); + } + /** fetch changes to tags */ + private void startFeaturedListFetcher(final SyncResultCallback callback, + final AtomicInteger finisher) { + new Thread(new Runnable() { + @Override + public void run() { + int time = Preferences.getInt(LAST_FEATURED_TAG_FETCH_TIME, 0); + try { + time = actFmSyncService.fetchFeaturedLists(time); + Preferences.setInt(LAST_FEATURED_TAG_FETCH_TIME, time); + } catch (JSONException e) { + handler.handleException("actfm-sync", e, e.toString()); //$NON-NLS-1$ + } catch (IOException e) { + handler.handleException("actfm-sync", e, e.toString()); //$NON-NLS-1$ + } finally { + callback.incrementProgress(20); + if(finisher.decrementAndGet() == 0) { + finishSync(callback); + } + } + } + }).start(); } /** @return runnable to fetch changes to tags */ @@ -341,10 +369,12 @@ public class ActFmSyncV2Provider extends SyncV2Provider { private void pushQueuedTags(final SyncResultCallback callback, final AtomicInteger finisher, int lastTagSyncTime) { TodorooCursor tagDataCursor = tagDataService.query(Query.select(TagData.PROPERTIES) - .where(Criterion.or( + .where(Criterion.and( + Functions.bitwiseAnd(TagData.FLAGS, TagData.FLAG_FEATURED).eq(0), + Criterion.or( TagData.REMOTE_ID.eq(0), Criterion.and(TagData.REMOTE_ID.gt(0), - TagData.MODIFICATION_DATE.gt(lastTagSyncTime))))); + TagData.MODIFICATION_DATE.gt(lastTagSyncTime)))))); try { pushQueued(callback, finisher, tagDataCursor, true, tagPusher); } finally { diff --git a/astrid/plugin-src/com/todoroo/astrid/people/PeopleFilterMode.java b/astrid/plugin-src/com/todoroo/astrid/people/PeopleFilterMode.java new file mode 100644 index 000000000..8828eacfa --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/people/PeopleFilterMode.java @@ -0,0 +1,59 @@ +package com.todoroo.astrid.people; + +import android.content.Context; + +import com.timsu.astrid.R; +import com.todoroo.astrid.activity.FilterListFragment; +import com.todoroo.astrid.activity.FilterModeSpec; +import com.todoroo.astrid.activity.TaskListFragment; +import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.api.FilterListItem; +import com.todoroo.astrid.api.FilterWithUpdate; +import com.todoroo.astrid.helper.AsyncImageView; +import com.todoroo.astrid.ui.MainMenuPopover; + +public class PeopleFilterMode implements FilterModeSpec { + + private AsyncImageView imageView; + + @Override + public Filter getDefaultFilter(Context context) { + Filter defaultFilter = PeopleFilterExposer.mySharedTasks(context); + return defaultFilter; + } + + @Override + public Class getFilterListClass() { + return PeopleListFragment.class; + } + + @Override + public void onFilterItemClickedCallback(FilterListItem item) { + if (imageView == null) + return; + if (item instanceof FilterWithUpdate) + imageView.setUrl(((FilterWithUpdate) item).imageUrl); + else + imageView.setUrl(null); + } + + public void setImageView(AsyncImageView imageView) { + this.imageView = imageView; + } + + @Override + public int[] getForbiddenMenuItems() { + return FORBIDDEN_MENU_ITEMS; + } + + @Override + public int getMainMenuIconAttr() { + return R.attr.asPeopleMenu; + } + + private static final int[] FORBIDDEN_MENU_ITEMS = { + TaskListFragment.MENU_NEW_FILTER_ID, + TaskListFragment.MENU_ADDONS_ID, + MainMenuPopover.MAIN_MENU_ITEM_FRIENDS + }; +} diff --git a/astrid/plugin-src/com/todoroo/astrid/people/PeopleListFragment.java b/astrid/plugin-src/com/todoroo/astrid/people/PeopleListFragment.java index a73069b4c..25ad25207 100644 --- a/astrid/plugin-src/com/todoroo/astrid/people/PeopleListFragment.java +++ b/astrid/plugin-src/com/todoroo/astrid/people/PeopleListFragment.java @@ -22,8 +22,8 @@ public class PeopleListFragment extends FilterListFragment { @Override protected int getLayout(Activity activity) { if (AstridPreferences.useTabletLayout(activity)) - return R.layout.people_list_fragment_3pane; + return R.layout.filter_list_fragment_alternative_3pane; else - return R.layout.people_list_fragment; + return R.layout.filter_list_fragment_alternative; } } diff --git a/astrid/plugin-src/com/todoroo/astrid/people/PeopleViewActivity.java b/astrid/plugin-src/com/todoroo/astrid/people/PeopleViewActivity.java deleted file mode 100644 index 52c96ef42..000000000 --- a/astrid/plugin-src/com/todoroo/astrid/people/PeopleViewActivity.java +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (c) 2012 Todoroo Inc - * - * See the file "LICENSE" for the full license governing this code. - */ -package com.todoroo.astrid.people; - -import android.content.Intent; -import android.os.Bundle; - -import com.timsu.astrid.R; -import com.todoroo.andlib.utility.AndroidUtilities; -import com.todoroo.astrid.activity.FilterListFragment; -import com.todoroo.astrid.activity.TaskListActivity; -import com.todoroo.astrid.activity.TaskListFragment; -import com.todoroo.astrid.api.Filter; -import com.todoroo.astrid.api.FilterListItem; -import com.todoroo.astrid.api.FilterWithUpdate; -import com.todoroo.astrid.helper.AsyncImageView; -import com.todoroo.astrid.ui.MainMenuPopover; - -public class PeopleViewActivity extends TaskListActivity { - - private AsyncImageView imageView; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - imageView = (AsyncImageView) findViewById(R.id.person_image); - imageView.setDefaultImageResource(R.drawable.icn_default_person_image); - } - - @Override - protected int getContentView() { - return R.layout.people_view_wrapper_activity; - } - - @Override - protected Filter getDefaultFilter() { - Filter defaultFilter = PeopleFilterExposer.mySharedTasks(this); - return defaultFilter; - } - - @Override - protected Class getFilterListClass() { - return PeopleListFragment.class; - } - - @Override - protected int getHeaderView() { - return R.layout.header_people_view; - } - - @Override - public boolean onFilterItemClicked(FilterListItem item) { - boolean result = super.onFilterItemClicked(item); - if (result && item instanceof FilterWithUpdate) - imageView.setUrl(((FilterWithUpdate) item).imageUrl); - else - imageView.setUrl(null); - return result; - } - - private static final int[] FORBIDDEN_MENU_ITEMS = { - TaskListFragment.MENU_NEW_FILTER_ID, - TaskListFragment.MENU_ADDONS_ID, - MainMenuPopover.MAIN_MENU_ITEM_FRIENDS - }; - - @Override - public boolean shouldAddMenuItem(int itemId) { - return AndroidUtilities.indexOf(FORBIDDEN_MENU_ITEMS, itemId) < 0; - } - - @Override - public void mainMenuItemSelected(int item, Intent customIntent) { - if (item == MainMenuPopover.MAIN_MENU_ITEM_LISTS) { - finish(); - return; - } - - super.mainMenuItemSelected(item, customIntent); - } -} diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java index 0d695b170..54113cb64 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java @@ -6,6 +6,7 @@ package com.todoroo.astrid.tags; import java.util.ArrayList; +import java.util.List; import android.app.Activity; import android.content.BroadcastReceiver; @@ -60,9 +61,11 @@ public class TagFilterExposer extends BroadcastReceiver implements AstridFilterE private static final String TAG = "tag"; //$NON-NLS-1$ - @Autowired TagDataService tagDataService; + @Autowired protected TagDataService tagDataService; @Autowired GtasksPreferenceService gtasksPreferenceService; + protected boolean addUntaggedFilter = true; + /** Create filter from new tag object */ @SuppressWarnings("nls") public static FilterWithCustomIntent filterFromTag(Context context, Tag tag, Criterion criterion) { @@ -141,7 +144,7 @@ public class TagFilterExposer extends BroadcastReceiver implements AstridFilterE context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); } - private FilterListItem[] prepareFilters(Context context) { + protected FilterListItem[] prepareFilters(Context context) { DependencyInjectionService.getInstance().inject(this); ContextManager.setContext(context); @@ -155,13 +158,18 @@ public class TagFilterExposer extends BroadcastReceiver implements AstridFilterE } private void addTags(ArrayList list) { - ArrayList tagList = TagService.getInstance().getTagList(); + List tagList = getTagList(); list.add(filterFromTags(tagList.toArray(new Tag[tagList.size()]), R.string.tag_FEx_header)); } + protected List getTagList() { + return TagService.getInstance().getTagList(); + } + private FilterCategory filterFromTags(Tag[] tags, int name) { - Filter[] filters = new Filter[tags.length + 1]; + int length = addUntaggedFilter ? tags.length + 1 : tags.length; + Filter[] filters = new Filter[length]; Context context = ContextManager.getContext(); Resources r = context.getResources(); @@ -170,22 +178,30 @@ public class TagFilterExposer extends BroadcastReceiver implements AstridFilterE int themeFlags = isTablet ? ThemeService.FLAG_FORCE_LIGHT : 0; // --- untagged - int untaggedLabel = gtasksPreferenceService.isLoggedIn() ? - R.string.tag_FEx_untagged_w_astrid : R.string.tag_FEx_untagged; - Filter untagged = new Filter(r.getString(untaggedLabel), - r.getString(R.string.tag_FEx_untagged), - TagService.untaggedTemplate(), - null); - untagged.listingIcon = ((BitmapDrawable)r.getDrawable( - ThemeService.getDrawable(R.drawable.gl_lists, themeFlags))).getBitmap(); - filters[0] = untagged; - - for(int i = 0; i < tags.length; i++) - filters[i+1] = filterFromTag(context, tags[i], TaskCriteria.activeAndVisible()); + if (addUntaggedFilter) { + int untaggedLabel = gtasksPreferenceService.isLoggedIn() ? + R.string.tag_FEx_untagged_w_astrid : R.string.tag_FEx_untagged; + Filter untagged = new Filter(r.getString(untaggedLabel), + r.getString(R.string.tag_FEx_untagged), + TagService.untaggedTemplate(), + null); + untagged.listingIcon = ((BitmapDrawable)r.getDrawable( + ThemeService.getDrawable(R.drawable.gl_lists, themeFlags))).getBitmap(); + filters[0] = untagged; + } + + for(int i = 0; i < tags.length; i++) { + int index = addUntaggedFilter ? i + 1 : i; + filters[index] = constructFilter(context, tags[i]); + } FilterCategory filter = new FilterCategory(context.getString(name), filters); return filter; } + protected Filter constructFilter(Context context, Tag tag) { + return filterFromTag(context, tag, TaskCriteria.activeAndVisible()); + } + // --- tag manipulation activities public abstract static class TagActivity extends Activity { diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java index 7802e4e58..78290471e 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagService.java @@ -330,7 +330,7 @@ public final class TagService { tagData.readFromCursor(cursor); String tagName = tagData.getValue(TagData.NAME).trim(); Tag tag = new Tag(tagData); - if(tagData.getValue(TagData.DELETION_DATE) > 0 || tagData.getFlag(TagData.FLAGS, TagData.FLAG_EMERGENT)) { + if(tagData.getValue(TagData.DELETION_DATE) > 0 || tagData.getFlag(TagData.FLAGS, TagData.FLAG_EMERGENT) || tagData.getFlag(TagData.FLAGS, TagData.FLAG_FEATURED)) { tags.remove(tagName); continue; } @@ -356,6 +356,37 @@ public final class TagService { return tagList; } + public ArrayList getFeaturedLists() { + HashMap tags = new HashMap(); + + TodorooCursor cursor = tagDataService.query(Query.select(TagData.PROPERTIES) + .where(Functions.bitwiseAnd(TagData.FLAGS, TagData.FLAG_FEATURED).gt(0))); + try { + TagData tagData = new TagData(); + for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + tagData.readFromCursor(cursor); + if (tagData.getValue(TagData.DELETION_DATE) > 0) + continue; + String tagName = tagData.getValue(TagData.NAME).trim(); + Tag tag = new Tag(tagData); + if(TextUtils.isEmpty(tag.tag)) + continue; + tags.put(tagName, tag); + } + } finally { + cursor.close(); + } + ArrayList tagList = new ArrayList(tags.values()); + Collections.sort(tagList, + new Comparator() { + @Override + public int compare(Tag object1, Tag object2) { + return object1.tag.compareToIgnoreCase(object2.tag); + } + }); + return tagList; + } + /** * Save the given array of tags into the database * @param taskId diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFilterAdapter.java b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFilterAdapter.java new file mode 100644 index 000000000..9b45cc937 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFilterAdapter.java @@ -0,0 +1,37 @@ +package com.todoroo.astrid.tags.reusable; + +import android.app.Activity; +import android.content.Intent; +import android.content.IntentFilter; +import android.widget.ListView; + +import com.todoroo.astrid.adapter.FilterAdapter; +import com.todoroo.astrid.utility.Constants; + +public class FeaturedListFilterAdapter extends FilterAdapter { + public static final String BROADCAST_REQUEST_FEATURED_LISTS = Constants.PACKAGE + ".REQUEST_FEATURED_LISTS"; //$NON-NLS-1$ + public static final String BROADCAST_SEND_FEATURED_LISTS = Constants.PACKAGE + ".SEND_FEATURED_LISTS"; //$NON-NLS-1$ + + public FeaturedListFilterAdapter(Activity activity, ListView listView, + int rowLayout, boolean skipIntentFilters) { + super(activity, listView, rowLayout, skipIntentFilters); + } + + @Override + public void getLists() { + Intent broadcastIntent = new Intent(BROADCAST_REQUEST_FEATURED_LISTS); + activity.sendBroadcast(broadcastIntent); + } + + @Override + public void registerRecevier() { + IntentFilter peopleFilter = new IntentFilter(BROADCAST_SEND_FEATURED_LISTS); + activity.registerReceiver(filterReceiver, peopleFilter); + getLists(); + } + + @Override + public void unregisterRecevier() { + activity.unregisterReceiver(filterReceiver); + } +} diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFilterExposer.java new file mode 100644 index 000000000..bedd759bc --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFilterExposer.java @@ -0,0 +1,102 @@ +package com.todoroo.astrid.tags.reusable; + +import java.util.List; + +import android.content.ComponentName; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.service.ContextManager; +import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.Functions; +import com.todoroo.andlib.sql.Order; +import com.todoroo.andlib.sql.Query; +import com.todoroo.andlib.sql.QueryTemplate; +import com.todoroo.astrid.actfm.TagViewFragment; +import com.todoroo.astrid.api.AstridApiConstants; +import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.api.FilterListItem; +import com.todoroo.astrid.api.FilterWithCustomIntent; +import com.todoroo.astrid.api.FilterWithUpdate; +import com.todoroo.astrid.core.PluginServices; +import com.todoroo.astrid.data.Metadata; +import com.todoroo.astrid.data.TagData; +import com.todoroo.astrid.data.TaskApiDao.TaskCriteria; +import com.todoroo.astrid.tags.TagFilterExposer; +import com.todoroo.astrid.tags.TagService; +import com.todoroo.astrid.tags.TagService.Tag; + +public class FeaturedListFilterExposer extends TagFilterExposer { + + @Override + public void onReceive(Context context, Intent intent) { + addUntaggedFilter = false; + FilterListItem[] listAsArray = prepareFilters(context); + + Intent broadcastIntent = new Intent(FeaturedListFilterAdapter.BROADCAST_SEND_FEATURED_LISTS); + broadcastIntent.putExtra(AstridApiConstants.EXTRAS_RESPONSE, listAsArray); + context.sendBroadcast(broadcastIntent, AstridApiConstants.PERMISSION_READ); + } + + private static FilterWithCustomIntent filterFromFeaturedList(Tag tag, Criterion criterion) { + String title = tag.tag; + QueryTemplate tagTemplate = tag.queryTemplate(criterion); + ContentValues contentValues = new ContentValues(); + contentValues.put(Metadata.KEY.name, TagService.KEY); + contentValues.put(TagService.TAG.name, tag.tag); + + FilterWithUpdate filter = new FilterWithUpdate(tag.tag, + title, tagTemplate, + contentValues); + + Class fragmentClass = FeaturedTaskListFragment.class; + filter.customTaskList = new ComponentName(ContextManager.getContext(), fragmentClass); + if(tag.image != null) + filter.imageUrl = tag.image; + + Bundle extras = new Bundle(); + extras.putString(TagViewFragment.EXTRA_TAG_NAME, tag.tag); + extras.putLong(TagViewFragment.EXTRA_TAG_REMOTE_ID, tag.remoteId); + filter.customExtras = extras; + + return filter; + } + + public static Filter getDefaultFilter() { + TodorooCursor firstFilter = PluginServices.getTagDataService() + .query(Query.select(TagData.PROPERTIES) + .where(Criterion.and( + Functions.bitwiseAnd(TagData.FLAGS, TagData.FLAG_FEATURED).gt(0), + TagData.DELETION_DATE.eq(0), + TagData.NAME.isNotNull(), + TagData.NAME.neq(""))) //$NON-NLS-1$ + .orderBy(Order.asc(TagData.NAME)) + .limit(1)); + try { + if (firstFilter.getCount() > 0) { + firstFilter.moveToFirst(); + TagData tagData = new TagData(firstFilter); + Tag tag = new Tag(tagData); + return filterFromFeaturedList(tag, TaskCriteria.activeAndVisible()); + } else { + return null; + } + } finally { + firstFilter.close(); + } + } + + @Override + protected Filter constructFilter(Context context, Tag tag) { + return filterFromFeaturedList(tag, TaskCriteria.activeAndVisible()); + } + + @Override + protected List getTagList() { + return TagService.getInstance().getFeaturedLists(); + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFilterMode.java b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFilterMode.java new file mode 100644 index 000000000..17b88d631 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFilterMode.java @@ -0,0 +1,53 @@ +package com.todoroo.astrid.tags.reusable; + +import android.content.Context; + +import com.timsu.astrid.R; +import com.todoroo.astrid.activity.FilterListFragment; +import com.todoroo.astrid.activity.FilterModeSpec; +import com.todoroo.astrid.activity.TaskListFragment; +import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.api.FilterListItem; +import com.todoroo.astrid.core.CoreFilterExposer; +import com.todoroo.astrid.ui.MainMenuPopover; + +public class FeaturedListFilterMode implements FilterModeSpec { + + @Override + public int[] getForbiddenMenuItems() { + return FORBIDDEN_MENU_ITEMS; + } + + private static final int[] FORBIDDEN_MENU_ITEMS = { + TaskListFragment.MENU_NEW_FILTER_ID, + TaskListFragment.MENU_ADDONS_ID, + MainMenuPopover.MAIN_MENU_ITEM_FEATURED_LISTS + }; + + @Override + public Class getFilterListClass() { + return FeaturedListFragment.class; + } + + @Override + public Filter getDefaultFilter(Context context) { + Filter defaultFilter = FeaturedListFilterExposer.getDefaultFilter(); + if (defaultFilter == null) + return CoreFilterExposer.buildInboxFilter(context.getResources()); + else + return defaultFilter; + } + + @Override + public int getMainMenuIconAttr() { + return R.attr.asMainMenu; + } + + @Override + public void onFilterItemClickedCallback(FilterListItem item) { + // + } + + + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFragment.java b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFragment.java new file mode 100644 index 000000000..55fe921a3 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedListFragment.java @@ -0,0 +1,25 @@ +package com.todoroo.astrid.tags.reusable; + +import android.app.Activity; + +import com.timsu.astrid.R; +import com.todoroo.andlib.utility.AndroidUtilities; +import com.todoroo.astrid.activity.FilterListFragment; +import com.todoroo.astrid.adapter.FilterAdapter; + +public class FeaturedListFragment extends FilterListFragment { + + @Override + protected FilterAdapter instantiateAdapter() { + return new FeaturedListFilterAdapter(getActivity(), null, R.layout.filter_adapter_row, false); + } + + @Override + protected int getLayout(Activity activity) { + if (AndroidUtilities.isTabletSized(activity)) + return R.layout.filter_list_fragment_alternative_3pane; + else + return R.layout.filter_list_fragment_alternative; + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedTaskListFragment.java b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedTaskListFragment.java new file mode 100644 index 000000000..58968b738 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/FeaturedTaskListFragment.java @@ -0,0 +1,113 @@ +package com.todoroo.astrid.tags.reusable; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.TextView; +import android.widget.Toast; + +import com.timsu.astrid.R; +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.sql.Query; +import com.todoroo.andlib.utility.DialogUtilities; +import com.todoroo.astrid.actfm.TagViewFragment; +import com.todoroo.astrid.adapter.TaskAdapter; +import com.todoroo.astrid.data.TagData; +import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.service.TagDataService; +import com.todoroo.astrid.utility.Flags; + +public class FeaturedTaskListFragment extends TagViewFragment { + + @Autowired private TagDataService tagDataService; + + @Override + protected TaskAdapter createTaskAdapter(TodorooCursor cursor) { + return new ReusableTaskAdapter(this, R.layout.reusable_task_adapter_row, + cursor, sqlQueryTemplate, false, null); + } + + @Override + protected void setupQuickAddBar() { + super.setupQuickAddBar(); + quickAddBar.setVisibility(View.GONE); + ((TextView) getView().findViewById(android.R.id.empty)).setOnClickListener(null); + } + + @Override + public void onTaskListItemClicked(long taskId) { + // Do nothing + } + + @Override + protected int getTaskListBodyLayout() { + return R.layout.task_list_body_featured_list; + } + + @Override + protected void setUpMembersGallery() { + // Repurposed this method to set up listener for clone list button + View clone = getView().findViewById(R.id.clone_list); + clone.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + // Clone list + if (taskAdapter == null || taskAdapter.getCount() == 0) { + Toast.makeText(getActivity(), R.string.actfm_feat_list_clone_empty, Toast.LENGTH_LONG).show(); + return; + } + final String localName = tagData.getValue(TagData.NAME) + " " + getString(R.string.actfm_feat_list_suffix); //$NON-NLS-1$ + long remoteId = 0; + TodorooCursor existing = tagDataService.query(Query.select(TagData.REMOTE_ID) + .where(TagData.NAME.eqCaseInsensitive(localName))); + try { + if (existing.getCount() > 0) { + existing.moveToFirst(); + TagData match = new TagData(existing); + remoteId = match.getValue(TagData.REMOTE_ID); + } + + } finally { + existing.close(); + } + + final ProgressDialog pd = DialogUtilities.progressDialog(getActivity(), getString(R.string.actfm_feat_list_cloning)); + + final long finalRemoteId = remoteId; + new Thread(new Runnable() { + @Override + public void run() { + TodorooCursor tasks = taskService.fetchFiltered(taskAdapter.getQuery(), null, Task.PROPERTIES); + try { + Task t = new Task(); + for (tasks.moveToFirst(); !tasks.isAfterLast(); tasks.moveToNext()) { + t.readFromCursor(tasks); + taskService.cloneReusableTask(t, + localName, finalRemoteId); + } + Activity activity = getActivity(); + if (activity != null) { + DialogUtilities.dismissDialog(activity, pd); + DialogUtilities.okDialog(activity, getString(R.string.actfm_feat_list_clone_success), null); + } + + Flags.set(Flags.REFRESH); + } finally { + tasks.close(); + } + } + }).start(); + } + }); + } + + @Override + protected void refresh() { + loadTaskListContent(true); + ((TextView)taskListView.findViewById(android.R.id.empty)).setText(R.string.TLA_no_items); + setUpMembersGallery(); + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/reusable/ReusableTaskAdapter.java b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/ReusableTaskAdapter.java new file mode 100644 index 000000000..afce2760b --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/tags/reusable/ReusableTaskAdapter.java @@ -0,0 +1,84 @@ +package com.todoroo.astrid.tags.reusable; + +import java.util.concurrent.atomic.AtomicReference; + +import android.content.Context; +import android.database.Cursor; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.timsu.astrid.R; +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.utility.Preferences; +import com.todoroo.astrid.activity.TaskListFragment; +import com.todoroo.astrid.adapter.TaskAdapter; +import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.utility.Flags; + +public class ReusableTaskAdapter extends TaskAdapter { + + public ReusableTaskAdapter(TaskListFragment fragment, int resource, + Cursor c, AtomicReference query, boolean autoRequery, + OnCompletedTaskListener onCompletedTaskListener) { + super(fragment, resource, c, query, autoRequery, onCompletedTaskListener); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + ViewGroup view = (ViewGroup)inflater.inflate(resource, parent, false); + + ReusableTaskViewHolder viewHolder = new ReusableTaskViewHolder(); + viewHolder.task = new Task(); + viewHolder.clone = (ImageView) view.findViewById(R.id.clone_task); + viewHolder.title = (TextView) view.findViewById(R.id.title); + + boolean showFullTaskTitle = Preferences.getBoolean(R.string.p_fullTaskTitle, false); + if (showFullTaskTitle) { + viewHolder.title.setMaxLines(Integer.MAX_VALUE); + } + + view.setTag(viewHolder); + for(int i = 0; i < view.getChildCount(); i++) + view.getChildAt(i).setTag(viewHolder); + + viewHolder.clone.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + ReusableTaskViewHolder holder = (ReusableTaskViewHolder) v.getTag(); + taskService.cloneReusableTask(holder.task, null, 0); + Toast.makeText(fragment.getActivity(), R.string.actfm_feat_list_task_clone_success, Toast.LENGTH_LONG).show(); + Flags.set(Flags.REFRESH); + } + }); + + return view; + } + + @Override + public void bindView(View view, Context context, Cursor c) { + TodorooCursor cursor = (TodorooCursor)c; + ReusableTaskViewHolder viewHolder = (ReusableTaskViewHolder) view.getTag(); + + Task task = viewHolder.task; + task.clear(); + task.readFromCursor(cursor); + + viewHolder.title.setText(task.getValue(Task.TITLE)); + if (Preferences.getBoolean(R.string.p_allowCompressedTaskRows, false)) { + view.setMinimumHeight(0); + } else { + view.setMinimumHeight(minRowHeight); + } + } + + public static class ReusableTaskViewHolder { + public Task task; + public ImageView clone; + public TextView title; + } + +} diff --git a/astrid/res/drawable-hdpi/icn_clone_down.png b/astrid/res/drawable-hdpi/icn_clone_down.png new file mode 100644 index 000000000..d99f8ad7f Binary files /dev/null and b/astrid/res/drawable-hdpi/icn_clone_down.png differ diff --git a/astrid/res/drawable-hdpi/icn_clone_up.png b/astrid/res/drawable-hdpi/icn_clone_up.png new file mode 100644 index 000000000..1ece4dce0 Binary files /dev/null and b/astrid/res/drawable-hdpi/icn_clone_up.png differ diff --git a/astrid/res/drawable-hdpi/icn_featured_lists.png b/astrid/res/drawable-hdpi/icn_featured_lists.png new file mode 100644 index 000000000..e2bda464f Binary files /dev/null and b/astrid/res/drawable-hdpi/icn_featured_lists.png differ diff --git a/astrid/res/drawable-hdpi/icn_featured_lists_dark.png b/astrid/res/drawable-hdpi/icn_featured_lists_dark.png new file mode 100644 index 000000000..a764b1bf6 Binary files /dev/null and b/astrid/res/drawable-hdpi/icn_featured_lists_dark.png differ diff --git a/astrid/res/drawable/btn_clone.xml b/astrid/res/drawable/btn_clone.xml new file mode 100644 index 000000000..ed1a20a15 --- /dev/null +++ b/astrid/res/drawable/btn_clone.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/astrid/res/drawable/icn_clone_down.png b/astrid/res/drawable/icn_clone_down.png new file mode 100644 index 000000000..c2c4bf5b1 Binary files /dev/null and b/astrid/res/drawable/icn_clone_down.png differ diff --git a/astrid/res/drawable/icn_clone_up.png b/astrid/res/drawable/icn_clone_up.png new file mode 100644 index 000000000..30cd930ab Binary files /dev/null and b/astrid/res/drawable/icn_clone_up.png differ diff --git a/astrid/res/drawable/icn_featured_lists.png b/astrid/res/drawable/icn_featured_lists.png index e2e8f5916..9ffff8074 100644 Binary files a/astrid/res/drawable/icn_featured_lists.png and b/astrid/res/drawable/icn_featured_lists.png differ diff --git a/astrid/res/drawable/icn_featured_lists_dark.png b/astrid/res/drawable/icn_featured_lists_dark.png new file mode 100644 index 000000000..7b34e0deb Binary files /dev/null and b/astrid/res/drawable/icn_featured_lists_dark.png differ diff --git a/astrid/res/layout-land/people_list_fragment.xml b/astrid/res/layout-land/filter_list_fragment_alternative.xml similarity index 89% rename from astrid/res/layout-land/people_list_fragment.xml rename to astrid/res/layout-land/filter_list_fragment_alternative.xml index d7a64a442..8c7491d4d 100644 --- a/astrid/res/layout-land/people_list_fragment.xml +++ b/astrid/res/layout-land/filter_list_fragment_alternative.xml @@ -18,6 +18,8 @@ android:layout_alignParentTop="true" android:scrollbars="vertical" android:divider="@android:color/transparent" + android:dividerHeight="1px" + android:background="@android:color/transparent" android:cacheColorHint="#00000000"/> diff --git a/astrid/res/layout/featured_list_activity.xml b/astrid/res/layout/featured_list_activity.xml new file mode 100644 index 000000000..99789bebb --- /dev/null +++ b/astrid/res/layout/featured_list_activity.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/astrid/res/layout/people_list_fragment.xml b/astrid/res/layout/filter_list_fragment_alternative.xml similarity index 89% rename from astrid/res/layout/people_list_fragment.xml rename to astrid/res/layout/filter_list_fragment_alternative.xml index e452d04f0..f3609e5a7 100644 --- a/astrid/res/layout/people_list_fragment.xml +++ b/astrid/res/layout/filter_list_fragment_alternative.xml @@ -18,6 +18,8 @@ android:layout_alignParentTop="true" android:scrollbars="vertical" android:divider="@android:color/transparent" + android:dividerHeight="1px" + android:background="@android:color/transparent" android:cacheColorHint="#00000000"/> diff --git a/astrid/res/layout/people_list_fragment_3pane.xml b/astrid/res/layout/filter_list_fragment_alternative_3pane.xml similarity index 88% rename from astrid/res/layout/people_list_fragment_3pane.xml rename to astrid/res/layout/filter_list_fragment_alternative_3pane.xml index d5144a3f3..db5720bc1 100644 --- a/astrid/res/layout/people_list_fragment_3pane.xml +++ b/astrid/res/layout/filter_list_fragment_alternative_3pane.xml @@ -18,6 +18,8 @@ android:layout_alignParentTop="true" android:scrollbars="vertical" android:divider="@android:color/transparent" + android:dividerHeight="1px" + android:background="@android:color/transparent" android:cacheColorHint="#00000000"/> diff --git a/astrid/res/layout/header_nav_views.xml b/astrid/res/layout/header_nav_views.xml index 12a87b63a..8430fb867 100644 --- a/astrid/res/layout/header_nav_views.xml +++ b/astrid/res/layout/header_nav_views.xml @@ -54,6 +54,13 @@ + +