OSDN Git Service

Eleven: Add loading dialogs to all fragments
[android-x86/packages-apps-Eleven.git] / src / com / cyngn / eleven / sectionadapter / SectionAdapter.java
1 /*
2  * Copyright (C) 2014 Cyanogen, Inc.
3  */
4 package com.cyngn.eleven.sectionadapter;
5
6 import android.app.Activity;
7 import android.content.Context;
8 import android.view.LayoutInflater;
9 import android.view.View;
10 import android.view.ViewGroup;
11 import android.widget.ArrayAdapter;
12 import android.widget.BaseAdapter;
13 import android.widget.TextView;
14
15 import com.cyngn.eleven.R;
16 import com.cyngn.eleven.ui.MusicHolder;
17 import com.cyngn.eleven.utils.SectionCreatorUtils.Section;
18 import com.cyngn.eleven.utils.SectionCreatorUtils.SectionType;
19 import com.cyngn.eleven.widgets.IPopupMenuCallback;
20
21 import java.util.TreeMap;
22
23 /**
24  * This class wraps an ArrayAdapter that implements BasicAdapter and allows Sections to be inserted
25  * into the list.  This wraps the methods for getting the view/indices and returns the section
26  * heads and if it is an underlying item it flows it through the underlying adapter
27  * @param <TItem> The underlying item that is in the array adapter
28  * @param <TArrayAdapter> the arrayadapter that contains TItem and implements BasicAdapter
29  */
30 public class SectionAdapter<TItem,
31         TArrayAdapter extends ArrayAdapter<TItem> & SectionAdapter.BasicAdapter & IPopupMenuCallback>
32         extends BaseAdapter implements IPopupMenuCallback, IPopupMenuCallback.IListener {
33     /**
34      * Basic interface that the adapters implement
35      */
36     public interface BasicAdapter {
37         public void unload();
38         public void buildCache();
39         public void flush();
40         public int getItemPosition(long id);
41     }
42
43     /**
44      * The underlying adapter to wrap
45      */
46     protected TArrayAdapter mUnderlyingAdapter;
47
48     /**
49      * A map of external position to the Section type and Identifier
50      */
51     protected TreeMap<Integer, Section> mSections;
52
53     protected int mHeaderLayoutId;
54     protected boolean mHeaderEnabled;
55
56     protected int mFooterLayoutId;
57     protected boolean mFooterEnabled;
58
59     /**
60      * Popup menu click listener
61      */
62     protected IListener mListener;
63
64     /**
65      * {@link Context}
66      */
67     protected final Context mContext;
68
69     /**
70      * Creates a SectionAdapter
71      * @param context The {@link Context} to use.
72      * @param underlyingAdapter the underlying adapter to wrap
73      */
74     public SectionAdapter(final Activity context, final TArrayAdapter underlyingAdapter) {
75         mContext = context;
76         mUnderlyingAdapter = underlyingAdapter;
77         mUnderlyingAdapter.setPopupMenuClickedListener(this);
78         mSections = new TreeMap<Integer, Section>();
79         setupHeaderParameters(R.layout.list_header, false);
80         // since we have no good default footer, just re-use the header layout
81         setupFooterParameters(R.layout.list_header, false);
82     }
83
84     /**
85      * Gets the underlying array adapter
86      * @return the underlying array adapter
87      */
88     public TArrayAdapter getUnderlyingAdapter() {
89         return mUnderlyingAdapter;
90     }
91
92     /**
93      * {@inheritDoc}
94      */
95     @Override
96     public View getView(final int position, View convertView, final ViewGroup parent) {
97         if (isSection(position)) {
98             if (convertView == null) {
99                 int layoutId = mHeaderLayoutId;
100                 if (isSectionFooter(position)) {
101                     layoutId = mFooterLayoutId;
102                 }
103
104                 convertView = LayoutInflater.from(mContext).inflate(layoutId, parent, false);
105             }
106
107             TextView title = (TextView)convertView.findViewById(R.id.title);
108             title.setText(mSections.get(position).mIdentifier);
109         } else {
110             convertView = mUnderlyingAdapter.getView(
111                     getInternalPosition(position), convertView, parent);
112
113             Object tag = convertView.getTag();
114             if (tag instanceof MusicHolder) {
115                 MusicHolder holder = (MusicHolder)tag;
116                 View divider = holder.mDivider.get();
117                 if (divider != null) {
118                     // if it is the last item in the list, or it is an item before a section divider
119                     // then hide the divider, otherwise show it
120                     if (position == getCount() - 1 || isSection(position + 1)) {
121                         divider.setVisibility(View.INVISIBLE);
122                     } else {
123                         divider.setVisibility(View.VISIBLE);
124                     }
125                 }
126             }
127         }
128
129         return convertView;
130     }
131
132     /**
133      * Setup the header parameters
134      * @param layoutId the layout id used to inflate
135      * @param enabled whether clicking is enabled on the header
136      */
137     public void setupHeaderParameters(int layoutId, boolean enabled) {
138         mHeaderLayoutId = layoutId;
139         mHeaderEnabled = enabled;
140     }
141
142     /**
143      * Setup the footer parameters
144      * @param layoutId the layout id used to inflate
145      * @param enabled whether clicking is enabled on the footer
146      */
147     public void setupFooterParameters(int layoutId, boolean enabled) {
148         mFooterLayoutId = layoutId;
149         mFooterEnabled = enabled;
150     }
151
152     /**
153      * {@inheritDoc}
154      */
155     @Override
156     public int getCount() {
157         return mSections.size() + mUnderlyingAdapter.getCount();
158     }
159
160     /**
161      * {@inheritDoc}
162      */
163     @Override
164     public Object getItem(int position) {
165         if (isSection(position)) {
166             return mSections.get(position);
167         }
168
169         return mUnderlyingAdapter.getItem(getInternalPosition(position));
170     }
171
172     /**
173      * Gets the underlying adapter's item
174      * @param position position to query for
175      * @return the underlying item or null if a section header is queried
176      */
177     public TItem getTItem(int position) {
178         if (isSection(position)) {
179             return null;
180         }
181
182         return mUnderlyingAdapter.getItem(getInternalPosition(position));
183     }
184
185     /**
186      * {@inheritDoc}
187      */
188     @Override
189     public long getItemId(int position) {
190         return position;
191     }
192
193     /**
194      * {@inheritDoc}
195      */
196     @Override
197     public boolean hasStableIds() {
198         return true;
199     }
200
201     /**
202      * {@inheritDoc}
203      */
204     @Override
205     public int getItemViewType(int position) {
206         if (isSectionHeader(position)) {
207             // use the last view type id as the section header
208             return getViewTypeCount() - 1;
209         } else if (isSectionFooter(position)) {
210             // use the last view type id as the section header
211             return getViewTypeCount() - 2;
212         }
213
214         return mUnderlyingAdapter.getItemViewType(getInternalPosition(position));
215     }
216
217     /**
218      * {@inheritDoc}
219      */
220     @Override
221     public int getViewTypeCount() {
222         // increment view type count by 2 for section headers and section footers
223         return mUnderlyingAdapter.getViewTypeCount() + 2;
224     }
225
226     /**
227      * {@inheritDoc}
228      */
229     @Override
230     public void notifyDataSetChanged() {
231         super.notifyDataSetChanged();
232
233         mUnderlyingAdapter.notifyDataSetChanged();
234     }
235
236     /**
237      * {@inheritDoc}
238      */
239     @Override
240     public void notifyDataSetInvalidated() {
241         super.notifyDataSetInvalidated();
242
243         mUnderlyingAdapter.notifyDataSetInvalidated();
244     }
245
246     /**
247      * {@inheritDoc}
248      */
249     @Override
250     public boolean isEnabled(int position) {
251         if (isSectionHeader(position)) {
252             return mHeaderEnabled;
253         } else if (isSectionFooter(position)) {
254             return mFooterEnabled;
255         }
256
257         return true;
258     }
259
260     /**
261      * {@inheritDoc}
262      */
263     @Override
264     public boolean areAllItemsEnabled() {
265         return false;
266     }
267
268     /**
269      * Determines whether the item at the position is a section header
270      * @param position position in the overall lis
271      * @return true if a section header
272      */
273     public boolean isSectionHeader(int position) {
274         return mSections.containsKey(position) && mSections.get(position).mType == SectionType.Header;
275     }
276
277     /**
278      * Determines whether the item at the position is a section footer
279      * @param position position in the overall lis
280      * @return true if a section footer
281      */
282     public boolean isSectionFooter(int position) {
283         return mSections.containsKey(position) && mSections.get(position).mType == SectionType.Footer;
284     }
285
286     /**
287      * Determines whether the item at the position is a section of some type
288      * @param position position in the overall lis
289      * @return true if the item is a section
290      */
291     public boolean isSection(int position) {
292         return mSections.containsKey(position);
293     }
294
295     /**
296      * Converts the external position to the internal position.  This is needed to determine
297      * the position to pass into the underlying adapter
298      * @param position external position
299      * @return the internal position
300      */
301     public int getInternalPosition(int position) {
302         if (isSection(position)) {
303             return -1;
304         }
305
306         int countSectionHeaders = 0;
307
308         for (Integer sectionPosition : mSections.keySet()) {
309             if (sectionPosition <= position) {
310                 countSectionHeaders++;
311             } else {
312                 break;
313             }
314         }
315
316         return position - countSectionHeaders;
317     }
318
319     /**
320      * Converts the underlaying adapter position to wrapped adapter position
321      * @param internalPosition the position of the underlying adapter
322      * @return the position of the wrapped adapter
323      */
324     public int getExternalPosition(int internalPosition) {
325         int externalPosition = internalPosition;
326         for (Integer sectionPosition : mSections.keySet()) {
327             // because the section headers are tracking the 'merged' lists, we need to keep bumping
328             // our position for each found section header
329             if (sectionPosition <= externalPosition) {
330                 externalPosition++;
331             } else {
332                 break;
333             }
334         }
335
336         return externalPosition;
337     }
338
339     /**
340      * Sets the data on the adapter
341      * @param data data to set
342      */
343     public void setData(SectionListContainer<TItem> data) {
344         mUnderlyingAdapter.unload();
345
346         if (data.mSections == null) {
347             mSections.clear();
348         } else {
349             mSections = data.mSections;
350         }
351
352         mUnderlyingAdapter.addAll(data.mListResults);
353
354         mUnderlyingAdapter.buildCache();
355
356         notifyDataSetChanged();
357     }
358
359     /**
360      * unloads the underlying adapter
361      */
362     public void unload() {
363         mSections.clear();
364         mUnderlyingAdapter.unload();
365         notifyDataSetChanged();
366     }
367
368     /**
369      * flushes the underlying adapter
370      */
371     public void flush() {
372         mUnderlyingAdapter.flush();
373         notifyDataSetChanged();
374     }
375
376     public void clear() {
377         mSections.clear();
378         mUnderlyingAdapter.clear();
379         mSections.clear();
380     }
381
382     /**
383      * Gets the item position for the given identifier
384      * @param identifier used to identify the object
385      * @return item position, or -1 if not found
386      */
387     public int getItemPosition(long identifier) {
388         int internalPosition = mUnderlyingAdapter.getItemPosition(identifier);
389         if (internalPosition >= 0) {
390             return getExternalPosition(internalPosition);
391         }
392
393         return -1;
394     }
395
396     @Override
397     public void setPopupMenuClickedListener(IListener listener) {
398         mListener = listener;
399     }
400
401     @Override
402     public void onPopupMenuClicked(View v, int position) {
403         if (mListener != null) {
404             mListener.onPopupMenuClicked(v, getExternalPosition(position));
405         }
406     }
407 }