/* * Copyright 2008-2013, ETH Zürich, Samuel Welten, Michael Kuhn, Tobias Langner, * Sandro Affentranger, Lukas Bossard, Michael Grob, Rahul Jain, * Dominic Langenegger, Sonia Mayor Alonso, Roger Odermatt, Tobias Schlueter, * Yannick Stucki, Sebastian Wendland, Samuel Zehnder, Samuel Zihlmann, * Samuel Zweifel * * This file is part of Jukefox. * * Jukefox is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or any later version. Jukefox is * distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * Jukefox. If not, see <http://www.gnu.org/licenses/>. */ package ch.ethz.dcg.pancho3.tablet.presenter.overlay; import java.util.ArrayList; import java.util.Collections; import java.util.List; import android.graphics.Bitmap; import android.graphics.Color; import android.util.SparseBooleanArray; import android.view.ActionMode; import android.view.ActionMode.Callback; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import ch.ethz.dcg.jukefox.commons.utils.Log; import ch.ethz.dcg.jukefox.controller.player.AndroidPlayerController; import ch.ethz.dcg.jukefox.model.collection.BaseAlbum; import ch.ethz.dcg.jukefox.model.collection.BaseArtist; import ch.ethz.dcg.jukefox.model.collection.BaseSong; import ch.ethz.dcg.jukefox.model.collection.PlaylistSong; import ch.ethz.dcg.jukefox.model.collection.PlaylistSong.SongSource; import ch.ethz.dcg.jukefox.model.commons.EmptyPlaylistException; import ch.ethz.dcg.jukefox.model.commons.PlaylistPositionOutOfRangeException; import ch.ethz.dcg.jukefox.playmode.MagicPlayMode; import ch.ethz.dcg.pancho3.R; import ch.ethz.dcg.pancho3.model.JukefoxApplication; import ch.ethz.dcg.pancho3.tablet.I18nManager; import ch.ethz.dcg.pancho3.tablet.interfaces.ISongAdapter; import ch.ethz.dcg.pancho3.tablet.model.DataFetcher.OnDataFetchedListener; import ch.ethz.dcg.pancho3.tablet.presenter.TabletPresenter; import ch.ethz.dcg.pancho3.tablet.view.ViewFactory; /** * An abstract base class for presenters of the overlay fragment. */ public abstract class AbstractOverlayPresenter implements OnDataFetchedListener<List<BaseSong<BaseArtist, BaseAlbum>>>, OnSeekBarChangeListener, Callback { protected final static int DEFAULT_CLOUD_COLOR = Color.GRAY; private static final String TAG = AbstractOverlayPresenter.class.getSimpleName(); // A reference to the main presenter to make explore or map calls. protected final TabletPresenter tabletPresenter; // The playlist controller is needed to add songs to the queue. protected final MagicPlayMode magicPlaylistController; protected final AndroidPlayerController playerController; // An interface to the overlay view. protected final IOverlayView overlay; // Used to inflate menus. protected final ViewFactory viewFactory; protected final I18nManager i18nManager; // The contextual action mode for the current action bar. Reference is kept to finish the mode. protected ActionMode actionMode; // All the songs in this overlay. private List<BaseSong<BaseArtist, BaseAlbum>> allSongs; // The songs currently displayed in this overlay. private List<BaseSong<BaseArtist, BaseAlbum>> selectedSongs; private boolean highlightByDrag; private boolean highlightByCheckedItems; private SparseBooleanArray latestIndices = new SparseBooleanArray(); private boolean allSongsChecked = false; private BaseArtist navigationArtist; private BaseAlbum navigationAlbum; private boolean active = true; /** * The constructor sets the references. */ public AbstractOverlayPresenter(TabletPresenter tabletPresenter, MagicPlayMode magicPlaylistController, IOverlayView overlay, ViewFactory viewFactory, I18nManager i18nManager) { this.tabletPresenter = tabletPresenter; this.magicPlaylistController = magicPlaylistController; this.overlay = overlay; this.viewFactory = viewFactory; this.i18nManager = i18nManager; this.playerController = JukefoxApplication.getPlayerController(); } /** * An interface to the overlay view. */ public static interface IOverlayView { /** * Hides (removes) the overlay. */ void hide(); /** * The overlay displays itself with the given callback for the action * mode. */ void show(Callback callback); /** * Sets a fancy background using this color for the whole view. */ void setBackgroundColor(int color); /** * Initializes the song seekbar which needs to know how many songs there * are for its max value. */ void initializeSongSeekbar(int numberOfSongs); /** * Sets the background image of the center piece of the overlay. */ void setAlbumArt(Bitmap bitmap); ImageView getAlbumArt(); /** * Sets the title of the header item. The header item is an item * representing all the songs displayed. */ void setHeaderItemTitle(String title); void highlight(); void unhighlight(); void showDropArea(); void hideDropArea(); void hideSongbarDescription(); void uncheckAllSongs(); void setHeaderChecked(boolean checked, boolean updateListItems); void setAdapter(ISongAdapter adapter); } public void onHeaderItemClick() { if (!getDisplayedSongs().isEmpty()) { if (allSongsChecked) { overlay.setHeaderChecked(false, true); highlightByCheckedItems = false; } else { overlay.setHeaderChecked(true, true); highlightByCheckedItems = true; } allSongsChecked = !allSongsChecked; updateHighlighting(); } } /** * When this is called, the header item which represents all the songs * starts dragging. */ public boolean onHeaderItemLongClick(View headerItem) { if (!getDisplayedSongs().isEmpty()) { tabletPresenter.getDragManager().startDraggingSongs(headerItem, getDisplayedSongs()); } return true; } /** * Signals that the initialization of the view has finished. Tell it what * album we're displaying. */ public void viewFinishedInit() { tabletPresenter.setCurrentOverlayPresenter(this); } @Override public void onDataFetched(List<BaseSong<BaseArtist, BaseAlbum>> songs) { this.allSongs = songs; this.selectedSongs = songs; displaySongs(songs); overlay.initializeSongSeekbar(songs.size()); overlay.setHeaderItemTitle(i18nManager.getNumberOfSongsText(songs.size())); } /** * Called when the seekbar selecting the amount of songs displayed has * changed. We select a new set of random songs and display it. */ @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { uncheckAllSongs(); this.selectedSongs = selectRandomSongs(progress); displaySongs(selectedSongs); overlay.setHeaderItemTitle(i18nManager.getNumberOfSongsText(progress)); } } /** * Called when the overlay is being hidden: We finish the action mode. */ public void onHideOverlay() { if (actionMode != null) { actionMode.finish(); } } /** * Called when the user clicks an action item in the action bar. */ @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { case R.id.explore: overlay.hide(); tabletPresenter.exploreArtistMaybe(navigationArtist); break; case R.id.map: overlay.hide(); tabletPresenter.mapAlbumMaybe(navigationAlbum); break; } return true; } /** * Called when the user clicks 'Done' in the action bar. We hide the * overlay. */ @Override public void onDestroyActionMode(ActionMode mode) { active = false; overlay.hide(); } protected List<BaseSong<BaseArtist, BaseAlbum>> selectRandomSongs(int number) { List<BaseSong<BaseArtist, BaseAlbum>> randomSongs = new ArrayList<BaseSong<BaseArtist, BaseAlbum>>(allSongs); Collections.shuffle(randomSongs); return randomSongs.subList(0, number); } public void highlight() { highlightByDrag = true; overlay.highlight(); updateHighlighting(); } public void unhighlight(boolean successful) { highlightByDrag = false; overlay.unhighlight(); if (successful) { uncheckAllSongs(); // includes updateHighlighting() } else { updateHighlighting(); } } public void playNext(List<PlaylistSong<BaseArtist, BaseAlbum>> songs) { try { playerController.insertSongsAsNext(songs); } catch (PlaylistPositionOutOfRangeException e) { Log.w(TAG, e); } catch (EmptyPlaylistException e) { Log.w(TAG, e); playerController.appendSongsAtEnd(songs); } } public void playNow(List<PlaylistSong<BaseArtist, BaseAlbum>> songs) { magicPlaylistController.swapPlayingSongWith(playerController, songs); } public void enqueue(List<PlaylistSong<BaseArtist, BaseAlbum>> songs) { playerController.appendSongsAtEnd(songs); } public void playNextClicked() { playNext(getCheckedSongs()); uncheckAllSongs(); } public void playNowClicked() { playNow(getCheckedSongs()); uncheckAllSongs(); } public void enqueueClicked() { enqueue(getCheckedSongs()); uncheckAllSongs(); } @Override public void onStartTrackingTouch(SeekBar seekBar) { overlay.hideSongbarDescription(); } public void onCheckedIndicesChange(int numberOfCheckedItems, SparseBooleanArray indices, int latestChangeIndex) { this.latestIndices = indices; if (numberOfCheckedItems == getDisplayedSongs().size()) { overlay.setHeaderChecked(true, false); allSongsChecked = true; } else { overlay.setHeaderChecked(false, false); allSongsChecked = false; } highlightByCheckedItems = numberOfCheckedItems > 0; updateHighlighting(); } private void updateHighlighting() { if (highlightByDrag || highlightByCheckedItems) { overlay.showDropArea(); } else { overlay.hideDropArea(); } } private ArrayList<PlaylistSong<BaseArtist, BaseAlbum>> getCheckedSongs() { ArrayList<PlaylistSong<BaseArtist, BaseAlbum>> songList = new ArrayList<PlaylistSong<BaseArtist, BaseAlbum>>(); if (allSongsChecked) { for (BaseSong<BaseArtist, BaseAlbum> song : getDisplayedSongs()) { songList.add(new PlaylistSong<BaseArtist, BaseAlbum>(song, SongSource.MANUALLY_SELECTED)); } } else { int count = latestIndices.size(); for (int i = 0; i < count; i++) { int key = latestIndices.keyAt(i); if (latestIndices.get(key)) { songList.add(new PlaylistSong<BaseArtist, BaseAlbum>( getDisplayedSongs().get(key), SongSource.MANUALLY_SELECTED)); } } } return songList; } protected void uncheckAllSongs() { highlightByCheckedItems = false; updateHighlighting(); overlay.uncheckAllSongs(); } protected void showNavigationExploreMap(BaseArtist artist, BaseAlbum album, Menu menu) { this.navigationArtist = artist; this.navigationAlbum = album; viewFactory.inflateMenuExploreMap(menu, artist, album); } protected void showNavigationExplore(BaseArtist artist, Menu menu) { this.navigationArtist = artist; viewFactory.inflateMenuExplore(menu, artist); } protected void showNavigationMap(BaseAlbum album, Menu menu) { this.navigationAlbum = album; viewFactory.inflateMenuMap(menu, album); } /** * If false, this overlay presenter isn't used anymore (and will never be * used again). Its overlay isn't not visible anymore. */ public boolean isActive() { return active; } // Below here are methods to override. protected abstract void displaySongs(List<BaseSong<BaseArtist, BaseAlbum>> songs); public abstract ISongAdapter getSongAdapter(); protected List<BaseSong<BaseArtist, BaseAlbum>> getDisplayedSongs() { return selectedSongs; } // Below here are NOPs. @Override public void onStopTrackingTouch(SeekBar seekBar) { } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } }