Browse Source

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.

pull/11/head
Eric Kok 11 years ago
parent
commit
1e08daa28d
  1. 3
      core/res/layout/list_item_simple.xml
  2. 3
      core/res/menu/activity_details.xml
  3. 32
      core/res/menu/activity_torrents.xml
  4. 21
      core/res/menu/fragment_details.xml
  5. 0
      core/res/menu/fragment_details_cab.xml
  6. 481
      core/src/com/commonsware/cwac/merge/MergeAdapter.java
  7. 177
      core/src/com/commonsware/cwac/sacklist/SackOfViewsAdapter.java
  8. 2
      core/src/org/transdroid/core/gui/DetailsFragment.java
  9. 75
      core/src/org/transdroid/core/gui/lists/DetailsAdapter.java
  10. 298
      core/src/org/transdroid/core/gui/lists/MergeAdapter.java
  11. 7
      core/src/org/transdroid/core/gui/lists/SimpleListItemAdapter.java
  12. 4
      core/src/org/transdroid/core/gui/lists/SimpleListItemView.java
  13. 107
      core/src/org/transdroid/core/gui/lists/ViewHolderAdapter.java
  14. 44
      core/src/org/transdroid/core/gui/navigation/FilterListAdapter.java
  15. 2
      core/src/org/transdroid/core/gui/navigation/FilterListDropDownAdapter.java

3
core/res/layout/list_item_simple.xml

@ -12,7 +12,6 @@ @@ -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" />
</FrameLayout>

3
core/res/menu/activity_details.xml

@ -4,6 +4,7 @@ @@ -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"/>
</menu>

32
core/res/menu/activity_torrents.xml

@ -4,7 +4,8 @@ @@ -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">
<menu>
<item android:id="@+id/action_add_fromurl" android:title="@string/action_addfromurl" />
<item android:id="@+id/action_add_fromfile" android:title="@string/action_addfromfile" />
@ -14,33 +15,39 @@ @@ -14,33 +15,39 @@
<item
android:id="@+id/action_search"
android:icon="?attr/ic_action_search"
android:showAsAction="ifRoom"
android:title="@string/action_search"/>
android:showAsAction="collapseActionView|ifRoom"
android:title="@string/action_search"
android:orderInCategory="101"/>
<item
android:id="@+id/action_rss"
android:icon="?attr/ic_action_rss"
android:showAsAction="ifRoom"
android:title="@string/action_rss"/>
android:title="@string/action_rss"
android:orderInCategory="900"/>
<item
android:id="@+id/action_enableturtle"
android:icon="@drawable/ic_action_turtle_disabled"
android:showAsAction="ifRoom"
android:title="@string/action_enableturtle"/>
android:title="@string/action_enableturtle"
android:orderInCategory="901"/>
<item
android:id="@+id/action_disableturtle"
android:showAsAction="ifRoom"
android:icon="@drawable/ic_action_turtle_enabled"
android:title="@string/action_disableturtle"/>
android:title="@string/action_disableturtle"
android:orderInCategory="902"/>
<item
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="903"/>
<item
android:id="@+id/action_sort"
android:icon="?attr/ic_action_sort_by_size"
android:showAsAction="ifRoom"
android:title="@string/action_sort">
android:title="@string/action_sort"
android:orderInCategory="904">
<menu>
<item android:id="@+id/action_sort_byname" android:title="@string/action_sort_alpha" />
<item android:id="@+id/action_sort_status" android:title="@string/action_sort_status" />
@ -53,14 +60,17 @@ @@ -53,14 +60,17 @@
<item
android:id="@+id/action_filter"
android:showAsAction="never"
android:title="@string/action_filter"/>
android:title="@string/action_filter"
android:orderInCategory="905"/>
<item
android:id="@+id/action_help"
android:showAsAction="always"
android:title="@string/action_help"/>
android:title="@string/action_help"
android:orderInCategory="906"/>
<item
android:id="@+id/action_settings"
android:showAsAction="always"
android:title="@string/action_settings"/>
android:title="@string/action_settings"
android:orderInCategory="907"/>
</menu>

21
core/res/menu/fragment_details.xml

