From 1e08daa28daa7137e621e23bbf229a8f3cbfbf1a Mon Sep 17 00:00:00 2001 From: Eric Kok Date: Fri, 28 Jun 2013 11:23:48 +0200 Subject: [PATCH] Replaced old merge and sack adapters will leaner, focussed merge and view holder adapter; the latter also correctly manages view visibility by showing and hiding rows. --- core/res/layout/list_item_simple.xml | 3 +- core/res/menu/activity_details.xml | 3 +- core/res/menu/activity_torrents.xml | 32 +- core/res/menu/fragment_details.xml | 21 +- ...ails_file.xml => fragment_details_cab.xml} | 0 .../commonsware/cwac/merge/MergeAdapter.java | 481 ------------------ .../cwac/sacklist/SackOfViewsAdapter.java | 177 ------- .../transdroid/core/gui/DetailsFragment.java | 2 +- .../core/gui/lists/DetailsAdapter.java | 75 +-- .../core/gui/lists/MergeAdapter.java | 298 +++++++++++ .../core/gui/lists/SimpleListItemAdapter.java | 7 +- .../core/gui/lists/SimpleListItemView.java | 4 +- .../core/gui/lists/ViewHolderAdapter.java | 107 ++++ .../gui/navigation/FilterListAdapter.java | 44 +- .../navigation/FilterListDropDownAdapter.java | 2 +- 15 files changed, 518 insertions(+), 738 deletions(-) rename core/res/menu/{fragment_details_file.xml => fragment_details_cab.xml} (100%) delete mode 100644 core/src/com/commonsware/cwac/merge/MergeAdapter.java delete mode 100644 core/src/com/commonsware/cwac/sacklist/SackOfViewsAdapter.java create mode 100644 core/src/org/transdroid/core/gui/lists/MergeAdapter.java create mode 100644 core/src/org/transdroid/core/gui/lists/ViewHolderAdapter.java diff --git a/core/res/layout/list_item_simple.xml b/core/res/layout/list_item_simple.xml index 0a62b6d7..48a567f0 100644 --- a/core/res/layout/list_item_simple.xml +++ b/core/res/layout/list_item_simple.xml @@ -12,7 +12,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="@dimen/text_default" - android:textIsSelectable="false" - android:autoLink="web" /> + android:textIsSelectable="false" /> \ No newline at end of file diff --git a/core/res/menu/activity_details.xml b/core/res/menu/activity_details.xml index cbfb2613..c1582953 100644 --- a/core/res/menu/activity_details.xml +++ b/core/res/menu/activity_details.xml @@ -4,6 +4,7 @@ android:id="@+id/action_refresh" android:icon="?attr/ic_action_refresh" android:showAsAction="always" - android:title="@string/action_refresh"/> + android:title="@string/action_refresh" + android:orderInCategory="100"/> \ No newline at end of file diff --git a/core/res/menu/activity_torrents.xml b/core/res/menu/activity_torrents.xml index b275c63f..633e76f0 100644 --- a/core/res/menu/activity_torrents.xml +++ b/core/res/menu/activity_torrents.xml @@ -4,7 +4,8 @@ android:id="@+id/action_add" android:icon="?attr/ic_action_new" android:showAsAction="ifRoom" - android:title="@string/action_add"> + android:title="@string/action_add" + android:orderInCategory="100"> @@ -14,33 +15,39 @@ + android:showAsAction="collapseActionView|ifRoom" + android:title="@string/action_search" + android:orderInCategory="101"/> + android:title="@string/action_rss" + android:orderInCategory="900"/> + android:title="@string/action_enableturtle" + android:orderInCategory="901"/> + android:title="@string/action_disableturtle" + android:orderInCategory="902"/> + android:title="@string/action_refresh" + android:orderInCategory="903"/> + android:title="@string/action_sort" + android:orderInCategory="904"> @@ -53,14 +60,17 @@ + android:title="@string/action_filter" + android:orderInCategory="905"/> + android:title="@string/action_help" + android:orderInCategory="906"/> + android:title="@string/action_settings" + android:orderInCategory="907"/> \ No newline at end of file diff --git a/core/res/menu/fragment_details.xml b/core/res/menu/fragment_details.xml index ac38581d..2b4c5ac9 100644 --- a/core/res/menu/fragment_details.xml +++ b/core/res/menu/fragment_details.xml @@ -4,17 +4,20 @@ android:id="@+id/action_resume" android:icon="?attr/ic_action_resume" android:showAsAction="always" - android:title="@string/action_resume" /> + android:title="@string/action_resume" + android:orderInCategory="200" /> + android:title="@string/action_pause" + android:orderInCategory="201" /> + android:title="@string/action_start" + android:orderInCategory="202"> @@ -24,12 +27,14 @@ android:id="@+id/action_stop" android:icon="?attr/ic_action_stop" android:showAsAction="ifRoom" - android:title="@string/action_stop" /> + android:title="@string/action_stop" + android:orderInCategory="203" /> + android:title="@string/action_remove" + android:orderInCategory="204"> @@ -39,11 +44,13 @@ android:id="@+id/action_setlabel" android:icon="?attr/ic_action_labels" android:showAsAction="ifRoom" - android:title="@string/action_setlabel" /> + android:title="@string/action_setlabel" + android:orderInCategory="205" /> + android:title="@string/action_updatetrackers" + android:orderInCategory="206" /> \ No newline at end of file diff --git a/core/res/menu/fragment_details_file.xml b/core/res/menu/fragment_details_cab.xml similarity index 100% rename from core/res/menu/fragment_details_file.xml rename to core/res/menu/fragment_details_cab.xml diff --git a/core/src/com/commonsware/cwac/merge/MergeAdapter.java b/core/src/com/commonsware/cwac/merge/MergeAdapter.java deleted file mode 100644 index a713b862..00000000 --- a/core/src/com/commonsware/cwac/merge/MergeAdapter.java +++ /dev/null @@ -1,481 +0,0 @@ -/*** - Copyright (c) 2008-2009 CommonsWare, LLC - Portions (c) 2009 Google, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -package com.commonsware.cwac.merge; - -import android.database.DataSetObserver; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ListAdapter; -import android.widget.SectionIndexer; -import java.util.ArrayList; -import java.util.List; -import com.commonsware.cwac.sacklist.SackOfViewsAdapter; - -/** - * Adapter that merges multiple child adapters and views - * into a single contiguous whole. - * - * Adapters used as pieces within MergeAdapter must have - * view type IDs monotonically increasing from 0. Ideally, - * adapters also have distinct ranges for their row ids, as - * returned by getItemId(). - * - */ -public class MergeAdapter extends BaseAdapter implements SectionIndexer { - protected PieceStateRoster pieces=new PieceStateRoster(); - - /** - * Stock constructor, simply chaining to the superclass. - */ - public MergeAdapter() { - super(); - } - - /** - * Adds a new adapter to the roster of things to appear in - * the aggregate list. - * - * @param adapter - * Source for row views for this section - */ - public void addAdapter(ListAdapter adapter) { - pieces.add(adapter); - adapter.registerDataSetObserver(new CascadeDataSetObserver()); - } - - /** - * Adds a new View to the roster of things to appear in - * the aggregate list. - * - * @param view - * Single view to add - */ - public void addView(View view) { - addView(view, false); - } - - /** - * Adds a new View to the roster of things to appear in - * the aggregate list. - * - * @param view - * Single view to add - * @param enabled - * false if views are disabled, true if enabled - */ - public void addView(View view, boolean enabled) { - ArrayList list=new ArrayList(1); - - list.add(view); - - addViews(list, enabled); - } - - /** - * Adds a list of views to the roster of things to appear - * in the aggregate list. - * - * @param views - * List of views to add - */ - public void addViews(List views) { - addViews(views, false); - } - - /** - * Adds a list of views to the roster of things to appear - * in the aggregate list. - * - * @param views - * List of views to add - * @param enabled - * false if views are disabled, true if enabled - */ - public void addViews(List views, boolean enabled) { - if (enabled) { - addAdapter(new EnabledSackAdapter(views)); - } - else { - addAdapter(new SackOfViewsAdapter(views)); - } - } - - /** - * Get the data item associated with the specified - * position in the data set. - * - * @param position - * Position of the item whose data we want - */ - @Override - public Object getItem(int position) { - for (ListAdapter piece : getPieces()) { - int size=piece.getCount(); - - if (position < size) { - return(piece.getItem(position)); - } - - position-=size; - } - - return(null); - } - - /** - * Get the adapter associated with the specified position - * in the data set. - * - * @param position - * Position of the item whose adapter we want - */ - public ListAdapter getAdapter(int position) { - for (ListAdapter piece : getPieces()) { - int size=piece.getCount(); - - if (position < size) { - return(piece); - } - - position-=size; - } - - return(null); - } - - /** - * How many items are in the data set represented by this - * Adapter. - */ - @Override - public int getCount() { - int total=0; - - for (ListAdapter piece : getPieces()) { - total+=piece.getCount(); - } - - return(total); - } - - /** - * Returns the number of types of Views that will be - * created by getView(). - */ - @Override - public int getViewTypeCount() { - int total=0; - - for (PieceState piece : pieces.getRawPieces()) { - total+=piece.adapter.getViewTypeCount(); - } - - return(Math.max(total, 1)); // needed for - // setListAdapter() before - // content add' - } - - /** - * Get the type of View that will be created by getView() - * for the specified item. - * - * @param position - * Position of the item whose data we want - */ - @Override - public int getItemViewType(int position) { - int typeOffset=0; - int result=-1; - - for (PieceState piece : pieces.getRawPieces()) { - if (piece.isActive) { - int size=piece.adapter.getCount(); - - if (position < size) { - result=typeOffset + piece.adapter.getItemViewType(position); - break; - } - - position-=size; - } - - typeOffset+=piece.adapter.getViewTypeCount(); - } - - return(result); - } - - /** - * Are all items in this ListAdapter enabled? If yes it - * means all items are selectable and clickable. - */ - @Override - public boolean areAllItemsEnabled() { - return(false); - } - - /** - * Returns true if the item at the specified position is - * not a separator. - * - * @param position - * Position of the item whose data we want - */ - @Override - public boolean isEnabled(int position) { - for (ListAdapter piece : getPieces()) { - int size=piece.getCount(); - - if (position < size) { - return(piece.isEnabled(position)); - } - - position-=size; - } - - return(false); - } - - /** - * Get a View that displays the data at the specified - * position in the data set. - * - * @param position - * Position of the item whose data we want - * @param convertView - * View to recycle, if not null - * @param parent - * ViewGroup containing the returned View - */ - @Override - public View getView(int position, View convertView, ViewGroup parent) { - for (ListAdapter piece : getPieces()) { - int size=piece.getCount(); - - if (position < size) { - - return(piece.getView(position, convertView, parent)); - } - - position-=size; - } - - return(null); - } - - /** - * Get the row id associated with the specified position - * in the list. - * - * @param position - * Position of the item whose data we want - */ - @Override - public long getItemId(int position) { - for (ListAdapter piece : getPieces()) { - int size=piece.getCount(); - - if (position < size) { - return(piece.getItemId(position)); - } - - position-=size; - } - - return(-1); - } - - @Override - public int getPositionForSection(int section) { - int position=0; - - for (ListAdapter piece : getPieces()) { - if (piece instanceof SectionIndexer) { - Object[] sections=((SectionIndexer)piece).getSections(); - int numSections=0; - - if (sections != null) { - numSections=sections.length; - } - - if (section < numSections) { - return(position + ((SectionIndexer)piece).getPositionForSection(section)); - } - else if (sections != null) { - section-=numSections; - } - } - - position+=piece.getCount(); - } - - return(0); - } - - @Override - public int getSectionForPosition(int position) { - int section=0; - - for (ListAdapter piece : getPieces()) { - int size=piece.getCount(); - - if (position < size) { - if (piece instanceof SectionIndexer) { - return(section + ((SectionIndexer)piece).getSectionForPosition(position)); - } - - return(0); - } - else { - if (piece instanceof SectionIndexer) { - Object[] sections=((SectionIndexer)piece).getSections(); - - if (sections != null) { - section+=sections.length; - } - } - } - - position-=size; - } - - return(0); - } - - @Override - public Object[] getSections() { - ArrayList sections=new ArrayList(); - - for (ListAdapter piece : getPieces()) { - if (piece instanceof SectionIndexer) { - Object[] curSections=((SectionIndexer)piece).getSections(); - - if (curSections != null) { - for (Object section : curSections) { - sections.add(section); - } - } - } - } - - if (sections.size() == 0) { - return(new String[0]); - } - - return(sections.toArray(new Object[0])); - } - - public void setActive(ListAdapter adapter, boolean isActive) { - pieces.setActive(adapter, isActive); - notifyDataSetChanged(); - } - - public void setActive(View v, boolean isActive) { - pieces.setActive(v, isActive); - notifyDataSetChanged(); - } - - protected List getPieces() { - return(pieces.getPieces()); - } - - private static class PieceState { - ListAdapter adapter; - boolean isActive=true; - - PieceState(ListAdapter adapter, boolean isActive) { - this.adapter=adapter; - this.isActive=isActive; - } - } - - private static class PieceStateRoster { - protected ArrayList pieces=new ArrayList(); - protected ArrayList active=null; - - void add(ListAdapter adapter) { - pieces.add(new PieceState(adapter, true)); - } - - void setActive(ListAdapter adapter, boolean isActive) { - for (PieceState state : pieces) { - if (state.adapter==adapter) { - state.isActive=isActive; - active=null; - break; - } - } - } - - void setActive(View v, boolean isActive) { - for (PieceState state : pieces) { - if (state.adapter instanceof SackOfViewsAdapter && - ((SackOfViewsAdapter)state.adapter).hasView(v)) { - state.isActive=isActive; - active=null; - break; - } - } - } - - List getRawPieces() { - return(pieces); - } - - List getPieces() { - if (active == null) { - active=new ArrayList(); - - for (PieceState state : pieces) { - if (state.isActive) { - active.add(state.adapter); - } - } - } - - return(active); - } - } - - private static class EnabledSackAdapter extends SackOfViewsAdapter { - public EnabledSackAdapter(List views) { - super(views); - } - - @Override - public boolean areAllItemsEnabled() { - return(true); - } - - @Override - public boolean isEnabled(int position) { - return(true); - } - } - - private class CascadeDataSetObserver extends DataSetObserver { - @Override - public void onChanged() { - notifyDataSetChanged(); - } - - @Override - public void onInvalidated() { - notifyDataSetInvalidated(); - } - } -} \ No newline at end of file diff --git a/core/src/com/commonsware/cwac/sacklist/SackOfViewsAdapter.java b/core/src/com/commonsware/cwac/sacklist/SackOfViewsAdapter.java deleted file mode 100644 index 2d248e58..00000000 --- a/core/src/com/commonsware/cwac/sacklist/SackOfViewsAdapter.java +++ /dev/null @@ -1,177 +0,0 @@ -/*** - Copyright (c) 2008-2009 CommonsWare, LLC - Portions (c) 2009 Google, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); you may - not use this file except in compliance with the License. You may obtain - a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -package com.commonsware.cwac.sacklist; - -import java.util.ArrayList; -import java.util.List; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; - -/** - * Adapter that simply returns row views from a list. - * - * If you supply a size, you must implement newView(), to - * create a required view. The adapter will then cache these - * views. - * - * If you supply a list of views in the constructor, that - * list will be used directly. If any elements in the list - * are null, then newView() will be called just for those - * slots. - * - * Subclasses may also wish to override areAllItemsEnabled() - * (default: false) and isEnabled() (default: false), if some - * of their rows should be selectable. - * - * It is assumed each view is unique, and therefore will not - * get recycled. - * - * Note that this adapter is not designed for long lists. It - * is more for screens that should behave like a list. This - * is particularly useful if you combine this with other - * adapters (e.g., SectionedAdapter) that might have an - * arbitrary number of rows, so it all appears seamless. - */ -public class SackOfViewsAdapter extends BaseAdapter { - private List views=null; - - /** - * Constructor creating an empty list of views, but with - * a specified count. Subclasses must override newView(). - */ - public SackOfViewsAdapter(int count) { - super(); - - views=new ArrayList(count); - - for (int i=0;i views) { - super(); - - this.views=views; - } - - /** - * Get the data item associated with the specified - * position in the data set. - * @param position Position of the item whose data we want - */ - @Override - public Object getItem(int position) { - return(views.get(position)); - } - - /** - * How many items are in the data set represented by this - * Adapter. - */ - @Override - public int getCount() { - return(views.size()); - } - - /** - * Returns the number of types of Views that will be - * created by getView(). - */ - @Override - public int getViewTypeCount() { - return(getCount()); - } - - /** - * Get the type of View that will be created by getView() - * for the specified item. - * @param position Position of the item whose data we want - */ - @Override - public int getItemViewType(int position) { - return(position); - } - - /** - * Are all items in this ListAdapter enabled? If yes it - * means all items are selectable and clickable. - */ - @Override - public boolean areAllItemsEnabled() { - return(false); - } - - /** - * Returns true if the item at the specified position is - * not a separator. - * @param position Position of the item whose data we want - */ - @Override - public boolean isEnabled(int position) { - return(false); - } - - /** - * Get a View that displays the data at the specified - * position in the data set. - * @param position Position of the item whose data we want - * @param convertView View to recycle, if not null - * @param parent ViewGroup containing the returned View - */ - @Override - public View getView(int position, View convertView, - ViewGroup parent) { - View result=views.get(position); - - if (result==null) { - result=newView(position, parent); - views.set(position, result); - } - - return(result); - } - - /** - * Get the row id associated with the specified position - * in the list. - * @param position Position of the item whose data we want - */ - @Override - public long getItemId(int position) { - return(position); - } - - public boolean hasView(View v) { - return(views.contains(v)); - } - - /** - * Create a new View to go into the list at the specified - * position. - * @param position Position of the item whose data we want - * @param parent ViewGroup containing the returned View - */ - protected View newView(int position, ViewGroup parent) { - throw new RuntimeException("You must override newView()!"); - } -} \ No newline at end of file diff --git a/core/src/org/transdroid/core/gui/DetailsFragment.java b/core/src/org/transdroid/core/gui/DetailsFragment.java index e4083d23..1a90570f 100644 --- a/core/src/org/transdroid/core/gui/DetailsFragment.java +++ b/core/src/org/transdroid/core/gui/DetailsFragment.java @@ -266,7 +266,7 @@ public class DetailsFragment extends SherlockFragment { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { // Show contextual action bar to start/stop/remove/etc. torrents in batch mode - mode.getMenuInflater().inflate(R.menu.fragment_details_file, menu); + mode.getMenuInflater().inflate(R.menu.fragment_details_cab, menu); selectionManagerMode = new SelectionManagerMode(detailsList, R.plurals.navigation_filesselected); selectionManagerMode.setOnlyCheckClass(TorrentFile.class); selectionManagerMode.onCreateActionMode(mode, menu); diff --git a/core/src/org/transdroid/core/gui/lists/DetailsAdapter.java b/core/src/org/transdroid/core/gui/lists/DetailsAdapter.java index 0eea9194..500c8144 100644 --- a/core/src/org/transdroid/core/gui/lists/DetailsAdapter.java +++ b/core/src/org/transdroid/core/gui/lists/DetailsAdapter.java @@ -4,30 +4,29 @@ import java.util.ArrayList; import java.util.List; import org.transdroid.core.R; -import org.transdroid.core.gui.navigation.FilterSeparatorView; import org.transdroid.core.gui.navigation.FilterSeparatorView_; import org.transdroid.daemon.Torrent; import org.transdroid.daemon.TorrentFile; import android.content.Context; +import android.text.util.Linkify; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; -import com.commonsware.cwac.merge.MergeAdapter; - /** * List adapter that holds a header view showing torrent details and show the list list contained by the torrent. * @author Eric Kok */ public class DetailsAdapter extends MergeAdapter { + private ViewHolderAdapter torrentDetailsViewAdapter = null; private TorrentDetailsView torrentDetailsView = null; - private FilterSeparatorView trackersSeparatorView = null; + private ViewHolderAdapter trackersSeparatorAdapter = null; private SimpleListItemAdapter trackersAdapter = null; - private FilterSeparatorView errorsSeparatorView = null; + private ViewHolderAdapter errorsSeparatorAdapter = null; private SimpleListItemAdapter errorsAdapter = null; - private FilterSeparatorView torrentFilesSeparatorView = null; + private ViewHolderAdapter torrentFilesSeparatorAdapter = null; private TorrentFilesAdapter torrentFilesAdapter = null; public DetailsAdapter(Context context) { @@ -36,41 +35,49 @@ public class DetailsAdapter extends MergeAdapter { // Torrent details header torrentDetailsView = TorrentDetailsView_.build(context); - torrentDetailsView.setVisibility(View.GONE); - addView(torrentDetailsView, false); - - // Trackers - trackersSeparatorView = FilterSeparatorView_.build(context).setText(context.getString(R.string.status_trackers)); - trackersSeparatorView.setVisibility(View.GONE); - addView(trackersSeparatorView, false); - this.trackersAdapter = new SimpleListItemAdapter(context, new ArrayList()); - addAdapter(trackersAdapter); - + torrentDetailsViewAdapter = new ViewHolderAdapter(torrentDetailsView); + torrentDetailsViewAdapter.setViewVisibility(View.GONE); + addAdapter(torrentDetailsViewAdapter); + // Tracker errors - errorsSeparatorView = FilterSeparatorView_.build(context).setText(context.getString(R.string.status_errors)); - errorsSeparatorView.setVisibility(View.GONE); - addView(errorsSeparatorView, false); + errorsSeparatorAdapter = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText( + context.getString(R.string.status_errors))); + errorsSeparatorAdapter.setViewEnabled(false); + errorsSeparatorAdapter.setViewVisibility(View.GONE); + addAdapter(errorsSeparatorAdapter); this.errorsAdapter = new SimpleListItemAdapter(context, new ArrayList()); + this.errorsAdapter.setAutoLinkMask(Linkify.WEB_URLS); addAdapter(errorsAdapter); - + + // Trackers + trackersSeparatorAdapter = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText( + context.getString(R.string.status_trackers))); + trackersSeparatorAdapter.setViewEnabled(false); + trackersSeparatorAdapter.setViewVisibility(View.GONE); + addAdapter(trackersSeparatorAdapter); + this.trackersAdapter = new SimpleListItemAdapter(context, new ArrayList()); + addAdapter(trackersAdapter); + // Torrent files - torrentFilesSeparatorView = FilterSeparatorView_.build(context).setText(context.getString(R.string.status_files)); - torrentFilesSeparatorView.setVisibility(View.GONE); - addView(torrentFilesSeparatorView, false); + torrentFilesSeparatorAdapter = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText( + context.getString(R.string.status_files))); + torrentFilesSeparatorAdapter.setViewEnabled(false); + torrentFilesSeparatorAdapter.setViewVisibility(View.GONE); + addAdapter(torrentFilesSeparatorAdapter); this.torrentFilesAdapter = new TorrentFilesAdapter(context, new ArrayList()); addAdapter(torrentFilesAdapter); - + } - + /** * Update the torrent data in the details header of this merge adapter * @param torrent The torrent for which detailed data is shown */ public void updateTorrent(Torrent torrent) { torrentDetailsView.update(torrent); - torrentDetailsView.setVisibility(torrent == null? View.GONE: View.VISIBLE); + torrentDetailsViewAdapter.setViewVisibility(torrent == null ? View.GONE : View.VISIBLE); } - + /** * Update the list of files contained in this torrent * @param torrentFiles The new list of files, or null if the list and header should be hidden @@ -78,10 +85,10 @@ public class DetailsAdapter extends MergeAdapter { public void updateTorrentFiles(List torrentFiles) { if (torrentFiles == null) { torrentFilesAdapter.update(new ArrayList()); - torrentFilesSeparatorView.setVisibility(View.GONE); + torrentFilesSeparatorAdapter.setViewVisibility(View.GONE); } else { torrentFilesAdapter.update(torrentFiles); - torrentFilesSeparatorView.setVisibility(View.VISIBLE); + torrentFilesSeparatorAdapter.setViewVisibility(View.VISIBLE); } } @@ -92,10 +99,10 @@ public class DetailsAdapter extends MergeAdapter { public void updateTrackers(List trackers) { if (trackers == null || trackers.isEmpty()) { trackersAdapter.update(new ArrayList()); - trackersSeparatorView.setVisibility(View.GONE); + trackersSeparatorAdapter.setViewVisibility(View.GONE); } else { trackersAdapter.update(trackers); - trackersSeparatorView.setVisibility(View.VISIBLE); + trackersSeparatorAdapter.setViewVisibility(View.VISIBLE); } } @@ -106,10 +113,10 @@ public class DetailsAdapter extends MergeAdapter { public void updateErrors(List errors) { if (errors == null || errors.isEmpty()) { errorsAdapter.update(new ArrayList()); - errorsSeparatorView.setVisibility(View.GONE); + errorsSeparatorAdapter.setViewVisibility(View.GONE); } else { errorsAdapter.update(errors); - errorsSeparatorView.setVisibility(View.VISIBLE); + errorsSeparatorAdapter.setViewVisibility(View.VISIBLE); } } @@ -122,7 +129,7 @@ public class DetailsAdapter extends MergeAdapter { updateErrors(null); updateTrackers(null); } - + protected static class TorrentFilesAdapter extends BaseAdapter { private final Context context; diff --git a/core/src/org/transdroid/core/gui/lists/MergeAdapter.java b/core/src/org/transdroid/core/gui/lists/MergeAdapter.java new file mode 100644 index 00000000..bf4db11a --- /dev/null +++ b/core/src/org/transdroid/core/gui/lists/MergeAdapter.java @@ -0,0 +1,298 @@ +package org.transdroid.core.gui.lists; + +import java.util.ArrayList; + +import android.database.DataSetObserver; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Adapter; +import android.widget.BaseAdapter; +import android.widget.ListAdapter; +import android.widget.SectionIndexer; +import android.widget.TextView; + +/** + * An adapter that can contain many other adapters and shows them in sequence. Taken from + * http://stackoverflow.com/questions/7964259/android-attaching-multiple-adapters-to-one-adapter and based on the Apache + * 2-licensed CommonsWare MergeAdapter. + * @author Eric Kok + * @author Alex Amiryan + * @author Mark Murphy + */ +public class MergeAdapter extends BaseAdapter implements SectionIndexer { + + protected ArrayList pieces = new ArrayList(); + protected String noItemsText; + + /** + * Stock constructor, simply chaining to the superclass. + */ + public MergeAdapter() { + super(); + } + + /** + * Adds a new adapter to the roster of things to appear in the aggregate list. + * @param adapter Source for row views for this section + */ + public void addAdapter(ListAdapter adapter) { + pieces.add(adapter); + adapter.registerDataSetObserver(new CascadeDataSetObserver()); + } + + /** + * Get the data item associated with the specified position in the data set. + * @param position Position of the item whose data we want + */ + public Object getItem(int position) { + for (ListAdapter piece : pieces) { + int size = piece.getCount(); + + if (position < size) { + return (piece.getItem(position)); + } + + position -= size; + } + + return (null); + } + + public void setNoItemsText(String text) { + noItemsText = text; + } + + /** + * Get the adapter associated with the specified position in the data set. + * @param position Position of the item whose adapter we want + */ + public ListAdapter getAdapter(int position) { + for (ListAdapter piece : pieces) { + int size = piece.getCount(); + + if (position < size) { + return (piece); + } + + position -= size; + } + + return (null); + } + + /** + * How many items are in the data set represented by this {@link Adapter}. + */ + public int getCount() { + int total = 0; + + for (ListAdapter piece : pieces) { + total += piece.getCount(); + } + + if (total == 0 && noItemsText != null) { + total = 1; + } + + return (total); + } + + /** + * Returns the number of types of {@link View}s that will be created by {@link #getView(int, View, ViewGroup)}. + */ + @Override + public int getViewTypeCount() { + int total = 0; + + for (ListAdapter piece : pieces) { + total += piece.getViewTypeCount(); + } + + return (Math.max(total, 1)); // needed for setListAdapter() before + // content add' + } + + /** + * Get the type of {@link View} that will be created by {@link #getView(int, View, ViewGroup)} for the specified item. + * @param position Position of the item whose data we want + */ + @Override + public int getItemViewType(int position) { + int typeOffset = 0; + int result = -1; + + for (ListAdapter piece : pieces) { + int size = piece.getCount(); + + if (position < size) { + result = typeOffset + piece.getItemViewType(position); + break; + } + + position -= size; + typeOffset += piece.getViewTypeCount(); + } + + return (result); + } + + /** + * Are all items in this {@link ListAdapter} enabled? If yes it means all items are selectable and clickable. + */ + @Override + public boolean areAllItemsEnabled() { + return (false); + } + + /** + * Returns true if the item at the specified position is not a separator. + * @param position Position of the item whose data we want + */ + @Override + public boolean isEnabled(int position) { + for (ListAdapter piece : pieces) { + int size = piece.getCount(); + + if (position < size) { + return (piece.isEnabled(position)); + } + + position -= size; + } + + return (false); + } + + /** + * Get a {@link View} that displays the data at the specified position in the data set. + * @param position Position of the item whose data we want + * @param convertView View to recycle, if not null + * @param parent ViewGroup containing the returned View + */ + public View getView(int position, View convertView, ViewGroup parent) { + for (ListAdapter piece : pieces) { + int size = piece.getCount(); + + if (position < size) { + + return (piece.getView(position, convertView, parent)); + } + + position -= size; + } + + if (noItemsText != null) { + TextView text = new TextView(parent.getContext()); + text.setText(noItemsText); + return text; + } + + return (null); + } + + /** + * Get the row id associated with the specified position in the list. + * @param position Position of the item whose data we want + */ + public long getItemId(int position) { + for (ListAdapter piece : pieces) { + int size = piece.getCount(); + + if (position < size) { + return (piece.getItemId(position)); + } + + position -= size; + } + + return (-1); + } + + public final int getPositionForSection(int section) { + int position = 0; + + for (ListAdapter piece : pieces) { + if (piece instanceof SectionIndexer) { + Object[] sections = ((SectionIndexer) piece).getSections(); + int numSections = 0; + + if (sections != null) { + numSections = sections.length; + } + + if (section < numSections) { + return (position + ((SectionIndexer) piece).getPositionForSection(section)); + } else if (sections != null) { + section -= numSections; + } + } + + position += piece.getCount(); + } + + return (0); + } + + public final int getSectionForPosition(int position) { + int section = 0; + + for (ListAdapter piece : pieces) { + int size = piece.getCount(); + + if (position < size) { + if (piece instanceof SectionIndexer) { + return (section + ((SectionIndexer) piece).getSectionForPosition(position)); + } + + return (0); + } else { + if (piece instanceof SectionIndexer) { + Object[] sections = ((SectionIndexer) piece).getSections(); + + if (sections != null) { + section += sections.length; + } + } + } + + position -= size; + } + + return (0); + } + + public final Object[] getSections() { + ArrayList sections = new ArrayList(); + + for (ListAdapter piece : pieces) { + if (piece instanceof SectionIndexer) { + Object[] curSections = ((SectionIndexer) piece).getSections(); + + if (curSections != null) { + for (Object section : curSections) { + sections.add(section); + } + } + } + } + + if (sections.size() == 0) { + return (null); + } + + return (sections.toArray(new Object[0])); + } + + private class CascadeDataSetObserver extends DataSetObserver { + @Override + public void onChanged() { + notifyDataSetChanged(); + } + + @Override + public void onInvalidated() { + notifyDataSetInvalidated(); + } + } + +} \ No newline at end of file diff --git a/core/src/org/transdroid/core/gui/lists/SimpleListItemAdapter.java b/core/src/org/transdroid/core/gui/lists/SimpleListItemAdapter.java index e6b28e54..38251bc9 100644 --- a/core/src/org/transdroid/core/gui/lists/SimpleListItemAdapter.java +++ b/core/src/org/transdroid/core/gui/lists/SimpleListItemAdapter.java @@ -12,6 +12,7 @@ public class SimpleListItemAdapter extends BaseAdapter { private final Context context; private List items; + private int autoLinkMask = 0; public SimpleListItemAdapter(Context context, List items) { this.context = context; @@ -27,6 +28,10 @@ public class SimpleListItemAdapter extends BaseAdapter { notifyDataSetChanged(); } + public void setAutoLinkMask(int autoLinkMask) { + this.autoLinkMask = autoLinkMask; + } + @Override public int getCount() { return items.size(); @@ -50,7 +55,7 @@ public class SimpleListItemAdapter extends BaseAdapter { } else { filterItemView = (SimpleListItemView) convertView; } - filterItemView.bind(getItem(position)); + filterItemView.bind(getItem(position), autoLinkMask); return filterItemView; } diff --git a/core/src/org/transdroid/core/gui/lists/SimpleListItemView.java b/core/src/org/transdroid/core/gui/lists/SimpleListItemView.java index 2caaff61..74601c31 100644 --- a/core/src/org/transdroid/core/gui/lists/SimpleListItemView.java +++ b/core/src/org/transdroid/core/gui/lists/SimpleListItemView.java @@ -21,8 +21,10 @@ public class SimpleListItemView extends FrameLayout { super(context); } - public void bind(SimpleListItem filterItem) { + public void bind(SimpleListItem filterItem, int autoLinkMask) { itemText.setText(filterItem.getName()); + if (autoLinkMask > 0) + itemText.setAutoLinkMask(autoLinkMask); } } diff --git a/core/src/org/transdroid/core/gui/lists/ViewHolderAdapter.java b/core/src/org/transdroid/core/gui/lists/ViewHolderAdapter.java new file mode 100644 index 00000000..626e4f58 --- /dev/null +++ b/core/src/org/transdroid/core/gui/lists/ViewHolderAdapter.java @@ -0,0 +1,107 @@ +package org.transdroid.core.gui.lists; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ListView; + +/** + * A simple wrapper adapter around a single view, typically for use in a {@link MergeAdapter}. Notably, this adapter + * handles the proper showing or hiding of the view according to the contained view's visibility if + * {@link #setViewVisibility(int)} is used on this adapter rather than setting the visibility of the view directly on + * the view object. This is required since otherwise the adapter's consumer (i.e. a {@link ListView}) does not update + * the list row accordingly. Use {@link #setViewEnabled(boolean)} to enable or disable this contained view for user + * interaction. + * @author Eric Kok + */ +public class ViewHolderAdapter extends BaseAdapter { + + private final View view; + + /** + * Instantiates this wrapper adapter with the one and only view to show. It can not be updated and view visibility + * should be set directly on this adapter using {@link #setViewVisibility(int)}. Use + * {@link #setViewEnabled(boolean)} to enable or disable this contained view for user interaction. + * @param view The view that will be wrapper in an adapter to show in a list view + */ + public ViewHolderAdapter(View view) { + this.view = view; + } + + /** + * Sets the visibility on the contained view and notifies consumers of this adapter (i.e. a {@link ListView}) + * accordingly. Use {@link View#GONE} to hide this adapter's view altogether. + * @param visibility The visibility to set on the contained view + */ + public void setViewVisibility(int visibility) { + view.setVisibility(visibility); + notifyDataSetChanged(); + } + + /** + * Sets whether the contained view should be enabled and notifies consumers of this adapter (i.e. a {@link ListView} + * ) accordingly. A contained enabled view allows user interaction (clicks, focus), while a disabled view does not. + * @param enabled Whether the contained view should be enabled + */ + public void setViewEnabled(boolean enabled) { + view.setEnabled(enabled); + notifyDataSetChanged(); + } + + /** + * Returns 1 if the contained view is {@link View#VISIBLE} or {@link View#INVISIBLE}, return 0 if {@link View#GONE}. + */ + @Override + public int getCount() { + return view.getVisibility() == View.VISIBLE ? 1 : 0; + } + + /** + * Always directly returns the single contained view instance. + */ + @Override + public Object getItem(int position) { + return view; + } + + /** + * Always returns the position directly as item id. + */ + @Override + public long getItemId(int position) { + return position; + } + + /** + * Always directly returns the single contained view instance. + */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + return view; + } + + /** + * Always returns true, as there is only one contained item and it is never changed. + */ + @Override + public boolean hasStableIds() { + return true; + } + + /** + * Returns false, as the contained view can still be enabled and disabled. + */ + @Override + public boolean areAllItemsEnabled() { + return false; + } + + /** + * Returns true if the contained view is enabled, returns false otherwise. + */ + @Override + public boolean isEnabled(int position) { + return view.isEnabled(); + } + +} diff --git a/core/src/org/transdroid/core/gui/navigation/FilterListAdapter.java b/core/src/org/transdroid/core/gui/navigation/FilterListAdapter.java index 4f50e910..cc85b8c8 100644 --- a/core/src/org/transdroid/core/gui/navigation/FilterListAdapter.java +++ b/core/src/org/transdroid/core/gui/navigation/FilterListAdapter.java @@ -6,13 +6,13 @@ import org.androidannotations.annotations.EBean; import org.androidannotations.annotations.RootContext; import org.transdroid.core.R; import org.transdroid.core.app.settings.ServerSetting; +import org.transdroid.core.gui.lists.MergeAdapter; +import org.transdroid.core.gui.lists.ViewHolderAdapter; import org.transdroid.core.gui.navigation.StatusType.StatusTypeFilter; import android.content.Context; import android.view.View; -import com.commonsware.cwac.merge.MergeAdapter; - /** * List adapter that holds filter items, that is, servers, view types and labels. A header item is inserted where * appropriate. @@ -26,9 +26,9 @@ public class FilterListAdapter extends MergeAdapter { private FilterListItemAdapter serverItems = null; private FilterListItemAdapter statusTypeItems = null; private FilterListItemAdapter labelItems = null; - protected FilterSeparatorView statusTypeSeparator; - protected FilterSeparatorView labelSeperator; - protected FilterSeparatorView serverSeparator; + protected ViewHolderAdapter statusTypeSeparator; + protected ViewHolderAdapter labelSeperator; + protected ViewHolderAdapter serverSeparator; /** * Update the list of available servers @@ -36,16 +36,17 @@ public class FilterListAdapter extends MergeAdapter { */ public void updateServers(List servers) { if (this.serverItems == null && servers != null) { - serverSeparator = FilterSeparatorView_.build(context).setText(context.getString(R.string.navigation_servers)); - serverSeparator.setVisibility(servers.isEmpty()? View.GONE: View.VISIBLE); - addView(serverSeparator, false); + serverSeparator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText( + context.getString(R.string.navigation_servers))); + serverSeparator.setViewVisibility(servers.isEmpty() ? View.GONE : View.VISIBLE); + addAdapter(serverSeparator); this.serverItems = new FilterListItemAdapter(context, servers); addAdapter(serverItems); } else if (this.serverItems != null && servers != null) { - serverSeparator.setVisibility(servers.isEmpty()? View.GONE: View.VISIBLE); + serverSeparator.setViewVisibility(servers.isEmpty() ? View.GONE : View.VISIBLE); this.serverItems.update(servers); } else { - serverSeparator.setVisibility(View.GONE); + serverSeparator.setViewVisibility(View.GONE); this.serverItems = null; } } @@ -56,17 +57,17 @@ public class FilterListAdapter extends MergeAdapter { */ public void updateStatusTypes(List statusTypes) { if (this.statusTypeItems == null && statusTypes != null) { - statusTypeSeparator = FilterSeparatorView_.build(context).setText( - context.getString(R.string.navigation_status)); - statusTypeSeparator.setVisibility(statusTypes.isEmpty()? View.GONE: View.VISIBLE); - addView(statusTypeSeparator, false); + statusTypeSeparator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText( + context.getString(R.string.navigation_status))); + statusTypeSeparator.setViewVisibility(statusTypes.isEmpty() ? View.GONE : View.VISIBLE); + addAdapter(statusTypeSeparator); this.statusTypeItems = new FilterListItemAdapter(context, statusTypes); addAdapter(statusTypeItems); } else if (this.statusTypeItems != null && statusTypes != null) { - statusTypeSeparator.setVisibility(statusTypes.isEmpty()? View.GONE: View.VISIBLE); + statusTypeSeparator.setViewVisibility(statusTypes.isEmpty() ? View.GONE : View.VISIBLE); this.statusTypeItems.update(statusTypes); } else { - statusTypeSeparator.setVisibility(View.GONE); + statusTypeSeparator.setViewVisibility(View.GONE); this.statusTypeItems = null; } } @@ -77,16 +78,17 @@ public class FilterListAdapter extends MergeAdapter { */ public void updateLabels(List