View Javadoc

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  		// register the drag'n'drop target.
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 		// do nothing
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 		// do nothing
408 	}
409 }