/*******************************************************************************
* Copyright 2012 Crazywater
*
* 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 de.knufficast.ui.main;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import de.knufficast.App;
import de.knufficast.R;
import de.knufficast.events.EpisodeDownloadProgressEvent;
import de.knufficast.events.EpisodeDownloadStateEvent;
import de.knufficast.events.EventBus;
import de.knufficast.events.Listener;
import de.knufficast.events.NewImageEvent;
import de.knufficast.events.PlayerProgressEvent;
import de.knufficast.events.PlayerStateChangeEvent;
import de.knufficast.events.QueueChangedEvent;
import de.knufficast.logic.db.DBEpisode;
import de.knufficast.logic.db.Queue;
import de.knufficast.player.QueuePlayer;
import de.knufficast.ui.BaseFragment;
import de.knufficast.ui.DnDListView;
import de.knufficast.util.TimeUtil;
/**
* Fragment for the "queue" tab in the main window.
*
* @author crazywater
*
*/
public class QueueFragment extends BaseFragment implements
QueueAdapter.Presenter {
private List<DBEpisode> ourQueue = new ArrayList<DBEpisode>();
private QueueAdapter episodesAdapter;
private Presenter presenter;
private EventBus eventBus;
private SeekBar seekBar;
private DnDListView list;
private ImageButton playButton;
private ImageButton rewButton;
private ImageButton ffButton;
private TextView elapsedTime;
private TextView totalTime;
private final TimeUtil timeUtil = new TimeUtil();
private boolean updatingDownloads = true;
private final Listener<QueueChangedEvent> queueUpdateListener = new Listener<QueueChangedEvent>() {
@Override
public void onEvent(QueueChangedEvent event) {
updateQueue();
}
};
private final Listener<PlayerStateChangeEvent> playerStateListener = new Listener<PlayerStateChangeEvent>() {
@Override
public void onEvent(PlayerStateChangeEvent event) {
setPlayButtonPlaying(event.isPlaying());
}
};
private final Listener<PlayerProgressEvent> playerProgressListener = new Listener<PlayerProgressEvent>() {
@Override
public void onEvent(final PlayerProgressEvent event) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
setControlsEnabled(event.getTotal() > 0);
setSeekbar(event.getProgress(), event.getTotal());
}
});
}
};
private final Listener<EpisodeDownloadStateEvent> episodeDownloadStateListener = new Listener<EpisodeDownloadStateEvent>() {
@Override
public void onEvent(EpisodeDownloadStateEvent event) {
updateQueue();
}
};
private final Listener<EpisodeDownloadProgressEvent> episodeDownloadProgressListener = new Listener<EpisodeDownloadProgressEvent>() {
@Override
public void onEvent(EpisodeDownloadProgressEvent event) {
if (updatingDownloads) {
for (DBEpisode ep : ourQueue) {
if (ep.getId() == event.getIdentifier()) {
updateQueue();
break;
}
}
}
}
};
private final Listener<NewImageEvent> newImageListener = new Listener<NewImageEvent>() {
@Override
public void onEvent(NewImageEvent event) {
redrawQueue();
}
};
private final DnDListView.Listener dndListener = new DnDListView.Listener() {
@Override
public void drop(int from, int to) {
presenter.moveEpisode(ourQueue.get(from), to);
}
@Override
public void remove(int which) {
presenter.removeEpisode(ourQueue.get(which));
}
@Override
public void click(int which) {
presenter.episodeClicked(ourQueue.get(which));
}
};
@Override
public int getLayoutId() {
return R.layout.fragment_queue;
}
@Override
public int getTitleId() {
return R.string.title_queue;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
episodesAdapter = new QueueAdapter(getContext(),
R.layout.queue_list_item, ourQueue, this);
eventBus = App.get().getEventBus();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Android way of getting a reference to the presenter
try {
presenter = (Presenter) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement "
+ this.getClass().getName() + ".Presenter");
}
}
@Override
public void onStart() {
super.onStart();
// find views
playButton = findView(R.id.queue_play_button);
rewButton = findView(R.id.queue_rewind_button);
ffButton = findView(R.id.queue_fast_forward_button);
list = findView(R.id.queue_episode_list);
seekBar = findView(R.id.queue_seek_bar);
elapsedTime = findView(R.id.queue_time_elapsed);
totalTime = findView(R.id.queue_time_total);
list.setListener(dndListener);
// add listeners to the eventbus
eventBus.addListener(QueueChangedEvent.class, queueUpdateListener);
eventBus.addListener(PlayerStateChangeEvent.class, playerStateListener);
eventBus.addListener(PlayerProgressEvent.class, playerProgressListener);
eventBus.addListener(EpisodeDownloadStateEvent.class,
episodeDownloadStateListener);
eventBus.addListener(EpisodeDownloadProgressEvent.class,
episodeDownloadProgressListener);
eventBus.addListener(NewImageEvent.class, newImageListener);
// add listeners to user events
playButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
presenter.playClicked();
}
});
ffButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// shortcut for immediate UI response
int setTo = Math.min(seekBar.getMax(), seekBar.getProgress()
+ QueuePlayer.FORWARD_MS);
setSeekbar(setTo, seekBar.getMax());
presenter.fastForwardClicked();
}
});
rewButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// shortcut for immediate UI response
int setTo = Math.max(0, seekBar.getProgress() - QueuePlayer.REWIND_MS);
setSeekbar(setTo, seekBar.getMax());
presenter.rewindClicked();
}
});
seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
if (fromUser) {
// shortcut for immediate UI-response to update elapsed time
elapsedTime.setText(timeUtil.formatTime(progress));
presenter.seekTo(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
// set initial state correctly
updateQueue();
setControlsEnabled(false);
list.setAdapter(episodesAdapter);
setPlayButtonPlaying(App.get().getPlayer().isPlaying());
// also used to request a PlayerProgressEvent
App.get().getPlayer().prepareAsync();
}
/**
* Transforms the button to a pause button (true) or play button (false).
*/
public void setPlayButtonPlaying(boolean playing) {
int id = playing ? android.R.drawable.ic_media_pause
: android.R.drawable.ic_media_play;
playButton.setImageResource(id);
}
/**
* Moves the seekbar to progress/max and updates the total/elapsed time
* displays.
*/
public void setSeekbar(int progress, int max) {
seekBar.setMax(max);
seekBar.setProgress(progress);
elapsedTime.setText(timeUtil.formatTime(progress));
totalTime.setText(timeUtil.formatTime(max));
}
private void redrawQueue() {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
episodesAdapter.notifyDataSetChanged();
}
});
}
@Override
public void onStop() {
super.onStop();
eventBus.removeListener(PlayerStateChangeEvent.class, playerStateListener);
eventBus.removeListener(QueueChangedEvent.class, queueUpdateListener);
eventBus.removeListener(PlayerProgressEvent.class, playerProgressListener);
eventBus.removeListener(EpisodeDownloadStateEvent.class,
episodeDownloadStateListener);
eventBus.removeListener(EpisodeDownloadProgressEvent.class,
episodeDownloadProgressListener);
eventBus.removeListener(NewImageEvent.class, newImageListener);
}
/**
* Re-renders the episodes in the queue.
*/
private void updateQueue() {
Queue queue = App.get().getQueue();
ourQueue.clear();
ourQueue.addAll(queue.asList());
redrawQueue();
}
/**
* Disables or enables the controls (play/pause, seekbar)
*/
private void setControlsEnabled(boolean enabled) {
playButton.setEnabled(enabled);
ffButton.setEnabled(enabled);
rewButton.setEnabled(enabled);
seekBar.setEnabled(enabled);
int visibility = enabled ? View.VISIBLE : View.GONE;
elapsedTime.setVisibility(visibility);
totalTime.setVisibility(visibility);
}
/**
* Presenter interface for the {@link QueueFragment}.
*
* @author crazywater
*
*/
public interface Presenter {
void episodeClicked(DBEpisode episode);
void playClicked();
void rewindClicked();
void fastForwardClicked();
void seekTo(int progress);
void moveEpisode(DBEpisode episode, int to);
void removeEpisode(DBEpisode episode);
}
/*
* Ugly hack to make updating download status work with drag and drop...
* otherwise the redraws screw registering long presses :/
*/
@Override
public boolean onTouch(int position, View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
updatingDownloads = false;
return false;
} else if (event.getAction() == MotionEvent.ACTION_UP
|| event.getAction() == MotionEvent.ACTION_CANCEL) {
updatingDownloads = true;
return false;
}
return false;
}
}