@ -4,17 +4,20 @@ @@ -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" />
<item
android:id="@+id/action_pause"
android:icon="?attr/ic_action_pause"
android:showAsAction="always"
android:title="@string/action_pause" />
android:title="@string/action_pause"
android:orderInCategory="201" />
<item
android:id="@+id/action_start"
android:icon="?attr/ic_action_start"
android:showAsAction="ifRoom"
android:title="@string/action_start">
android:title="@string/action_start"
android:orderInCategory="202">
<menu>
<item android:id="@+id/action_start_default" android:title="@string/action_start_default" />
<item android:id="@+id/action_start_forced" android:title="@string/action_start_forced" />
@ -24,12 +27,14 @@ @@ -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" />
<item
android:id="@+id/action_remove"
android:icon="?attr/ic_action_remove"
android:showAsAction="always"
android:title="@string/action_remove">
android:title="@string/action_remove"
android:orderInCategory="204">
<menu>
<item android:id="@+id/action_remove_default" android:title="@string/action_remove_default" />
<item android:id="@+id/action_remove_withdata" android:title="@string/action_remove_withdata" />
@ -39,11 +44,13 @@ @@ -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" />
<item
android:id="@+id/action_updatetrackers"
android:icon="?attr/ic_action_trackers"
android:showAsAction="ifRoom"
android:title="@string/action_updatetrackers" />
android:title="@string/action_updatetrackers"
android:orderInCategory="206" />
</menu>

0
core/res/menu/fragment_details_file.xml → core/res/menu/fragment_details_cab.xml

481
core/src/com/commonsware/cwac/merge/MergeAdapter.java

@ -1,481 +0,0 @@ @@ -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<View> list=new ArrayList<View>(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<View> 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<View> 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<Object> sections=new ArrayList<Object>();
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<ListAdapter> 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<PieceState> pieces=new ArrayList<PieceState>();
protected ArrayList<ListAdapter> 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<PieceState> getRawPieces() {
return(pieces);
}
List<ListAdapter> getPieces() {
if (active == null) {
active=new ArrayList<ListAdapter>();
for (PieceState state : pieces) {
if (state.isActive) {
active.add(state.adapter);
}
}
}
return(active);
}
}
private static class EnabledSackAdapter extends SackOfViewsAdapter {
public EnabledSackAdapter(List<View> 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();
}
}
}

177
core/src/com/commonsware/cwac/sacklist/SackOfViewsAdapter.java

@ -1,177 +0,0 @@ @@ -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<View> 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<View>(count);
for (int i=0;i<count;i++) {
views.add(null);
}
}
/**
* Constructor wrapping a supplied list of views.
* Subclasses must override newView() if any of the elements
* in the list are null.
*/
public SackOfViewsAdapter(List<View> 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()!");
}
}

2
core/src/org/transdroid/core/gui/DetailsFragment.java

@ -266,7 +266,7 @@ public class DetailsFragment extends SherlockFragment { @@ -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);

75
core/src/org/transdroid/core/gui/lists/DetailsAdapter.java

