package tv.emby.embyatv.details;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.v17.leanback.app.BackgroundManager;
import android.support.v17.leanback.app.RowsFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.ClassPresenterSelector;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import mediabrowser.apiinteraction.EmptyResponse;
import mediabrowser.apiinteraction.Response;
import mediabrowser.model.dto.BaseItemDto;
import mediabrowser.model.dto.BaseItemPerson;
import mediabrowser.model.dto.ImageOptions;
import mediabrowser.model.dto.UserItemDataDto;
import mediabrowser.model.entities.ImageType;
import mediabrowser.model.entities.PersonType;
import mediabrowser.model.livetv.ChannelInfoDto;
import mediabrowser.model.livetv.SeriesTimerInfoDto;
import mediabrowser.model.querying.EpisodeQuery;
import mediabrowser.model.querying.ItemFields;
import mediabrowser.model.querying.ItemQuery;
import mediabrowser.model.querying.ItemsResult;
import mediabrowser.model.querying.NextUpQuery;
import mediabrowser.model.querying.SeasonQuery;
import mediabrowser.model.querying.SimilarItemsQuery;
import mediabrowser.model.querying.UpcomingEpisodesQuery;
import tv.emby.embyatv.R;
import tv.emby.embyatv.TvApp;
import tv.emby.embyatv.base.BaseActivity;
import tv.emby.embyatv.imagehandling.PicassoBackgroundManagerTarget;
import tv.emby.embyatv.itemhandling.BaseRowItem;
import tv.emby.embyatv.itemhandling.ItemLauncher;
import tv.emby.embyatv.itemhandling.ItemRowAdapter;
import tv.emby.embyatv.model.ChapterItemInfo;
import tv.emby.embyatv.playback.MediaManager;
import tv.emby.embyatv.playback.PlaybackOverlayActivity;
import tv.emby.embyatv.presentation.CardPresenter;
import tv.emby.embyatv.presentation.MyDetailsOverviewRowPresenter;
import tv.emby.embyatv.querying.QueryType;
import tv.emby.embyatv.querying.SpecialsQuery;
import tv.emby.embyatv.querying.StdItemQuery;
import tv.emby.embyatv.querying.TrailersQuery;
import tv.emby.embyatv.ui.GenreButton;
import tv.emby.embyatv.ui.IRecordingIndicatorView;
import tv.emby.embyatv.ui.ImageButton;
import tv.emby.embyatv.ui.RecordPopup;
import tv.emby.embyatv.util.DelayedMessage;
import tv.emby.embyatv.util.InfoLayoutHelper;
import tv.emby.embyatv.util.KeyProcessor;
import tv.emby.embyatv.util.Utils;
/**
* Created by Eric on 2/19/2015.
*/
public class FullDetailsActivity extends BaseActivity implements IRecordingIndicatorView {
private int BUTTON_SIZE;
private LinearLayout mGenreRow;
private ImageButton mResumeButton;
private ImageButton mPrevButton;
private ImageButton mRecordButton;
private ImageButton mRecSeriesButton;
private ImageButton mWatchedToggleButton;
private Target mBackgroundTarget;
private Drawable mDefaultBackground;
private DisplayMetrics mMetrics;
protected BaseItemDto mProgramInfo;
protected String mItemId;
protected String mChannelId;
protected BaseRowItem mCurrentItem;
private Calendar mLastUpdated;
private String mPrevItemId;
private TextView mTitle;
private RowsFragment mRowsFragment;
private ArrayObjectAdapter mRowsAdapter;
private MyDetailsOverviewRowPresenter mDorPresenter;
private MyDetailsOverviewRow mDetailsOverviewRow;
private TvApp mApplication;
private FullDetailsActivity mActivity;
private Handler mLoopHandler = new Handler();
private Runnable mBackdropLoop;
private Runnable mClockLoop;
public static int BACKDROP_ROTATION_INTERVAL = 8000;
private Typeface roboto;
private BaseItemDto mBaseItem;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_full_details);
BUTTON_SIZE = Utils.convertDpToPixel(this, 35);
mApplication = TvApp.getApplication();
mActivity = this;
roboto = mApplication.getDefaultFont();
mTitle = (TextView) findViewById(R.id.fdTitle);
mTitle.setTypeface(roboto);
mTitle.setShadowLayer(5, 5, 5, Color.BLACK);
mGenreRow = (LinearLayout) findViewById(R.id.fdGenreRow);
BackgroundManager backgroundManager = BackgroundManager.getInstance(this);
backgroundManager.attach(getWindow());
mBackgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
mMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
mRowsFragment = new RowsFragment();
getFragmentManager().beginTransaction().add(R.id.rowsFragment, mRowsFragment).commit();
mRowsFragment.setOnItemViewClickedListener(new ItemViewClickedListener());
mRowsFragment.setOnItemViewSelectedListener(new ItemViewSelectedListener());
mDorPresenter = new MyDetailsOverviewRowPresenter();
mDefaultBackground = getResources().getDrawable(R.drawable.moviebg);
mItemId = getIntent().getStringExtra("ItemId");
mChannelId = getIntent().getStringExtra("ChannelId");
String programJson = getIntent().getStringExtra("ProgramInfo");
if (programJson != null) mProgramInfo = mApplication.getSerializer().DeserializeFromString(programJson, BaseItemDto.class);
loadItem(mItemId);
}
@Override
protected void onResume() {
super.onResume();
startClock();
rotateBackdrops();
//Update information that may have changed - delay slightly to allow changes to take on the server
if (mApplication.getLastPlayback().after(mLastUpdated) && !"MusicArtist".equals(mBaseItem.getType())) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if ("Episode".equals(mBaseItem.getType()) && mApplication.getLastPlayback().after(mLastUpdated) && mApplication.getLastPlayedItem() != null && !mBaseItem.getId().equals(mApplication.getLastPlayedItem().getId()) && "Episode".equals(mApplication.getLastPlayedItem().getType())) {
mApplication.getLogger().Info("Re-loading after new episode playback");
loadItem(mApplication.getLastPlayedItem().getId());
mApplication.setLastPlayedItem(null); //blank this out so a detail screen we back up to doesn't also do this
} else {
mApplication.getLogger().Debug("Updating info after playback");
mApplication.getApiClient().GetItemAsync(mBaseItem.getId(), mApplication.getCurrentUser().getId(), new Response<BaseItemDto>() {
@Override
public void onResponse(BaseItemDto response) {
if (!isFinishing()) {
mBaseItem = response;
if (mResumeButton != null) {
mResumeButton.setVisibility(("Series".equals(mBaseItem.getType()) && ! mBaseItem.getUserData().getPlayed()) || response.getCanResume() ? View.VISIBLE : View.GONE);
}
updatePlayedDate();
updateWatched();
updatePoster();
mLastUpdated = Calendar.getInstance();
}
}
});
}
}
}, 750);
}
}
@Override
protected void onPause() {
super.onPause();
stopClock();
stopRotate();
}
@Override
protected void onStop() {
super.onStop();
stopClock();
stopRotate();
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (mCurrentItem != null) {
return KeyProcessor.HandleKey(keyCode, mCurrentItem, this) || super.onKeyUp(keyCode, event);
} else if ((keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) && Utils.CanPlay(mBaseItem)) {
//default play action
Long pos = mBaseItem.getUserData().getPlaybackPositionTicks() / 10000;
play(mBaseItem, pos.intValue() , false);
return true;
}
return super.onKeyUp(keyCode, event);
}
private void startClock() {
mClockLoop = new Runnable() {
@Override
public void run() {
mDorPresenter.updateEndTime(getEndTime());
mLoopHandler.postDelayed(this, 15000);
}
};
mLoopHandler.postDelayed(mClockLoop, 15000);
}
private void stopClock() {
if (mLoopHandler != null && mClockLoop != null) {
mLoopHandler.removeCallbacks(mClockLoop);
}
}
private static String[] playableTypes = new String[] {"Episode","Movie","Series","Season","Folder","Video","Recording","Program","ChannelVideoItem","Trailer","MusicArtist","Person","MusicVideo"};
private static List<String> playableTypeList = Arrays.asList(playableTypes);
private static String[] directPlayableTypes = new String[] {"Episode","Movie","Video","Recording","Program"};
private static List<String> directPlayableTypeList = Arrays.asList(directPlayableTypes);
private void updatePoster() {
if (isFinishing()) return;
Picasso.with(mActivity)
.load(Utils.getPrimaryImageUrl(mBaseItem, TvApp.getApplication().getApiClient(),false, false, posterHeight))
.skipMemoryCache()
.resize(posterWidth, posterHeight)
.centerInside()
.into(mDorPresenter.getPosterView());
}
private void updateWatched() {
if (mWatchedToggleButton != null && mBaseItem != null && mBaseItem.getUserData() != null && !isFinishing()) {
mWatchedToggleButton.setImageResource(mBaseItem.getUserData().getPlayed() ? R.drawable.redcheck : R.drawable.whitecheck);
}
}
private void loadItem(String id) {
final FullDetailsActivity us = this;
if (mChannelId != null && mProgramInfo == null) {
// if we are displaying a live tv channel - we want to get whatever is showing now on that channel
mApplication.getApiClient().GetLiveTvChannelAsync(mChannelId, TvApp.getApplication().getCurrentUser().getId(), new Response<ChannelInfoDto>() {
@Override
public void onResponse(ChannelInfoDto response) {
mProgramInfo = response.getCurrentProgram();
mItemId = mProgramInfo.getId();
mApplication.getApiClient().GetItemAsync(mItemId, mApplication.getCurrentUser().getId(), new DetailItemLoadResponse(us));
}
});
} else {
mApplication.getApiClient().GetItemAsync(id, mApplication.getCurrentUser().getId(), new DetailItemLoadResponse(this));
}
mLastUpdated = Calendar.getInstance();
}
@Override
public void setRecTimer(String id) {
mProgramInfo.setTimerId(id);
if (mRecordButton != null) mRecordButton.setImageResource(id == null ? R.drawable.recwhite : R.drawable.rec);
}
private int posterWidth;
private int posterHeight;
@Override
public void setRecSeriesTimer(String id) {
mProgramInfo.setSeriesTimerId(id);
if (mRecSeriesButton != null) mRecSeriesButton.setImageResource(id == null ? R.drawable.recserieswhite : R.drawable.recseries);
}
private class BuildDorTask extends AsyncTask<BaseItemDto, Integer, MyDetailsOverviewRow> {
@Override
protected MyDetailsOverviewRow doInBackground(BaseItemDto... params) {
BaseItemDto item = params[0];
// Figure image size
Double aspect = Utils.getImageAspectRatio(item, false);
posterHeight = aspect > 1 ? Utils.convertDpToPixel(mActivity, 170) : Utils.convertDpToPixel(mActivity, 300);
posterWidth = (int)((aspect) * posterHeight);
if (posterHeight < 10) posterWidth = Utils.convertDpToPixel(mActivity, 150); //Guard against zero size images causing picasso to barf
String primaryImageUrl = Utils.getPrimaryImageUrl(mBaseItem, TvApp.getApplication().getApiClient(),false, false, posterHeight);
mDetailsOverviewRow = new MyDetailsOverviewRow(item);
mDetailsOverviewRow.setSummary(item.getOverview());
switch (item.getType()) {
case "Person":
case "MusicArtist":
mDetailsOverviewRow.setSummarySubTitle("");
break;
default:
BaseItemPerson director = Utils.GetFirstPerson(item, PersonType.Director);
if (director != null) {
mDetailsOverviewRow.setSummaryTitle(getString(R.string.lbl_directed_by)+director.getName());
}
mDetailsOverviewRow.setSummarySubTitle(getEndTime());
}
try {
//Main image
Bitmap poster = Picasso.with(mActivity)
.load(primaryImageUrl)
.skipMemoryCache()
.resize(posterWidth, posterHeight)
.centerInside()
.get();
mDetailsOverviewRow.setImageBitmap(mActivity, poster);
//Studio image
int height = Utils.convertDpToPixel(mActivity, 40);
int width = Utils.convertDpToPixel(mActivity, 100);
if (item.getStudios() != null && item.getStudios().length > 0 && item.getStudios()[0].getHasPrimaryImage()) {
String studioImageUrl = Utils.getPrimaryImageUrl(item.getStudios()[0], mApplication.getApiClient(), height);
if (studioImageUrl != null) mDetailsOverviewRow.setStudioBitmap(mActivity, Picasso.with(mActivity).load(studioImageUrl).resize(width, height).centerInside().get());
} else {
if (item.getSeriesStudio() != null) {
String studioImageUrl = null;
try {
ImageOptions options = new ImageOptions();
options.setMaxHeight(height);
options.setImageType(ImageType.Primary);
studioImageUrl = mApplication.getApiClient().GetStudioImageUrl(URLEncoder.encode(item.getSeriesStudio(), "utf-8"), options);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if (studioImageUrl != null) mDetailsOverviewRow.setStudioBitmap(mActivity, Picasso.with(mActivity).load(studioImageUrl).resize(width, height).centerInside().get());
}
}
} catch (IOException e) {
TvApp.getApplication().getLogger().ErrorException("Error loading image", e);
}
return mDetailsOverviewRow;
}
@Override
protected void onPostExecute(MyDetailsOverviewRow detailsOverviewRow) {
super.onPostExecute(detailsOverviewRow);
if (isFinishing()) return;
ClassPresenterSelector ps = new ClassPresenterSelector();
ps.addClassPresenter(MyDetailsOverviewRow.class, mDorPresenter);
ps.addClassPresenter(ListRow.class, new ListRowPresenter());
mRowsAdapter = new ArrayObjectAdapter(ps);
mRowsFragment.setAdapter(mRowsAdapter);
mRowsAdapter.add(detailsOverviewRow);
updateInfo(detailsOverviewRow.getItem());
addAdditionalRows(mRowsAdapter);
}
}
public void setBaseItem(BaseItemDto item) {
mBaseItem = item;
if (mBaseItem != null) {
if (mChannelId != null) {
mBaseItem.setParentId(mChannelId);
mBaseItem.setPremiereDate(mProgramInfo.getStartDate());
mBaseItem.setEndDate(mProgramInfo.getEndDate());
mBaseItem.setRunTimeTicks(mProgramInfo.getRunTimeTicks());
}
new BuildDorTask().execute(item);
}
}
protected void addItemRow(ArrayObjectAdapter parent, ItemRowAdapter row, int index, String headerText) {
HeaderItem header = new HeaderItem(index, headerText);
ListRow listRow = new ListRow(header, row);
parent.add(listRow);
row.setRow(listRow);
row.Retrieve();
}
protected void addAdditionalRows(ArrayObjectAdapter adapter) {
TvApp.getApplication().getLogger().Debug("Item type: "+mBaseItem.getType());
switch (mBaseItem.getType()) {
case "Movie":
//Cast/Crew
if (mBaseItem.getPeople() != null && mBaseItem.getPeople().length > 0) {
ItemRowAdapter castAdapter = new ItemRowAdapter(mBaseItem.getPeople(), new CardPresenter(), adapter);
addItemRow(adapter, castAdapter, 0, mActivity.getString(R.string.lbl_cast_crew));
}
//Specials
if (mBaseItem.getSpecialFeatureCount() != null && mBaseItem.getSpecialFeatureCount() > 0) {
addItemRow(adapter, new ItemRowAdapter(new SpecialsQuery(mBaseItem.getId()), new CardPresenter(), adapter), 2, mActivity.getString(R.string.lbl_specials));
}
//Trailers
if (mBaseItem.getLocalTrailerCount() != null && mBaseItem.getLocalTrailerCount() > 1) {
addItemRow(adapter, new ItemRowAdapter(new TrailersQuery(mBaseItem.getId()), new CardPresenter(), adapter), 3, mActivity.getString(R.string.lbl_trailers));
}
//Chapters
if (mBaseItem.getChapters() != null && mBaseItem.getChapters().size() > 0) {
List<ChapterItemInfo> chapters = Utils.buildChapterItems(mBaseItem);
ItemRowAdapter chapterAdapter = new ItemRowAdapter(chapters, new CardPresenter(), adapter);
addItemRow(adapter, chapterAdapter, 1, mActivity.getString(R.string.lbl_chapters));
}
//Similar
SimilarItemsQuery similar = new SimilarItemsQuery();
similar.setFields(new ItemFields[] {ItemFields.PrimaryImageAspectRatio});
similar.setUserId(TvApp.getApplication().getCurrentUser().getId());
similar.setId(mBaseItem.getId());
similar.setLimit(10);
ItemRowAdapter similarMoviesAdapter = new ItemRowAdapter(similar, QueryType.SimilarMovies, new CardPresenter(), adapter);
addItemRow(adapter, similarMoviesAdapter, 4, mActivity.getString(R.string.lbl_similar_movies));
break;
case "Person":
ItemQuery personMovies = new ItemQuery();
personMovies.setFields(new ItemFields[]{ItemFields.PrimaryImageAspectRatio});
personMovies.setUserId(TvApp.getApplication().getCurrentUser().getId());
personMovies.setPersonIds(new String[] {mBaseItem.getId()});
personMovies.setRecursive(true);
personMovies.setIncludeItemTypes(new String[] {"Movie"});
ItemRowAdapter personMoviesAdapter = new ItemRowAdapter(personMovies, 100, false, new CardPresenter(), adapter);
addItemRow(adapter, personMoviesAdapter, 0, mApplication.getString(R.string.lbl_movies));
ItemQuery personSeries = new ItemQuery();
personSeries.setFields(new ItemFields[]{ItemFields.PrimaryImageAspectRatio, ItemFields.DisplayPreferencesId});
personSeries.setUserId(TvApp.getApplication().getCurrentUser().getId());
personSeries.setPersonIds(new String[] {mBaseItem.getId()});
personSeries.setRecursive(true);
personSeries.setIncludeItemTypes(new String[] {"Series", "Episode"});
ItemRowAdapter personSeriesAdapter = new ItemRowAdapter(personSeries, 100, false, new CardPresenter(), adapter);
addItemRow(adapter, personSeriesAdapter, 1, mApplication.getString(R.string.lbl_tv_series));
break;
case "MusicArtist":
ItemQuery artistAlbums = new ItemQuery();
artistAlbums.setFields(new ItemFields[]{ItemFields.PrimaryImageAspectRatio});
artistAlbums.setUserId(TvApp.getApplication().getCurrentUser().getId());
artistAlbums.setArtistIds(new String[]{mBaseItem.getId()});
artistAlbums.setRecursive(true);
artistAlbums.setIncludeItemTypes(new String[]{"MusicAlbum"});
ItemRowAdapter artistAlbumsAdapter = new ItemRowAdapter(artistAlbums, 100, false, new CardPresenter(), adapter);
addItemRow(adapter, artistAlbumsAdapter, 0, mApplication.getString(R.string.lbl_albums));
break;
case "Series":
NextUpQuery nextUpQuery = new NextUpQuery();
nextUpQuery.setUserId(TvApp.getApplication().getCurrentUser().getId());
nextUpQuery.setSeriesId(mBaseItem.getId());
nextUpQuery.setFields(new ItemFields[]{ItemFields.PrimaryImageAspectRatio});
ItemRowAdapter nextUpAdapter = new ItemRowAdapter(nextUpQuery, false, new CardPresenter(), adapter);
addItemRow(adapter, nextUpAdapter, 0, mApplication.getString(R.string.lbl_next_up));
SeasonQuery seasons = new SeasonQuery();
seasons.setSeriesId(mBaseItem.getId());
seasons.setUserId(TvApp.getApplication().getCurrentUser().getId());
seasons.setFields(new ItemFields[] {ItemFields.PrimaryImageAspectRatio, ItemFields.DisplayPreferencesId});
ItemRowAdapter seasonsAdapter = new ItemRowAdapter(seasons, new CardPresenter(), adapter);
addItemRow(adapter, seasonsAdapter, 1, mActivity.getString(R.string.lbl_seasons));
UpcomingEpisodesQuery upcoming = new UpcomingEpisodesQuery();
upcoming.setUserId(TvApp.getApplication().getCurrentUser().getId());
upcoming.setParentId(mBaseItem.getId());
upcoming.setFields(new ItemFields[]{ItemFields.PrimaryImageAspectRatio});
ItemRowAdapter upcomingAdapter = new ItemRowAdapter(upcoming, new CardPresenter(), adapter);
addItemRow(adapter, upcomingAdapter, 2, mActivity.getString(R.string.lbl_upcoming));
if (mBaseItem.getPeople() != null && mBaseItem.getPeople().length > 0) {
ItemRowAdapter seriesCastAdapter = new ItemRowAdapter(mBaseItem.getPeople(), new CardPresenter(), adapter);
addItemRow(adapter, seriesCastAdapter, 3, mApplication.getString(R.string.lbl_cast_crew));
}
SimilarItemsQuery similarSeries = new SimilarItemsQuery();
similarSeries.setFields(new ItemFields[]{ItemFields.PrimaryImageAspectRatio, ItemFields.DisplayPreferencesId});
similarSeries.setUserId(TvApp.getApplication().getCurrentUser().getId());
similarSeries.setId(mBaseItem.getId());
similarSeries.setLimit(20);
ItemRowAdapter similarAdapter = new ItemRowAdapter(similarSeries, QueryType.SimilarSeries, new CardPresenter(), adapter);
addItemRow(adapter, similarAdapter, 4, mActivity.getString(R.string.lbl_similar_series));
break;
case "Episode":
if (mBaseItem.getSeasonId() != null && mBaseItem.getIndexNumber() != null) {
StdItemQuery nextEpisodes = new StdItemQuery();
nextEpisodes.setParentId(mBaseItem.getSeasonId());
nextEpisodes.setIncludeItemTypes(new String[]{"Episode"});
nextEpisodes.setStartIndex(mBaseItem.getIndexNumber()); // query index is zero-based but episode no is not
nextEpisodes.setLimit(20);
ItemRowAdapter nextAdapter = new ItemRowAdapter(nextEpisodes, 0 , false, true, new CardPresenter(), adapter);
addItemRow(adapter, nextAdapter, 5, "Next Episodes");
}
break;
}
}
private void updateInfo(BaseItemDto item) {
setTitle(item.getName());
if (item.getName().length() > 32) {
// scale down the title so more will fit
mTitle.setTextSize(32);
}
LinearLayout mainInfoRow = (LinearLayout)findViewById(R.id.fdMainInfoRow);
InfoLayoutHelper.addInfoRow(this, item, mainInfoRow, false, false);
addGenres(mGenreRow);
if (playableTypeList.contains(item.getType())) addButtons(BUTTON_SIZE);
// updatePlayedDate();
//
updateBackground(Utils.getBackdropImageUrl(item, TvApp.getApplication().getApiClient(), true));
mLastUpdated = Calendar.getInstance();
}
public void setTitle(String title) {
mTitle.setText(title);
}
private void updatePlayedDate() {
// if (directPlayableTypeList.contains(mBaseItem.getType())) {
// mLastPlayedText.setText(mBaseItem.getUserData() != null && mBaseItem.getUserData().getLastPlayedDate() != null ?
// getString(R.string.lbl_last_played)+ DateUtils.getRelativeTimeSpanString(Utils.convertToLocalDate(mBaseItem.getUserData().getLastPlayedDate()).getTime()).toString()
// : getString(R.string.lbl_never_played));
// } else {
// mLastPlayedText.setText("");
// }
}
private String getEndTime() {
if (mBaseItem != null) {
Long runtime = Utils.NullCoalesce(mBaseItem.getRunTimeTicks(), mBaseItem.getOriginalRunTimeTicks());
if (runtime != null && runtime > 0) {
long endTimeTicks = "Program".equals(mBaseItem.getType()) && mBaseItem.getEndDate() != null ? Utils.convertToLocalDate(mBaseItem.getEndDate()).getTime() : System.currentTimeMillis() + runtime / 10000;
String text = getString(R.string.lbl_runs) + runtime / 600000000 + getString(R.string.lbl_min) + " " + getString(R.string.lbl_ends) + android.text.format.DateFormat.getTimeFormat(this).format(new Date(endTimeTicks));
if (mBaseItem.getCanResume()) {
endTimeTicks = System.currentTimeMillis() + ((runtime - mBaseItem.getUserData().getPlaybackPositionTicks()) / 10000);
text += " ("+android.text.format.DateFormat.getTimeFormat(this).format(new Date(endTimeTicks))+getString(R.string.lbl_if_resumed);
}
return text;
}
}
return "";
}
private void addGenres(LinearLayout layout) {
if (mBaseItem.getGenres() != null && mBaseItem.getGenres().size() > 0) {
boolean first = true;
for (String genre : mBaseItem.getGenres()) {
if (!first) InfoLayoutHelper.addSpacer(this, layout, " / ", 14);
first = false;
layout.addView(new GenreButton(this, roboto, 16, genre, mBaseItem.getType()));
}
}
}
private void addButtons(int buttonSize) {
mResumeButton = new ImageButton(this, R.drawable.resume, buttonSize, "Series".equals(mBaseItem.getType()) ? getString(R.string.lbl_play_next_up) : getString(R.string.lbl_resume), null, new View.OnClickListener() {
@Override
public void onClick(View v) {
if ("Series".equals(mBaseItem.getType())) {
//play next up
NextUpQuery nextUpQuery = new NextUpQuery();
nextUpQuery.setUserId(TvApp.getApplication().getCurrentUser().getId());
nextUpQuery.setSeriesId(mBaseItem.getId());
TvApp.getApplication().getApiClient().GetNextUpEpisodesAsync(nextUpQuery, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult response) {
if (response.getItems().length > 0) {
play(response.getItems()[0], 0 , false);
} else {
Utils.showToast(TvApp.getApplication(), "Unable to find next up episode");
}
}
@Override
public void onError(Exception exception) {
TvApp.getApplication().getLogger().ErrorException("Error playing next up episode", exception);
Utils.showToast(TvApp.getApplication(), getString(R.string.msg_video_playback_error));
}
});
} else {
//resume
Long pos = mBaseItem.getUserData().getPlaybackPositionTicks() / 10000;
play(mBaseItem, pos.intValue(), false);
}
}
});
if (Utils.CanPlay(mBaseItem)) {
mDetailsOverviewRow.addAction(mResumeButton);
mResumeButton.setVisibility(("Series".equals(mBaseItem.getType()) && ! mBaseItem.getUserData().getPlayed()) || mBaseItem.getCanResume() ? View.VISIBLE : View.GONE);
ImageButton play = new ImageButton(this, R.drawable.play, buttonSize, getString(Utils.isLiveTv(mBaseItem) ? R.string.lbl_tune_to_channel : mBaseItem.getIsFolder() ? R.string.lbl_play_all : R.string.lbl_play), null, new View.OnClickListener() {
@Override
public void onClick(View v) {
play(mBaseItem, 0, false);
}
});
mDetailsOverviewRow.addAction(play);
if (!mBaseItem.getIsFolder() && !Utils.isLiveTv(mBaseItem)) {
ImageButton queue = new ImageButton(this, R.drawable.addtoqueue, buttonSize, getString(R.string.lbl_add_to_queue), null, new View.OnClickListener() {
@Override
public void onClick(View v) {
if ("Audio".equals(mBaseItem.getType())) {
MediaManager.addToAudioQueue(Arrays.asList(mBaseItem));
} else {
MediaManager.addToVideoQueue(mBaseItem);
}
}
});
mDetailsOverviewRow.addAction(queue);
}
if (mBaseItem.getIsFolder()) {
ImageButton shuffle = new ImageButton(this, R.drawable.shuffle, buttonSize, getString(R.string.lbl_shuffle_all), null, new View.OnClickListener() {
@Override
public void onClick(View v) {
play(mBaseItem, 0, true);
}
});
mDetailsOverviewRow.addAction(shuffle);
}
if ("MusicArtist".equals(mBaseItem.getType())) {
ImageButton imix = new ImageButton(this, R.drawable.mix, buttonSize, getString(R.string.lbl_instant_mix), null, new View.OnClickListener() {
@Override
public void onClick(View v) {
Utils.Beep();
Utils.playInstantMix(mBaseItem.getId());
}
});
mDetailsOverviewRow.addAction(imix);
}
}
if (mBaseItem.getLocalTrailerCount() != null && mBaseItem.getLocalTrailerCount() > 0) {
ImageButton trailer = new ImageButton(this, R.drawable.trailer, buttonSize, getString(R.string.lbl_play_trailers), null, new View.OnClickListener() {
@Override
public void onClick(View v) {
TvApp.getApplication().getApiClient().GetLocalTrailersAsync(TvApp.getApplication().getCurrentUser().getId(), mBaseItem.getId(), new Response<BaseItemDto[]>() {
@Override
public void onResponse(BaseItemDto[] response) {
play(response, 0, false);
}
@Override
public void onError(Exception exception) {
TvApp.getApplication().getLogger().ErrorException("Error retrieving trailers for playback", exception);
Utils.showToast(mActivity, R.string.msg_video_playback_error);
}
});
}
});
mDetailsOverviewRow.addAction(trailer);
}
if (mProgramInfo != null) {
if (Utils.convertToLocalDate(mBaseItem.getEndDate()).getTime() > System.currentTimeMillis()) {
//Record button
mRecordButton = new ImageButton(this, mProgramInfo.getTimerId() != null ? R.drawable.rec : R.drawable.recwhite, buttonSize, getString(R.string.lbl_record), null, new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mProgramInfo.getTimerId() == null) {
showRecordingOptions(false);
} else {
TvApp.getApplication().getApiClient().CancelLiveTvTimerAsync(mProgramInfo.getTimerId(), new EmptyResponse() {
@Override
public void onResponse() {
setRecTimer(null);
TvApp.getApplication().setLastDeletedItemId(mProgramInfo.getId());
Utils.showToast(mActivity, R.string.msg_recording_cancelled);
}
@Override
public void onError(Exception ex) {
Utils.showToast(mActivity, R.string.msg_unable_to_cancel);
}
});
}
}
});
mDetailsOverviewRow.addAction(mRecordButton);
}
if (mProgramInfo.getIsSeries() != null && mProgramInfo.getIsSeries()) {
mRecSeriesButton= new ImageButton(this, mProgramInfo.getSeriesTimerId() != null ? R.drawable.recseries : R.drawable.recserieswhite, buttonSize, getString(R.string.lbl_record_series), null, new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mProgramInfo.getSeriesTimerId() == null) {
showRecordingOptions(true);
} else {
new AlertDialog.Builder(mActivity)
.setTitle(getString(R.string.lbl_cancel_series))
.setMessage(getString(R.string.msg_cancel_entire_series))
.setNegativeButton(R.string.lbl_no, null)
.setPositiveButton(R.string.lbl_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TvApp.getApplication().getApiClient().CancelLiveTvSeriesTimerAsync(mProgramInfo.getSeriesTimerId(), new EmptyResponse() {
@Override
public void onResponse() {
setRecSeriesTimer(null);
TvApp.getApplication().setLastDeletedItemId(mProgramInfo.getId());
Utils.showToast(mActivity, R.string.msg_recording_cancelled);
}
@Override
public void onError(Exception ex) {
Utils.showToast(mActivity, R.string.msg_unable_to_cancel);
}
});
}
}).show();
}
}
});
mDetailsOverviewRow.addAction(mRecSeriesButton);
}
}
UserItemDataDto userData = mBaseItem.getUserData();
if (userData != null) {
if (!"MusicArtist".equals(mBaseItem.getType()) && !"Person".equals(mBaseItem.getType())) {
mWatchedToggleButton = new ImageButton(this, userData.getPlayed() ? R.drawable.redcheck : R.drawable.whitecheck, buttonSize, getString(R.string.lbl_toggle_watched), null, markWatchedListener);
mDetailsOverviewRow.addAction(mWatchedToggleButton);
}
//Favorite
ImageButton fav = new ImageButton(this, userData.getIsFavorite() ? R.drawable.redheart : R.drawable.whiteheart, buttonSize, getString(R.string.lbl_toggle_favorite), null, new View.OnClickListener() {
@Override
public void onClick(final View v) {
UserItemDataDto data = mBaseItem.getUserData();
mApplication.getApiClient().UpdateFavoriteStatusAsync(mBaseItem.getId(), mApplication.getCurrentUser().getId(), !data.getIsFavorite(), new Response<UserItemDataDto>() {
@Override
public void onResponse(UserItemDataDto response) {
mBaseItem.setUserData(response);
((ImageButton)v).setImageResource(response.getIsFavorite() ? R.drawable.redheart : R.drawable.whiteheart);
TvApp.getApplication().setLastFavoriteUpdate(System.currentTimeMillis());
}
});
}
});
mDetailsOverviewRow.addAction(fav);
}
if ("Episode".equals(mBaseItem.getType()) && mBaseItem.getSeriesId() != null) {
//add the prev button first so it will be there in proper position - we'll show it later if needed
mPrevButton = new ImageButton(this, R.drawable.prev, buttonSize, "Previous Episode", null, new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mPrevItemId != null) {
Intent intent = new Intent(mActivity, FullDetailsActivity.class);
intent.putExtra("ItemId", mPrevItemId);
mActivity.startActivity(intent);
}
}
});
mPrevButton.setVisibility(View.GONE);
mDetailsOverviewRow.addAction(mPrevButton);
//now go get our prev episode id
EpisodeQuery adjacent = new EpisodeQuery();
adjacent.setUserId(TvApp.getApplication().getCurrentUser().getId());
adjacent.setSeriesId(mBaseItem.getSeriesId());
adjacent.setAdjacentTo(mBaseItem.getId());
TvApp.getApplication().getApiClient().GetEpisodesAsync(adjacent, new Response<ItemsResult>() {
@Override
public void onResponse(ItemsResult response) {
if (response.getTotalRecordCount() > 0) {
//Just look at first item - if it isn't us, then it is the prev episode
if (!mBaseItem.getId().equals(response.getItems()[0].getId())) {
mPrevItemId = response.getItems()[0].getId();
mPrevButton.setVisibility(View.VISIBLE);
}
}
}
});
ImageButton series = new ImageButton(this, R.drawable.tvicon, buttonSize, getString(R.string.lbl_goto_series), null, new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(mActivity, FullDetailsActivity.class);
intent.putExtra("ItemId", mBaseItem.getSeriesId());
mActivity.startActivity(intent);
}
});
mDetailsOverviewRow.addAction(series);
}
if (("Recording".equals(mBaseItem.getType()) && TvApp.getApplication().getCurrentUser().getPolicy().getEnableLiveTvManagement() && mBaseItem.getCanDelete()) ||
(("Movie".equals(mBaseItem.getType()) || "Episode".equals(mBaseItem.getType()) || "Video".equals(mBaseItem.getType())) && TvApp.getApplication().getCurrentUser().getPolicy().getEnableContentDeletion())) {
final Activity activity = this;
ImageButton del = new ImageButton(this, R.drawable.trash, buttonSize, getString(R.string.lbl_delete), null, new View.OnClickListener() {
@Override
public void onClick(View v) {
new AlertDialog.Builder(activity)
.setTitle(R.string.lbl_delete)
.setMessage("This will PERMANENTLY DELETE " + mBaseItem.getName() + " from your library. Are you VERY sure?")
.setPositiveButton("Delete", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
final DelayedMessage msg = new DelayedMessage(activity, 150);
TvApp.getApplication().getApiClient().DeleteItem(mBaseItem.getId(), new EmptyResponse() {
@Override
public void onResponse() {
msg.Cancel();
Utils.showToast(activity, mBaseItem.getName() + " Deleted");
TvApp.getApplication().setLastDeletedItemId(mBaseItem.getId());
finish();
}
@Override
public void onError(Exception ex) {
msg.Cancel();
Utils.showToast(activity, ex.getLocalizedMessage());
}
});
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Utils.showToast(activity, "Item NOT Deleted");
}
})
.show();
}
});
mDetailsOverviewRow.addAction(del);
}
}
RecordPopup mRecordPopup;
public void showRecordingOptions(final boolean recordSeries) {
if (mRecordPopup == null) {
int width = Utils.convertDpToPixel(this, 600);
Point size = new Point();
getWindowManager().getDefaultDisplay().getSize(size);
mRecordPopup = new RecordPopup(this, mTitle, (size.x/2) - (width/2), mTitle.getTop(), width);
}
TvApp.getApplication().getApiClient().GetDefaultLiveTvTimerInfo(mProgramInfo.getId(), new Response<SeriesTimerInfoDto>() {
@Override
public void onResponse(SeriesTimerInfoDto response) {
if (recordSeries || Utils.isTrue(mProgramInfo.getIsSports())){
mRecordPopup.setContent(mProgramInfo, response, mActivity, recordSeries);
mRecordPopup.show();
} else {
//just record with defaults
TvApp.getApplication().getApiClient().CreateLiveTvTimerAsync(response, new EmptyResponse() {
@Override
public void onResponse() {
// we have to re-retrieve the program to get the timer id
TvApp.getApplication().getApiClient().GetLiveTvProgramAsync(mProgramInfo.getId(), TvApp.getApplication().getCurrentUser().getId(), new Response<BaseItemDto>() {
@Override
public void onResponse(BaseItemDto response) {
setRecTimer(response.getTimerId());
}
});
Utils.showToast(mActivity, R.string.msg_set_to_record);
}
@Override
public void onError(Exception ex) {
Utils.showToast(mActivity, R.string.msg_unable_to_create_recording);
}
});
}
}
});
}
private final class ItemViewClickedListener implements OnItemViewClickedListener {
@Override
public void onItemClicked(final Presenter.ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
if (!(item instanceof BaseRowItem)) return;
ItemLauncher.launch((BaseRowItem) item, (ItemRowAdapter) ((ListRow)row).getAdapter(), ((BaseRowItem)item).getIndex(), mActivity);
}
}
private final class ItemViewSelectedListener implements OnItemViewSelectedListener {
@Override
public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
if (!(item instanceof BaseRowItem)) {
mCurrentItem = null;
} else {
mCurrentItem = (BaseRowItem)item;
}
}
}
private View.OnClickListener markWatchedListener = new View.OnClickListener() {
@Override
public void onClick(final View v) {
final UserItemDataDto data = mBaseItem.getUserData();
if (mBaseItem.getIsFolder()) {
new AlertDialog.Builder(mActivity)
.setTitle(getString(data.getPlayed() ? R.string.lbl_mark_unplayed : R.string.lbl_mark_played))
.setMessage(getString(data.getPlayed() ? R.string.lbl_confirm_mark_unwatched : R.string.lbl_confirm_mark_watched))
.setNegativeButton(getString(R.string.lbl_no), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}).setPositiveButton(getString(R.string.lbl_yes), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (data.getPlayed()) markUnPlayed(v); else markPlayed(v);
}
}).show();
} else {
if (data.getPlayed()) {
markUnPlayed(v);
} else {
markPlayed(v);
}
}
}
};
private void markPlayed(final View v) {
mApplication.getApiClient().MarkPlayedAsync(mBaseItem.getId(), mApplication.getCurrentUser().getId(), null, new Response<UserItemDataDto>() {
@Override
public void onResponse(UserItemDataDto response) {
mBaseItem.setUserData(response);
((ImageButton) v).setImageResource(R.drawable.redcheck);
//adjust resume
if (mResumeButton != null && !mBaseItem.getCanResume())
mResumeButton.setVisibility(View.GONE);
//force lists to re-fetch
TvApp.getApplication().setLastPlayback(Calendar.getInstance());
}
});
}
private void markUnPlayed(final View v) {
mApplication.getApiClient().MarkUnplayedAsync(mBaseItem.getId(), mApplication.getCurrentUser().getId(), new Response<UserItemDataDto>() {
@Override
public void onResponse(UserItemDataDto response) {
mBaseItem.setUserData(response);
((ImageButton) v).setImageResource(R.drawable.whitecheck);
//adjust resume
if (mResumeButton != null && !mBaseItem.getCanResume())
mResumeButton.setVisibility(View.GONE);
//force lists to re-fetch
TvApp.getApplication().setLastPlayback(Calendar.getInstance());
}
});
}
protected void play(final BaseItemDto item, final int pos, final boolean shuffle) {
final Activity activity = this;
Utils.getItemsToPlay(item, pos == 0 && item.getType().equals("Movie"), shuffle, new Response<List<BaseItemDto>>() {
@Override
public void onResponse(List<BaseItemDto> response) {
if ("MusicArtist".equals(item.getType())) {
MediaManager.playNow(response);
} else {
Intent intent = new Intent(activity, PlaybackOverlayActivity.class);
MediaManager.setCurrentVideoQueue(response);
intent.putExtra("Position", pos);
startActivity(intent);
}
}
});
}
protected void play(final BaseItemDto[] items, final int pos, final boolean shuffle) {
List<BaseItemDto> itemsToPlay = Arrays.asList(items);
Intent intent = new Intent(this, PlaybackOverlayActivity.class);
if (shuffle) Collections.shuffle(itemsToPlay);
MediaManager.setCurrentVideoQueue(itemsToPlay);
intent.putExtra("Position", pos);
startActivity(intent);
}
private void rotateBackdrops() {
mBackdropLoop = new Runnable() {
@Override
public void run() {
updateBackground(Utils.getBackdropImageUrl(mBaseItem, TvApp.getApplication().getApiClient(), true));
mLoopHandler.postDelayed(this, BACKDROP_ROTATION_INTERVAL);
}
};
mLoopHandler.postDelayed(mBackdropLoop, BACKDROP_ROTATION_INTERVAL);
}
private void stopRotate() {
if (mLoopHandler != null && mBackdropLoop != null) {
mLoopHandler.removeCallbacks(mBackdropLoop);
}
}
protected void updateBackground(String url) {
if (url == null) {
BackgroundManager.getInstance(this).setDrawable(mDefaultBackground);
} else {
Picasso.with(this)
.load(url)
.skipMemoryCache()
.resize(mMetrics.widthPixels, mMetrics.heightPixels)
.centerCrop()
.error(mDefaultBackground)
.into(mBackgroundTarget);
}
}
}