1 /***
2 * Ambient - A music player for the Android platform
3 Copyright (C) 2007 Martin Vysny
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package sk.baka.ambient.activity.main.cb;
20
21 import java.util.ArrayList;
22 import java.util.List;
23
24 import sk.baka.ambient.ActionsEnum;
25 import sk.baka.ambient.AmbientApplication;
26 import sk.baka.ambient.R;
27 import sk.baka.ambient.activity.main.AbstractListController;
28 import sk.baka.ambient.collection.CategoryEnum;
29 import sk.baka.ambient.collection.ICollection;
30 import sk.baka.ambient.collection.TrackMetadataBean;
31 import sk.baka.ambient.commons.Interval;
32 import sk.baka.ambient.library.ILibraryListener;
33 import sk.baka.ambient.lrc.lyrdb.LyrdbTrack;
34 import sk.baka.ambient.views.gesturelist.GesturesListView;
35 import android.app.Activity;
36 import android.graphics.Typeface;
37 import android.text.SpannableStringBuilder;
38 import android.text.Spanned;
39 import android.text.style.StyleSpan;
40 import android.view.View;
41 import android.widget.TextView;
42
43 /***
44 * <p>
45 * Controller which displays a collection browser on given list view and allows
46 * user to navigate it using gestures.
47 * </p>
48 * <p>
49 * The controller listens on two actions:
50 * </p>
51 * <li>
52 * <ul>
53 * {@link ActionsEnum#Back} - go back in the menu hierarchy
54 * </ul>
55 * <ul>
56 * {@link ActionsEnum#CollectionYear} to switch year being displayed
57 * </ul>
58 * </li>
59 *
60 * @author Martin Vysny
61 */
62 public abstract class AbstractCollectionController extends
63 AbstractListController implements ILibraryListener {
64
65 /***
66 * The collection to display the data from.
67 */
68 protected final ICollection collection;
69
70 /***
71 * the ID of {@link TextView} which will display current category path.
72 */
73 private final int categoryPathId;
74
75 /***
76 * Creates new controller instance. Subclasses should invoke
77 * {@link #updateData()} in the constructor to update the display.
78 *
79 * @param mainViewId
80 * the view whose visibility is controlled.
81 * @param listViewId
82 * the id of the {@link GesturesListView}
83 * @param mainActivity
84 * the main activity instance.
85 * @param playlistView
86 * the playlist view - target of drag'n'drop operations.
87 * @param collection
88 * the collection to display the data from.
89 * @param categoryPathId
90 * the ID of {@link TextView} which will display current category
91 * path.
92 */
93 public AbstractCollectionController(final int mainViewId, int listViewId,
94 Activity mainActivity, final GesturesListView playlistView,
95 final ICollection collection, final int categoryPathId) {
96 super(mainViewId, listViewId, mainActivity);
97 this.collection = collection;
98 this.categoryPathId = categoryPathId;
99
100 listView.dragDropViews.clear();
101 listView.dragDropViews.add(playlistView);
102 reset();
103 }
104
105 @Override
106 public void destroy() {
107 for (final IContentManager man : managerChain) {
108 man.uninitialize();
109 }
110 managerChain.clear();
111 listView.dragDropViews.clear();
112 super.destroy();
113 }
114
115 /***
116 * Resets the controller and shows the first manager, the
117 * {@link GroupingManager}.
118 */
119 protected final void reset() {
120 for (final IContentManager man : managerChain) {
121 man.uninitialize();
122 }
123 managerChain.clear();
124 final GroupingManager initialManager = new GroupingManager();
125 initialManager.initialize(false, collection);
126 manager = initialManager;
127 managerChain.add(manager);
128 updateData();
129 }
130
131 private volatile IContentManager manager;
132
133 private final List<IContentManager> managerChain = new ArrayList<IContentManager>();
134
135 @Override
136 public final boolean canHighlight() {
137 return manager.canReturnTracks();
138 }
139
140 public void itemActivated(final int index, final Object model) {
141 activateItem(index);
142 }
143
144 /***
145 * If <code>true</code> then we cannot fetch new data, change manager etc.
146 */
147 private volatile boolean fetchingData = false;
148
149 private void activateItem(int index) {
150 if (fetchingData) {
151 return;
152 }
153 final IContentManager newManager = manager.itemActivated(index);
154 if (newManager == null) {
155 return;
156 }
157 fetchData(newManager, 1);
158 }
159
160 /***
161 * Fetches new data asynchronously. When the data is ready the listview is
162 * updated.
163 *
164 * @param newManager
165 * replace current manager with this one.
166 * @param direction
167 * if -1 then going back. If 1 then going forward. If 0 then
168 * refreshing current manager.
169 */
170 private void fetchData(final IContentManager newManager, final int direction) {
171 if (fetchingData) {
172 throw new IllegalStateException();
173 }
174 fetchingData = true;
175 final Runnable fetcher = new Runnable() {
176 public void run() {
177 try {
178 final boolean changed = newManager.initialize(year,
179 collection);
180 if (!changed) {
181 fetchingData = false;
182 return;
183 }
184 if (Thread.currentThread().isInterrupted()) {
185 throw new InterruptedException();
186 }
187 final Runnable updater = new Runnable() {
188 public void run() {
189 if (manager != newManager) {
190 manager.uninitialize();
191 manager = newManager;
192 }
193 int prevIndex = -1;
194 if (direction > 0) {
195 managerChain.add(manager);
196 } else if (direction < 0) {
197 managerChain.remove(managerChain.size() - 1);
198 prevIndex = manager
199 .getIndexOfPreviouslyActivatedItem(managerChain
200 .get(managerChain.size() - 1));
201 managerChain.set(managerChain.size() - 1,
202 manager);
203 }
204 updateData();
205 if (prevIndex >= 0) {
206 listView.setSelection(prevIndex);
207 }
208 fetchingData = false;
209 }
210 };
211 AmbientApplication.getHandler().post(updater);
212 } catch (Exception ex) {
213 fetchingData = false;
214 throw new RuntimeException(ex);
215 }
216 }
217 };
218 final String caption = collection.getName() + ": "
219 + app.getString(R.string.gettingData);
220 final boolean accepted = app.getBackgroundTasks().schedule(fetcher,
221 getClass(), !collection.isLocal(), caption);
222 if (!accepted) {
223 fetchingData = false;
224 return;
225 }
226 }
227
228 @Override
229 public void removeItems(Interval remove) {
230 goBack();
231 }
232
233 private void goBack() {
234 if (fetchingData) {
235 return;
236 }
237 final IContentManager newManager = manager.goBack();
238 if (newManager == null) {
239 return;
240 }
241 fetchData(newManager, -1);
242 }
243
244 /***
245 * Updates the data which is currently being shown.
246 */
247 protected final void refetchData() {
248 fetchData(manager, 0);
249 }
250
251 @Override
252 protected void onAction(ActionsEnum action) {
253 if (action == ActionsEnum.Back) {
254 goBack();
255 return;
256 }
257 if (action == ActionsEnum.CollectionYear) {
258 if (fetchingData) {
259 return;
260 }
261 year = !year;
262 refetchData();
263 return;
264 }
265 super.onAction(action);
266 }
267
268 /***
269 * Returns displayable name of the current category.
270 *
271 * @return displayable name.
272 */
273 private CharSequence getPathName() {
274 final SpannableStringBuilder b = new SpannableStringBuilder();
275 if (managerChain.size() <= 1) {
276 b.append(managerChain.get(0).getSelectedItemName());
277 b.setSpan(new StyleSpan(Typeface.ITALIC), 0, b.length(),
278 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
279 return b;
280 }
281 boolean isFirst = true;
282 for (int i = 1; i < managerChain.size(); i++) {
283 if (isFirst) {
284 isFirst = false;
285 } else {
286 b.append('/');
287 }
288 final String manItem = managerChain.get(i).getSelectedItemName();
289 b.append(manItem);
290 final boolean lastMan = i == managerChain.size() - 1;
291 if (lastMan) {
292 b.setSpan(new StyleSpan(Typeface.ITALIC), b.length()
293 - manItem.length(), b.length(),
294 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
295 }
296 }
297 return b;
298 }
299
300 /***
301 * Show year besides the album name?
302 */
303 private volatile boolean year = false;
304
305 @Override
306 protected final void recomputeListItems() {
307 final List<Object> model = listView.getModel().getModel();
308 model.clear();
309 model.addAll(manager.getDisplayableContent());
310 }
311
312 public void update(GesturesListView listView, View itemView, int index,
313 Object model) {
314 final TextView view = (TextView) itemView;
315 if (listView.getHighlight().contains(index)) {
316 view.setBackgroundColor(highlightColor);
317 } else {
318 view.setBackgroundColor(0);
319 }
320 view.setText((String) model);
321 }
322
323 /***
324 * Updates data shown in the controller.
325 */
326 protected final void updateData() {
327 update(Interval.EMPTY);
328 final TextView text = (TextView) mainActivity
329 .findViewById(categoryPathId);
330 text.setText(getPathName());
331 }
332
333 @Override
334 public final List<TrackMetadataBean> computeTracks(Interval highlight) {
335 try {
336 return manager.getTracks(highlight);
337 } catch (final Exception e) {
338 throw new RuntimeException(e);
339 }
340 }
341
342 @Override
343 public final boolean isComputeTracksLong(Interval interval) {
344 if (manager instanceof TrackManager) {
345 return false;
346 }
347 return !interval.isEmpty();
348 }
349
350 @Override
351 public boolean isComputeTracksOnlineOp(Interval interval) {
352 return !collection.isLocal();
353 }
354
355 @Override
356 public String getHint(Interval highlight) {
357 final int resid;
358 if (manager instanceof TrackManager) {
359 resid = R.string.numTracks;
360 } else if (manager instanceof CategoryManager) {
361 final CategoryEnum cat = ((CategoryManager) manager).getCurrent();
362 switch (cat) {
363 case Album:
364 resid = R.string.numAlbums;
365 break;
366 case Artist:
367 resid = R.string.numArtists;
368 break;
369 case Genre:
370 resid = R.string.numGenres;
371 break;
372 case Title:
373 resid = R.string.numTracks;
374 break;
375 default:
376 throw new IllegalStateException();
377 }
378 } else {
379 throw new IllegalStateException();
380 }
381 return listView.getResources().getString(resid, highlight.length);
382 }
383
384 @Override
385 public final boolean isReadOnly() {
386 return true;
387 }
388
389 @Override
390 public final boolean canComputeItems() {
391 return !(manager instanceof GroupingManager);
392 }
393
394 public void coverLoaded(TrackMetadataBean track) {
395
396 }
397
398 public void libraryUpdate(boolean updateStarted, boolean interrupted,
399 boolean userNotified) {
400 if (!updateStarted) {
401 updateData();
402 }
403 }
404
405 public void lyricsLoaded(TrackMetadataBean track,
406 final List<LyrdbTrack> lyrics) {
407
408 }
409 }