@ -4,30 +4,29 @@ import java.util.ArrayList; @@ -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 { @@ -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<SimpleListItem>());
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<SimpleListItem>());
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<SimpleListItem>());
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<TorrentFile>());
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 { @@ -78,10 +85,10 @@ public class DetailsAdapter extends MergeAdapter {
public void updateTorrentFiles(List<TorrentFile> torrentFiles) {
if (torrentFiles == null) {
torrentFilesAdapter.update(new ArrayList<TorrentFile>());
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 { @@ -92,10 +99,10 @@ public class DetailsAdapter extends MergeAdapter {
public void updateTrackers(List<? extends SimpleListItem> trackers) {
if (trackers == null || trackers.isEmpty()) {
trackersAdapter.update(new ArrayList<SimpleListItemAdapter.SimpleStringItem>());
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 { @@ -106,10 +113,10 @@ public class DetailsAdapter extends MergeAdapter {
public void updateErrors(List<? extends SimpleListItem> errors) {
if (errors == null || errors.isEmpty()) {
errorsAdapter.update(new ArrayList<SimpleListItemAdapter.SimpleStringItem>());
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 { @@ -122,7 +129,7 @@ public class DetailsAdapter extends MergeAdapter {
updateErrors(null);
updateTrackers(null);
}
protected static class TorrentFilesAdapter extends BaseAdapter {
private final Context context;

298
core/src/org/transdroid/core/gui/lists/MergeAdapter.java

@ -0,0 +1,298 @@ @@ -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<ListAdapter> pieces = new ArrayList<ListAdapter>();
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<Object> sections = new ArrayList<Object>();
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();
}
}
}

7
core/src/org/transdroid/core/gui/lists/SimpleListItemAdapter.java

@ -12,6 +12,7 @@ public class SimpleListItemAdapter extends BaseAdapter { @@ -12,6 +12,7 @@ public class SimpleListItemAdapter extends BaseAdapter {
private final Context context;
private List<? extends SimpleListItem> items;
private int autoLinkMask = 0;
public SimpleListItemAdapter(Context context, List<? extends SimpleListItem> items) {
this.context = context;
@ -27,6 +28,10 @@ public class SimpleListItemAdapter extends BaseAdapter { @@ -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 { @@ -50,7 +55,7 @@ public class SimpleListItemAdapter extends BaseAdapter {
} else {
filterItemView = (SimpleListItemView) convertView;
}
filterItemView.bind(getItem(position));
filterItemView.bind(getItem(position), autoLinkMask);
return filterItemView;
}

4
core/src/org/transdroid/core/gui/lists/SimpleListItemView.java

@ -21,8 +21,10 @@ public class SimpleListItemView extends FrameLayout { @@ -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);
}
}

107
core/src/org/transdroid/core/gui/lists/ViewHolderAdapter.java

@ -0,0 +1,107 @@ @@ -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();
}
}

44
core/src/org/transdroid/core/gui/navigation/FilterListAdapter.java

@ -6,13 +6,13 @@ import org.androidannotations.annotations.EBean; @@ -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 { @@ -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 { @@ -36,16 +36,17 @@ public class FilterListAdapter extends MergeAdapter {
*/
public void updateServers(List<ServerSetting> 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 { @@ -56,17 +57,17 @@ public class FilterListAdapter extends MergeAdapter {
*/
public void updateStatusTypes(List<StatusTypeFilter> 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 { @@ -77,16 +78,17 @@ public class FilterListAdapter extends MergeAdapter {
*/
public void updateLabels(List<Label> labels) {
if (this.labelItems == null && labels != null) {
labelSeperator = FilterSeparatorView_.build(context).setText(context.getString(R.string.navigation_labels));
labelSeperator.setVisibility(labels.isEmpty()? View.GONE: View.VISIBLE);
addView(labelSeperator, false);
labelSeperator = new ViewHolderAdapter(FilterSeparatorView_.build(context).setText(
context.getString(R.string.navigation_labels)));
labelSeperator.setViewVisibility(labels.isEmpty() ? View.GONE : View.VISIBLE);
addAdapter(labelSeperator);
this.labelItems = new FilterListItemAdapter(context, labels);
addAdapter(labelItems);
} else if (this.labelItems != null && labels != null) {
labelSeperator.setVisibility(labels.isEmpty()? View.GONE: View.VISIBLE);
labelSeperator.setViewVisibility(labels.isEmpty() ? View.GONE : View.VISIBLE);
this.labelItems.update(labels);
} else {
labelSeperator.setVisibility(View.GONE);
labelSeperator.setViewVisibility(View.GONE);
this.labelItems = null;
}
}

2
core/src/org/transdroid/core/gui/navigation/FilterListDropDownAdapter.java

@ -47,7 +47,7 @@ public class FilterListDropDownAdapter extends FilterListAdapter { @@ -47,7 +47,7 @@ public class FilterListDropDownAdapter extends FilterListAdapter {
}
public void hideServersLabel() {
serverSeparator.setVisibility(View.INVISIBLE);
serverSeparator.setViewVisibility(View.GONE);
notifyDataSetInvalidated();
}

Loading…
Cancel
Save