diff --git a/app/src/main/java/de/nicidienase/chaosflix/DetailsDescriptionPresenter.java b/app/src/main/java/de/nicidienase/chaosflix/DetailsDescriptionPresenter.java new file mode 100644 index 00000000..e4051b82 --- /dev/null +++ b/app/src/main/java/de/nicidienase/chaosflix/DetailsDescriptionPresenter.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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.nicidienase.chaosflix; + +import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter; + +import de.nicidienase.chaosflix.entities.Movie; + +public class DetailsDescriptionPresenter extends AbstractDetailsDescriptionPresenter { + + @Override + protected void onBindDescription(ViewHolder viewHolder, Object item) { + Movie movie = (Movie) item; + + if (movie != null) { + viewHolder.getTitle().setText(movie.getTitle()); + viewHolder.getSubtitle().setText(movie.getStudio()); + viewHolder.getBody().setText(movie.getDescription()); + } + } +} diff --git a/app/src/main/java/de/nicidienase/chaosflix/Utils.java b/app/src/main/java/de/nicidienase/chaosflix/Utils.java new file mode 100644 index 00000000..e7c2976c --- /dev/null +++ b/app/src/main/java/de/nicidienase/chaosflix/Utils.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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.nicidienase.chaosflix; + +import android.content.Context; +import android.graphics.Point; +import android.view.Display; +import android.view.WindowManager; +import android.widget.Toast; + +/** + * A collection of utility methods, all static. + */ +public class Utils { + + /* + * Making sure public utility methods remain static + */ + private Utils() { + } + + /** + * Returns the screen/display size + */ + public static Point getDisplaySize(Context context) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = wm.getDefaultDisplay(); + Point size = new Point(); + display.getSize(size); + return size; + } + + /** + * Shows a (long) toast + */ + public static void showToast(Context context, String msg) { + Toast.makeText(context, msg, Toast.LENGTH_LONG).show(); + } + + /** + * Shows a (long) toast. + */ + public static void showToast(Context context, int resourceId) { + Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG).show(); + } + + public static int convertDpToPixel(Context ctx, int dp) { + float density = ctx.getResources().getDisplayMetrics().density; + return Math.round((float) dp * density); + } + + /** + * Formats time in milliseconds to hh:mm:ss string format. + */ + public static String formatMillis(int millis) { + String result = ""; + int hr = millis / 3600000; + millis %= 3600000; + int min = millis / 60000; + millis %= 60000; + int sec = millis / 1000; + if (hr > 0) { + result += hr + ":"; + } + if (min >= 0) { + if (min > 9) { + result += min + ":"; + } else { + result += "0" + min + ":"; + } + } + if (sec > 9) { + result += sec; + } else { + result += "0" + sec; + } + return result; + } +} diff --git a/app/src/main/java/de/nicidienase/chaosflix/activities/BrowseErrorActivity.java b/app/src/main/java/de/nicidienase/chaosflix/activities/BrowseErrorActivity.java new file mode 100644 index 00000000..f8b813f6 --- /dev/null +++ b/app/src/main/java/de/nicidienase/chaosflix/activities/BrowseErrorActivity.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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.nicidienase.chaosflix.activities; + +import android.app.Activity; +import android.app.Fragment; +import android.os.Bundle; +import android.os.Handler; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ProgressBar; + +import de.nicidienase.chaosflix.fragments.ErrorFragment; +import de.nicidienase.chaosflix.R; + +/* + * BrowseErrorActivity shows how to use ErrorFragment + */ +public class BrowseErrorActivity extends Activity { + private static int TIMER_DELAY = 3000; + private static int SPINNER_WIDTH = 100; + private static int SPINNER_HEIGHT = 100; + + private ErrorFragment mErrorFragment; + private SpinnerFragment mSpinnerFragment; + + /** + * Called when the activity is first created. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + testError(); + } + + private void testError() { + mErrorFragment = new ErrorFragment(); + getFragmentManager().beginTransaction().add(R.id.main_browse_fragment, mErrorFragment).commit(); + + mSpinnerFragment = new SpinnerFragment(); + getFragmentManager().beginTransaction().add(R.id.main_browse_fragment, mSpinnerFragment).commit(); + + final Handler handler = new Handler(); + handler.postDelayed(new Runnable() { + @Override + public void run() { + getFragmentManager().beginTransaction().remove(mSpinnerFragment).commit(); + mErrorFragment.setErrorContent(); + } + }, TIMER_DELAY); + } + + static public class SpinnerFragment extends Fragment { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + ProgressBar progressBar = new ProgressBar(container.getContext()); + if (container instanceof FrameLayout) { + FrameLayout.LayoutParams layoutParams = + new FrameLayout.LayoutParams(SPINNER_WIDTH, SPINNER_HEIGHT, Gravity.CENTER); + progressBar.setLayoutParams(layoutParams); + } + return progressBar; + } + } +} diff --git a/app/src/main/java/de/nicidienase/chaosflix/activities/DetailsActivity.java b/app/src/main/java/de/nicidienase/chaosflix/activities/DetailsActivity.java new file mode 100644 index 00000000..88475f3f --- /dev/null +++ b/app/src/main/java/de/nicidienase/chaosflix/activities/DetailsActivity.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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.nicidienase.chaosflix.activities; + +import android.app.Activity; +import android.os.Bundle; + +import de.nicidienase.chaosflix.R; + +/* + * Details activity class that loads LeanbackDetailsFragment class + */ +public class DetailsActivity extends Activity { + public static final String SHARED_ELEMENT_NAME = "hero"; + public static final String MOVIE = "Movie"; + + /** + * Called when the activity is first created. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_details); + } + +} diff --git a/app/src/main/java/de/nicidienase/chaosflix/activities/PlaybackOverlayActivity.java b/app/src/main/java/de/nicidienase/chaosflix/activities/PlaybackOverlayActivity.java new file mode 100644 index 00000000..60e6b9d6 --- /dev/null +++ b/app/src/main/java/de/nicidienase/chaosflix/activities/PlaybackOverlayActivity.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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.nicidienase.chaosflix.activities; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.media.MediaMetadata; +import android.media.MediaPlayer; +import android.media.session.MediaSession; +import android.media.session.PlaybackState; +import android.net.Uri; +import android.os.Bundle; +import android.view.KeyEvent; +import android.widget.VideoView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; + +import de.nicidienase.chaosflix.R; +import de.nicidienase.chaosflix.entities.Movie; +import de.nicidienase.chaosflix.fragments.PlaybackOverlayFragment; + +/** + * PlaybackOverlayActivity for video playback that loads PlaybackOverlayFragment + */ +public class PlaybackOverlayActivity extends Activity implements + PlaybackOverlayFragment.OnPlayPauseClickedListener { + private static final String TAG = "PlaybackOverlayActivity"; + + private VideoView mVideoView; + private LeanbackPlaybackState mPlaybackState = LeanbackPlaybackState.IDLE; + private MediaSession mSession; + + /** + * Called when the activity is first created. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.playback_controls); + loadViews(); + setupCallbacks(); + mSession = new MediaSession(this, "LeanbackSampleApp"); + mSession.setCallback(new MediaSessionCallback()); + mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | + MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); + + mSession.setActive(true); + } + + @Override + public void onDestroy() { + super.onDestroy(); + mVideoView.suspend(); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + PlaybackOverlayFragment playbackOverlayFragment = (PlaybackOverlayFragment) getFragmentManager().findFragmentById(R.id.playback_controls_fragment); + switch (keyCode) { + case KeyEvent.KEYCODE_MEDIA_PLAY: + playbackOverlayFragment.togglePlayback(false); + return true; + case KeyEvent.KEYCODE_MEDIA_PAUSE: + playbackOverlayFragment.togglePlayback(false); + return true; + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + if (mPlaybackState == LeanbackPlaybackState.PLAYING) { + playbackOverlayFragment.togglePlayback(false); + } else { + playbackOverlayFragment.togglePlayback(true); + } + return true; + default: + return super.onKeyUp(keyCode, event); + } + } + + /** + * Implementation of OnPlayPauseClickedListener + */ + public void onFragmentPlayPause(Movie movie, int position, Boolean playPause) { + mVideoView.setVideoPath(movie.getVideoUrl()); + + if (position == 0 || mPlaybackState == LeanbackPlaybackState.IDLE) { + setupCallbacks(); + mPlaybackState = LeanbackPlaybackState.IDLE; + } + + if (playPause && mPlaybackState != LeanbackPlaybackState.PLAYING) { + mPlaybackState = LeanbackPlaybackState.PLAYING; + if (position > 0) { + mVideoView.seekTo(position); + mVideoView.start(); + } + } else { + mPlaybackState = LeanbackPlaybackState.PAUSED; + mVideoView.pause(); + } + updatePlaybackState(position); + updateMetadata(movie); + } + + private void updatePlaybackState(int position) { + PlaybackState.Builder stateBuilder = new PlaybackState.Builder() + .setActions(getAvailableActions()); + int state = PlaybackState.STATE_PLAYING; + if (mPlaybackState == LeanbackPlaybackState.PAUSED) { + state = PlaybackState.STATE_PAUSED; + } + stateBuilder.setState(state, position, 1.0f); + mSession.setPlaybackState(stateBuilder.build()); + } + + private long getAvailableActions() { + long actions = PlaybackState.ACTION_PLAY | + PlaybackState.ACTION_PLAY_FROM_MEDIA_ID | + PlaybackState.ACTION_PLAY_FROM_SEARCH; + + if (mPlaybackState == LeanbackPlaybackState.PLAYING) { + actions |= PlaybackState.ACTION_PAUSE; + } + + return actions; + } + + private void updateMetadata(final Movie movie) { + final MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder(); + + String title = movie.getTitle().replace("_", " -"); + + metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, title); + metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, + movie.getDescription()); + metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, + movie.getCardImageUrl()); + + // And at minimum the title and artist for legacy support + metadataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, title); + metadataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, movie.getStudio()); + + Glide.with(this) + .load(Uri.parse(movie.getCardImageUrl())) + .asBitmap() + .into(new SimpleTarget(500, 500) { + @Override + public void onResourceReady(Bitmap bitmap, GlideAnimation anim) { + metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART, bitmap); + mSession.setMetadata(metadataBuilder.build()); + } + }); + } + + private void loadViews() { + mVideoView = (VideoView) findViewById(R.id.videoView); + mVideoView.setFocusable(false); + mVideoView.setFocusableInTouchMode(false); + } + + private void setupCallbacks() { + + mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + String msg = ""; + if (extra == MediaPlayer.MEDIA_ERROR_TIMED_OUT) { + msg = getString(R.string.video_error_media_load_timeout); + } else if (what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) { + msg = getString(R.string.video_error_server_inaccessible); + } else { + msg = getString(R.string.video_error_unknown_error); + } + mVideoView.stopPlayback(); + mPlaybackState = LeanbackPlaybackState.IDLE; + return false; + } + }); + + mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + if (mPlaybackState == LeanbackPlaybackState.PLAYING) { + mVideoView.start(); + } + } + }); + + mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + mPlaybackState = LeanbackPlaybackState.IDLE; + } + }); + + } + + @Override + public void onResume() { + super.onResume(); + mSession.setActive(true); + } + + @Override + public void onPause() { + super.onPause(); + if (mVideoView.isPlaying()) { + if (!requestVisibleBehind(true)) { + // Try to play behind launcher, but if it fails, stop playback. + stopPlayback(); + } + } else { + requestVisibleBehind(false); + } + } + + @Override + protected void onStop() { + super.onStop(); + mSession.release(); + } + + + @Override + public void onVisibleBehindCanceled() { + super.onVisibleBehindCanceled(); + } + + private void stopPlayback() { + if (mVideoView != null) { + mVideoView.stopPlayback(); + } + } + + /* + * List of various states that we can be in + */ + public enum LeanbackPlaybackState { + PLAYING, PAUSED, BUFFERING, IDLE + } + + private class MediaSessionCallback extends MediaSession.Callback { + } +} diff --git a/app/src/main/java/de/nicidienase/chaosflix/entities/Movie.java b/app/src/main/java/de/nicidienase/chaosflix/entities/Movie.java new file mode 100644 index 00000000..98b34387 --- /dev/null +++ b/app/src/main/java/de/nicidienase/chaosflix/entities/Movie.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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.nicidienase.chaosflix.entities; + +import android.util.Log; + +import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; + +/* + * Movie class represents video entity with title, description, image thumbs and video url. + * + */ +public class Movie implements Serializable { + static final long serialVersionUID = 727566175075960653L; + private static long count = 0; + private long id; + private String title; + private String description; + private String bgImageUrl; + private String cardImageUrl; + private String videoUrl; + private String studio; + private String category; + + public Movie() { + } + + public static long getCount() { + return count; + } + + public static void incCount() { + count++; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getStudio() { + return studio; + } + + public void setStudio(String studio) { + this.studio = studio; + } + + public String getVideoUrl() { + return videoUrl; + } + + public void setVideoUrl(String videoUrl) { + this.videoUrl = videoUrl; + } + + public String getBackgroundImageUrl() { + return bgImageUrl; + } + + public void setBackgroundImageUrl(String bgImageUrl) { + this.bgImageUrl = bgImageUrl; + } + + public String getCardImageUrl() { + return cardImageUrl; + } + + public void setCardImageUrl(String cardImageUrl) { + this.cardImageUrl = cardImageUrl; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public URI getBackgroundImageURI() { + try { + Log.d("BACK MOVIE: ", bgImageUrl); + return new URI(getBackgroundImageUrl()); + } catch (URISyntaxException e) { + Log.d("URI exception: ", bgImageUrl); + return null; + } + } + + public URI getCardImageURI() { + try { + return new URI(getCardImageUrl()); + } catch (URISyntaxException e) { + return null; + } + } + + @Override + public String toString() { + return "Movie{" + + "id=" + id + + ", title='" + title + '\'' + + ", videoUrl='" + videoUrl + '\'' + + ", backgroundImageUrl='" + bgImageUrl + '\'' + + ", backgroundImageURI='" + getBackgroundImageURI().toString() + '\'' + + ", cardImageUrl='" + cardImageUrl + '\'' + + '}'; + } +} diff --git a/app/src/main/java/de/nicidienase/chaosflix/entities/MovieList.java b/app/src/main/java/de/nicidienase/chaosflix/entities/MovieList.java new file mode 100644 index 00000000..eca3a626 --- /dev/null +++ b/app/src/main/java/de/nicidienase/chaosflix/entities/MovieList.java @@ -0,0 +1,86 @@ +package de.nicidienase.chaosflix.entities; + +import java.util.ArrayList; +import java.util.List; + +public final class MovieList { + public static final String MOVIE_CATEGORY[] = { + "Category Zero", + "Category One", + "Category Two", + "Category Three", + "Category Four", + "Category Five", + }; + + public static List list; + + public static List setupMovies() { + list = new ArrayList(); + String title[] = { + "Zeitgeist 2010_ Year in Review", + "Google Demo Slam_ 20ft Search", + "Introducing Gmail Blue", + "Introducing Google Fiber to the Pole", + "Introducing Google Nose" + }; + + String description = "Fusce id nisi turpis. Praesent viverra bibendum semper. " + + "Donec tristique, orci sed semper lacinia, quam erat rhoncus massa, non congue tellus est " + + "quis tellus. Sed mollis orci venenatis quam scelerisque accumsan. Curabitur a massa sit " + + "amet mi accumsan mollis sed et magna. Vivamus sed aliquam risus. Nulla eget dolor in elit " + + "facilisis mattis. Ut aliquet luctus lacus. Phasellus nec commodo erat. Praesent tempus id " + + "lectus ac scelerisque. Maecenas pretium cursus lectus id volutpat."; + + String videoUrl[] = { + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/Zeitgeist/Zeitgeist%202010_%20Year%20in%20Review.mp4", + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/Demo%20Slam/Google%20Demo%20Slam_%2020ft%20Search.mp4", + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/April%20Fool's%202013/Introducing%20Gmail%20Blue.mp4", + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/April%20Fool's%202013/Introducing%20Google%20Fiber%20to%20the%20Pole.mp4", + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/April%20Fool's%202013/Introducing%20Google%20Nose.mp4" + }; + String bgImageUrl[] = { + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/Zeitgeist/Zeitgeist%202010_%20Year%20in%20Review/bg.jpg", + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/Demo%20Slam/Google%20Demo%20Slam_%2020ft%20Search/bg.jpg", + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/April%20Fool's%202013/Introducing%20Gmail%20Blue/bg.jpg", + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/April%20Fool's%202013/Introducing%20Google%20Fiber%20to%20the%20Pole/bg.jpg", + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/April%20Fool's%202013/Introducing%20Google%20Nose/bg.jpg", + }; + String cardImageUrl[] = { + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/Zeitgeist/Zeitgeist%202010_%20Year%20in%20Review/card.jpg", + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/Demo%20Slam/Google%20Demo%20Slam_%2020ft%20Search/card.jpg", + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/April%20Fool's%202013/Introducing%20Gmail%20Blue/card.jpg", + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/April%20Fool's%202013/Introducing%20Google%20Fiber%20to%20the%20Pole/card.jpg", + "http://commondatastorage.googleapis.com/android-tv/Sample%20videos/April%20Fool's%202013/Introducing%20Google%20Nose/card.jpg" + }; + + list.add(buildMovieInfo("category", title[0], + description, "Studio Zero", videoUrl[0], cardImageUrl[0], bgImageUrl[0])); + list.add(buildMovieInfo("category", title[1], + description, "Studio One", videoUrl[1], cardImageUrl[1], bgImageUrl[1])); + list.add(buildMovieInfo("category", title[2], + description, "Studio Two", videoUrl[2], cardImageUrl[2], bgImageUrl[2])); + list.add(buildMovieInfo("category", title[3], + description, "Studio Three", videoUrl[3], cardImageUrl[3], bgImageUrl[3])); + list.add(buildMovieInfo("category", title[4], + description, "Studio Four", videoUrl[4], cardImageUrl[4], bgImageUrl[4])); + + return list; + } + + private static Movie buildMovieInfo(String category, String title, + String description, String studio, String videoUrl, String cardImageUrl, + String bgImageUrl) { + Movie movie = new Movie(); + movie.setId(Movie.getCount()); + Movie.incCount(); + movie.setTitle(title); + movie.setDescription(description); + movie.setStudio(studio); + movie.setCategory(category); + movie.setCardImageUrl(cardImageUrl); + movie.setBackgroundImageUrl(bgImageUrl); + movie.setVideoUrl(videoUrl); + return movie; + } +} diff --git a/app/src/main/java/de/nicidienase/chaosflix/fragments/ErrorFragment.java b/app/src/main/java/de/nicidienase/chaosflix/fragments/ErrorFragment.java new file mode 100644 index 00000000..be48ac34 --- /dev/null +++ b/app/src/main/java/de/nicidienase/chaosflix/fragments/ErrorFragment.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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.nicidienase.chaosflix.fragments; + +import android.os.Bundle; +import android.util.Log; +import android.view.View; + +import de.nicidienase.chaosflix.R; + +/* + * This class demonstrates how to extend ErrorFragment + */ +public class ErrorFragment extends android.support.v17.leanback.app.ErrorFragment { + private static final String TAG = "ErrorFragment"; + private static final boolean TRANSLUCENT = true; + + @Override + public void onCreate(Bundle savedInstanceState) { + Log.d(TAG, "onCreate"); + super.onCreate(savedInstanceState); + setTitle(getResources().getString(R.string.app_name)); + } + + public void setErrorContent() { + setImageDrawable(getResources().getDrawable(R.drawable.lb_ic_sad_cloud)); + setMessage(getResources().getString(R.string.error_fragment_message)); + setDefaultBackground(TRANSLUCENT); + + setButtonText(getResources().getString(R.string.dismiss_error)); + setButtonClickListener(new View.OnClickListener() { + @Override + public void onClick(View arg0) { + getFragmentManager().beginTransaction().remove(ErrorFragment.this).commit(); + } + }); + } +} diff --git a/app/src/main/java/de/nicidienase/chaosflix/fragments/PlaybackOverlayFragment.java b/app/src/main/java/de/nicidienase/chaosflix/fragments/PlaybackOverlayFragment.java new file mode 100644 index 00000000..63fb7507 --- /dev/null +++ b/app/src/main/java/de/nicidienase/chaosflix/fragments/PlaybackOverlayFragment.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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.nicidienase.chaosflix.fragments; + +import android.app.Activity; + +import android.media.MediaMetadataRetriever; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter; +import android.support.v17.leanback.widget.Action; +import android.support.v17.leanback.widget.ArrayObjectAdapter; +import android.support.v17.leanback.widget.ClassPresenterSelector; +import android.support.v17.leanback.widget.ControlButtonPresenterSelector; +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.OnActionClickedListener; +import android.support.v17.leanback.widget.OnItemViewClickedListener; +import android.support.v17.leanback.widget.OnItemViewSelectedListener; +import android.support.v17.leanback.widget.PlaybackControlsRow; +import android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction; +import android.support.v17.leanback.widget.PlaybackControlsRow.PlayPauseAction; +import android.support.v17.leanback.widget.PlaybackControlsRow.RepeatAction; +import android.support.v17.leanback.widget.PlaybackControlsRow.RewindAction; +import android.support.v17.leanback.widget.PlaybackControlsRow.ShuffleAction; +import android.support.v17.leanback.widget.PlaybackControlsRow.SkipNextAction; +import android.support.v17.leanback.widget.PlaybackControlsRow.SkipPreviousAction; +import android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsDownAction; +import android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsUpAction; +import android.support.v17.leanback.widget.PlaybackControlsRowPresenter; +import android.support.v17.leanback.widget.Presenter; +import android.support.v17.leanback.widget.Row; +import android.support.v17.leanback.widget.RowPresenter; +import android.util.Log; +import android.widget.Toast; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import de.nicidienase.chaosflix.CardPresenter; +import de.nicidienase.chaosflix.R; +import de.nicidienase.chaosflix.activities.DetailsActivity; +import de.nicidienase.chaosflix.entities.Movie; +import de.nicidienase.chaosflix.entities.MovieList; + +/* + * Class for video playback with media control + */ +public class PlaybackOverlayFragment extends android.support.v17.leanback.app.PlaybackOverlayFragment { + private static final String TAG = "PlaybackControlsFragmnt"; + + private static final boolean SHOW_DETAIL = true; + private static final boolean HIDE_MORE_ACTIONS = false; + private static final int PRIMARY_CONTROLS = 5; + private static final boolean SHOW_IMAGE = PRIMARY_CONTROLS <= 5; + private static final int BACKGROUND_TYPE = PlaybackOverlayFragment.BG_LIGHT; + private static final int CARD_WIDTH = 200; + private static final int CARD_HEIGHT = 240; + private static final int DEFAULT_UPDATE_PERIOD = 1000; + private static final int UPDATE_PERIOD = 16; + private static final int SIMULATED_BUFFERED_TIME = 10000; + + private ArrayObjectAdapter mRowsAdapter; + private ArrayObjectAdapter mPrimaryActionsAdapter; + private ArrayObjectAdapter mSecondaryActionsAdapter; + private PlayPauseAction mPlayPauseAction; + private RepeatAction mRepeatAction; + private ThumbsUpAction mThumbsUpAction; + private ThumbsDownAction mThumbsDownAction; + private ShuffleAction mShuffleAction; + private FastForwardAction mFastForwardAction; + private RewindAction mRewindAction; + private SkipNextAction mSkipNextAction; + private SkipPreviousAction mSkipPreviousAction; + private PlaybackControlsRow mPlaybackControlsRow; + private ArrayList mItems = new ArrayList(); + private int mCurrentItem; + private Handler mHandler; + private Runnable mRunnable; + private Movie mSelectedMovie; + + private OnPlayPauseClickedListener mCallback; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mItems = new ArrayList(); + mSelectedMovie = (Movie) getActivity() + .getIntent().getSerializableExtra(DetailsActivity.MOVIE); + + List movies = MovieList.list; + + for (int j = 0; j < movies.size(); j++) { + mItems.add(movies.get(j)); + if (mSelectedMovie.getTitle().contentEquals(movies.get(j).getTitle())) { + mCurrentItem = j; + } + } + + mHandler = new Handler(); + + setBackgroundType(BACKGROUND_TYPE); + setFadingEnabled(false); + + setupRows(); + + setOnItemViewSelectedListener(new OnItemViewSelectedListener() { + @Override + public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, + RowPresenter.ViewHolder rowViewHolder, Row row) { + Log.i(TAG, "onItemSelected: " + item + " row " + row); + } + }); + setOnItemViewClickedListener(new OnItemViewClickedListener() { + @Override + public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, + RowPresenter.ViewHolder rowViewHolder, Row row) { + Log.i(TAG, "onItemClicked: " + item + " row " + row); + } + }); + } + + @SuppressWarnings("deprecation") + @Override + public void onAttach(Activity context) { + super.onAttach(context); + if (context instanceof OnPlayPauseClickedListener) { + mCallback = (OnPlayPauseClickedListener) context; + } else { + throw new RuntimeException(context.toString() + + " must implement OnPlayPauseClickedListener"); + } + } + + private void setupRows() { + + ClassPresenterSelector ps = new ClassPresenterSelector(); + + PlaybackControlsRowPresenter playbackControlsRowPresenter; + if (SHOW_DETAIL) { + playbackControlsRowPresenter = new PlaybackControlsRowPresenter( + new DescriptionPresenter()); + } else { + playbackControlsRowPresenter = new PlaybackControlsRowPresenter(); + } + playbackControlsRowPresenter.setOnActionClickedListener(new OnActionClickedListener() { + public void onActionClicked(Action action) { + if (action.getId() == mPlayPauseAction.getId()) { + togglePlayback(mPlayPauseAction.getIndex() == PlayPauseAction.PLAY); + } else if (action.getId() == mSkipNextAction.getId()) { + next(); + } else if (action.getId() == mSkipPreviousAction.getId()) { + prev(); + } else if (action.getId() == mFastForwardAction.getId()) { + Toast.makeText(getActivity(), "TODO: Fast Forward", Toast.LENGTH_SHORT).show(); + } else if (action.getId() == mRewindAction.getId()) { + Toast.makeText(getActivity(), "TODO: Rewind", Toast.LENGTH_SHORT).show(); + } + if (action instanceof PlaybackControlsRow.MultiAction) { + ((PlaybackControlsRow.MultiAction) action).nextIndex(); + notifyChanged(action); + } + } + }); + playbackControlsRowPresenter.setSecondaryActionsHidden(HIDE_MORE_ACTIONS); + + ps.addClassPresenter(PlaybackControlsRow.class, playbackControlsRowPresenter); + ps.addClassPresenter(ListRow.class, new ListRowPresenter()); + mRowsAdapter = new ArrayObjectAdapter(ps); + + addPlaybackControlsRow(); + addOtherRows(); + + setAdapter(mRowsAdapter); + } + + public void togglePlayback(boolean playPause) { + if (playPause) { + startProgressAutomation(); + setFadingEnabled(true); + mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), + mPlaybackControlsRow.getCurrentTime(), true); + mPlayPauseAction.setIcon(mPlayPauseAction.getDrawable(PlayPauseAction.PAUSE)); + } else { + stopProgressAutomation(); + setFadingEnabled(false); + mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), + mPlaybackControlsRow.getCurrentTime(), false); + mPlayPauseAction.setIcon(mPlayPauseAction.getDrawable(PlayPauseAction.PLAY)); + } + notifyChanged(mPlayPauseAction); + } + + private int getDuration() { + Movie movie = mItems.get(mCurrentItem); + MediaMetadataRetriever mmr = new MediaMetadataRetriever(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + mmr.setDataSource(movie.getVideoUrl(), new HashMap()); + } else { + mmr.setDataSource(movie.getVideoUrl()); + } + String time = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); + long duration = Long.parseLong(time); + return (int) duration; + } + + private void addPlaybackControlsRow() { + if (SHOW_DETAIL) { + mPlaybackControlsRow = new PlaybackControlsRow(mSelectedMovie); + } else { + mPlaybackControlsRow = new PlaybackControlsRow(); + } + mRowsAdapter.add(mPlaybackControlsRow); + + updatePlaybackRow(mCurrentItem); + + ControlButtonPresenterSelector presenterSelector = new ControlButtonPresenterSelector(); + mPrimaryActionsAdapter = new ArrayObjectAdapter(presenterSelector); + mSecondaryActionsAdapter = new ArrayObjectAdapter(presenterSelector); + mPlaybackControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter); + mPlaybackControlsRow.setSecondaryActionsAdapter(mSecondaryActionsAdapter); + + mPlayPauseAction = new PlayPauseAction(getActivity()); + mRepeatAction = new RepeatAction(getActivity()); + mThumbsUpAction = new ThumbsUpAction(getActivity()); + mThumbsDownAction = new ThumbsDownAction(getActivity()); + mShuffleAction = new ShuffleAction(getActivity()); + mSkipNextAction = new PlaybackControlsRow.SkipNextAction(getActivity()); + mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(getActivity()); + mFastForwardAction = new PlaybackControlsRow.FastForwardAction(getActivity()); + mRewindAction = new PlaybackControlsRow.RewindAction(getActivity()); + + if (PRIMARY_CONTROLS > 5) { + mPrimaryActionsAdapter.add(mThumbsUpAction); + } else { + mSecondaryActionsAdapter.add(mThumbsUpAction); + } + mPrimaryActionsAdapter.add(mSkipPreviousAction); + if (PRIMARY_CONTROLS > 3) { + mPrimaryActionsAdapter.add(new PlaybackControlsRow.RewindAction(getActivity())); + } + mPrimaryActionsAdapter.add(mPlayPauseAction); + if (PRIMARY_CONTROLS > 3) { + mPrimaryActionsAdapter.add(new PlaybackControlsRow.FastForwardAction(getActivity())); + } + mPrimaryActionsAdapter.add(mSkipNextAction); + + mSecondaryActionsAdapter.add(mRepeatAction); + mSecondaryActionsAdapter.add(mShuffleAction); + if (PRIMARY_CONTROLS > 5) { + mPrimaryActionsAdapter.add(mThumbsDownAction); + } else { + mSecondaryActionsAdapter.add(mThumbsDownAction); + } + mSecondaryActionsAdapter.add(new PlaybackControlsRow.HighQualityAction(getActivity())); + mSecondaryActionsAdapter.add(new PlaybackControlsRow.ClosedCaptioningAction(getActivity())); + } + + private void notifyChanged(Action action) { + ArrayObjectAdapter adapter = mPrimaryActionsAdapter; + if (adapter.indexOf(action) >= 0) { + adapter.notifyArrayItemRangeChanged(adapter.indexOf(action), 1); + return; + } + adapter = mSecondaryActionsAdapter; + if (adapter.indexOf(action) >= 0) { + adapter.notifyArrayItemRangeChanged(adapter.indexOf(action), 1); + return; + } + } + + private void updatePlaybackRow(int index) { + if (mPlaybackControlsRow.getItem() != null) { + Movie item = (Movie) mPlaybackControlsRow.getItem(); + item.setTitle(mItems.get(mCurrentItem).getTitle()); + item.setStudio(mItems.get(mCurrentItem).getStudio()); + } + if (SHOW_IMAGE) { + updateVideoImage(mItems.get(mCurrentItem).getCardImageURI().toString()); + } + mRowsAdapter.notifyArrayItemRangeChanged(0, 1); + mPlaybackControlsRow.setTotalTime(getDuration()); + mPlaybackControlsRow.setCurrentTime(0); + mPlaybackControlsRow.setBufferedProgress(0); + } + + private void addOtherRows() { + ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter()); + for (Movie movie : mItems) { + listRowAdapter.add(movie); + } + HeaderItem header = new HeaderItem(0, getString(R.string.related_movies)); + mRowsAdapter.add(new ListRow(header, listRowAdapter)); + + } + + private int getUpdatePeriod() { + if (getView() == null || mPlaybackControlsRow.getTotalTime() <= 0) { + return DEFAULT_UPDATE_PERIOD; + } + return Math.max(UPDATE_PERIOD, mPlaybackControlsRow.getTotalTime() / getView().getWidth()); + } + + private void startProgressAutomation() { + mRunnable = new Runnable() { + @Override + public void run() { + int updatePeriod = getUpdatePeriod(); + int currentTime = mPlaybackControlsRow.getCurrentTime() + updatePeriod; + int totalTime = mPlaybackControlsRow.getTotalTime(); + mPlaybackControlsRow.setCurrentTime(currentTime); + mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME); + + if (totalTime > 0 && totalTime <= currentTime) { + next(); + } + mHandler.postDelayed(this, updatePeriod); + } + }; + mHandler.postDelayed(mRunnable, getUpdatePeriod()); + } + + private void next() { + if (++mCurrentItem >= mItems.size()) { + mCurrentItem = 0; + } + + if (mPlayPauseAction.getIndex() == PlayPauseAction.PLAY) { + mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, false); + } else { + mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, true); + } + updatePlaybackRow(mCurrentItem); + } + + private void prev() { + if (--mCurrentItem < 0) { + mCurrentItem = mItems.size() - 1; + } + if (mPlayPauseAction.getIndex() == PlayPauseAction.PLAY) { + mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, false); + } else { + mCallback.onFragmentPlayPause(mItems.get(mCurrentItem), 0, true); + } + updatePlaybackRow(mCurrentItem); + } + + private void stopProgressAutomation() { + if (mHandler != null && mRunnable != null) { + mHandler.removeCallbacks(mRunnable); + } + } + + @Override + public void onStop() { + stopProgressAutomation(); + super.onStop(); + } + + protected void updateVideoImage(String uri) { + Glide.with(getActivity()) + .load(uri) + .centerCrop() + .into(new SimpleTarget(CARD_WIDTH, CARD_HEIGHT) { + @Override + public void onResourceReady(GlideDrawable resource, GlideAnimation glideAnimation) { + mPlaybackControlsRow.setImageDrawable(resource); + mRowsAdapter.notifyArrayItemRangeChanged(0, mRowsAdapter.size()); + } + }); + } + + // Container Activity must implement this interface + public interface OnPlayPauseClickedListener { + void onFragmentPlayPause(Movie movie, int position, Boolean playPause); + } + + static class DescriptionPresenter extends AbstractDetailsDescriptionPresenter { + @Override + protected void onBindDescription(ViewHolder viewHolder, Object item) { + viewHolder.getTitle().setText(((Movie) item).getTitle()); + viewHolder.getSubtitle().setText(((Movie) item).getStudio()); + } + } +} diff --git a/app/src/main/java/de/nicidienase/chaosflix/fragments/VideoDetailsFragment.java b/app/src/main/java/de/nicidienase/chaosflix/fragments/VideoDetailsFragment.java new file mode 100644 index 00000000..837b24bb --- /dev/null +++ b/app/src/main/java/de/nicidienase/chaosflix/fragments/VideoDetailsFragment.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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.nicidienase.chaosflix.fragments; + +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.v17.leanback.app.BackgroundManager; +import android.support.v17.leanback.app.DetailsFragment; +import android.support.v17.leanback.widget.Action; +import android.support.v17.leanback.widget.ArrayObjectAdapter; +import android.support.v17.leanback.widget.ClassPresenterSelector; +import android.support.v17.leanback.widget.DetailsOverviewRow; +import android.support.v17.leanback.widget.DetailsOverviewRowPresenter; +import android.support.v17.leanback.widget.HeaderItem; +import android.support.v17.leanback.widget.ImageCardView; +import android.support.v17.leanback.widget.ListRow; +import android.support.v17.leanback.widget.ListRowPresenter; +import android.support.v17.leanback.widget.OnActionClickedListener; +import android.support.v17.leanback.widget.OnItemViewClickedListener; +import android.support.v17.leanback.widget.Presenter; +import android.support.v17.leanback.widget.Row; +import android.support.v17.leanback.widget.RowPresenter; +import android.support.v4.app.ActivityOptionsCompat; +import android.util.DisplayMetrics; +import android.util.Log; +import android.widget.Toast; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.resource.drawable.GlideDrawable; +import com.bumptech.glide.request.animation.GlideAnimation; +import com.bumptech.glide.request.target.SimpleTarget; + +import java.util.Collections; +import java.util.List; + +import de.nicidienase.chaosflix.CardPresenter; +import de.nicidienase.chaosflix.DetailsDescriptionPresenter; +import de.nicidienase.chaosflix.activities.PlaybackOverlayActivity; +import de.nicidienase.chaosflix.R; +import de.nicidienase.chaosflix.Utils; +import de.nicidienase.chaosflix.activities.DetailsActivity; +import de.nicidienase.chaosflix.activities.MainActivity; +import de.nicidienase.chaosflix.entities.Movie; +import de.nicidienase.chaosflix.entities.MovieList; + +/* + * LeanbackDetailsFragment extends DetailsFragment, a Wrapper fragment for leanback details screens. + * It shows a detailed view of video and its meta plus related videos. + */ +public class VideoDetailsFragment extends DetailsFragment { + private static final String TAG = "VideoDetailsFragment"; + + private static final int ACTION_WATCH_TRAILER = 1; + private static final int ACTION_RENT = 2; + private static final int ACTION_BUY = 3; + + private static final int DETAIL_THUMB_WIDTH = 274; + private static final int DETAIL_THUMB_HEIGHT = 274; + + private static final int NUM_COLS = 10; + + private Movie mSelectedMovie; + + private ArrayObjectAdapter mAdapter; + private ClassPresenterSelector mPresenterSelector; + + private BackgroundManager mBackgroundManager; + private Drawable mDefaultBackground; + private DisplayMetrics mMetrics; + + @Override + public void onCreate(Bundle savedInstanceState) { + Log.d(TAG, "onCreate DetailsFragment"); + super.onCreate(savedInstanceState); + + prepareBackgroundManager(); + + mSelectedMovie = (Movie) getActivity().getIntent() + .getSerializableExtra(DetailsActivity.MOVIE); + if (mSelectedMovie != null) { + setupAdapter(); + setupDetailsOverviewRow(); + setupDetailsOverviewRowPresenter(); + setupMovieListRow(); + setupMovieListRowPresenter(); + updateBackground(mSelectedMovie.getBackgroundImageUrl()); + setOnItemViewClickedListener(new ItemViewClickedListener()); + } else { + Intent intent = new Intent(getActivity(), MainActivity.class); + startActivity(intent); + } + } + + @Override + public void onStop() { + super.onStop(); + } + + private void prepareBackgroundManager() { + mBackgroundManager = BackgroundManager.getInstance(getActivity()); + mBackgroundManager.attach(getActivity().getWindow()); + mDefaultBackground = getResources().getDrawable(R.drawable.default_background); + mMetrics = new DisplayMetrics(); + getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics); + } + + protected void updateBackground(String uri) { + Glide.with(getActivity()) + .load(uri) + .centerCrop() + .error(mDefaultBackground) + .into(new SimpleTarget(mMetrics.widthPixels, mMetrics.heightPixels) { + @Override + public void onResourceReady(GlideDrawable resource, + GlideAnimation glideAnimation) { + mBackgroundManager.setDrawable(resource); + } + }); + } + + private void setupAdapter() { + mPresenterSelector = new ClassPresenterSelector(); + mAdapter = new ArrayObjectAdapter(mPresenterSelector); + setAdapter(mAdapter); + } + + private void setupDetailsOverviewRow() { + Log.d(TAG, "doInBackground: " + mSelectedMovie.toString()); + final DetailsOverviewRow row = new DetailsOverviewRow(mSelectedMovie); + row.setImageDrawable(getResources().getDrawable(R.drawable.default_background)); + int width = Utils.convertDpToPixel(getActivity() + .getApplicationContext(), DETAIL_THUMB_WIDTH); + int height = Utils.convertDpToPixel(getActivity() + .getApplicationContext(), DETAIL_THUMB_HEIGHT); + Glide.with(getActivity()) + .load(mSelectedMovie.getCardImageUrl()) + .centerCrop() + .error(R.drawable.default_background) + .into(new SimpleTarget(width, height) { + @Override + public void onResourceReady(GlideDrawable resource, + GlideAnimation + glideAnimation) { + Log.d(TAG, "details overview card image url ready: " + resource); + row.setImageDrawable(resource); + mAdapter.notifyArrayItemRangeChanged(0, mAdapter.size()); + } + }); + + row.addAction(new Action(ACTION_WATCH_TRAILER, getResources().getString( + R.string.watch_trailer_1), getResources().getString(R.string.watch_trailer_2))); + row.addAction(new Action(ACTION_RENT, getResources().getString(R.string.rent_1), + getResources().getString(R.string.rent_2))); + row.addAction(new Action(ACTION_BUY, getResources().getString(R.string.buy_1), + getResources().getString(R.string.buy_2))); + + mAdapter.add(row); + } + + private void setupDetailsOverviewRowPresenter() { + // Set detail background and style. + DetailsOverviewRowPresenter detailsPresenter = + new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter()); + detailsPresenter.setBackgroundColor(getResources().getColor(R.color.selected_background)); + detailsPresenter.setStyleLarge(true); + + // Hook up transition element. + detailsPresenter.setSharedElementEnterTransition(getActivity(), + DetailsActivity.SHARED_ELEMENT_NAME); + + detailsPresenter.setOnActionClickedListener(new OnActionClickedListener() { + @Override + public void onActionClicked(Action action) { + if (action.getId() == ACTION_WATCH_TRAILER) { + Intent intent = new Intent(getActivity(), PlaybackOverlayActivity.class); + intent.putExtra(DetailsActivity.MOVIE, mSelectedMovie); + startActivity(intent); + } else { + Toast.makeText(getActivity(), action.toString(), Toast.LENGTH_SHORT).show(); + } + } + }); + mPresenterSelector.addClassPresenter(DetailsOverviewRow.class, detailsPresenter); + } + + private void setupMovieListRow() { + String subcategories[] = {getString(R.string.related_movies)}; + List list = MovieList.list; + + Collections.shuffle(list); + ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter()); + for (int j = 0; j < NUM_COLS; j++) { + listRowAdapter.add(list.get(j % 5)); + } + + HeaderItem header = new HeaderItem(0, subcategories[0]); + mAdapter.add(new ListRow(header, listRowAdapter)); + } + + private void setupMovieListRowPresenter() { + mPresenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter()); + } + + private final class ItemViewClickedListener implements OnItemViewClickedListener { + @Override + public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, + RowPresenter.ViewHolder rowViewHolder, Row row) { + + if (item instanceof Movie) { + Movie movie = (Movie) item; + Log.d(TAG, "Item: " + item.toString()); + Intent intent = new Intent(getActivity(), DetailsActivity.class); + intent.putExtra(getResources().getString(R.string.movie), mSelectedMovie); + intent.putExtra(getResources().getString(R.string.should_start), true); + startActivity(intent); + + + Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation( + getActivity(), + ((ImageCardView) itemViewHolder.view).getMainImageView(), + DetailsActivity.SHARED_ELEMENT_NAME).toBundle(); + getActivity().startActivity(intent, bundle); + } + } + } +} diff --git a/app/src/main/res/drawable-hdpi/app_icon_quantum.png b/app/src/main/res/drawable-hdpi/app_icon_quantum.png new file mode 100644 index 00000000..825ef637 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/app_icon_quantum.png differ diff --git a/app/src/main/res/drawable-hdpi/app_icon_quantum_card.png b/app/src/main/res/drawable-hdpi/app_icon_quantum_card.png new file mode 100644 index 00000000..9b1703d8 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/app_icon_quantum_card.png differ diff --git a/app/src/main/res/drawable-hdpi/card_background_default.9.png b/app/src/main/res/drawable-hdpi/card_background_default.9.png new file mode 100644 index 00000000..29f4e01d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/card_background_default.9.png differ diff --git a/app/src/main/res/drawable-hdpi/default_background.xml b/app/src/main/res/drawable-hdpi/default_background.xml new file mode 100644 index 00000000..521a359c --- /dev/null +++ b/app/src/main/res/drawable-hdpi/default_background.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/grid_bg.png b/app/src/main/res/drawable-hdpi/grid_bg.png new file mode 100644 index 00000000..476c698e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/grid_bg.png differ diff --git a/app/src/main/res/drawable-hdpi/movie.png b/app/src/main/res/drawable-hdpi/movie.png new file mode 100644 index 00000000..cb5cb6d3 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/movie.png differ diff --git a/app/src/main/res/drawable-hdpi/scrubber_disabled.png b/app/src/main/res/drawable-hdpi/scrubber_disabled.png new file mode 100644 index 00000000..9bb7247d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/scrubber_disabled.png differ diff --git a/app/src/main/res/drawable-hdpi/scrubber_focussed.png b/app/src/main/res/drawable-hdpi/scrubber_focussed.png new file mode 100644 index 00000000..22898599 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/scrubber_focussed.png differ diff --git a/app/src/main/res/drawable-hdpi/scrubber_normal.png b/app/src/main/res/drawable-hdpi/scrubber_normal.png new file mode 100644 index 00000000..34aff7ce Binary files /dev/null and b/app/src/main/res/drawable-hdpi/scrubber_normal.png differ diff --git a/app/src/main/res/drawable-hdpi/scrubber_pressed.png b/app/src/main/res/drawable-hdpi/scrubber_pressed.png new file mode 100644 index 00000000..3c492e16 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/scrubber_pressed.png differ diff --git a/app/src/main/res/drawable-hdpi/shadow7.9.png b/app/src/main/res/drawable-hdpi/shadow7.9.png new file mode 100644 index 00000000..6d00d096 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/shadow7.9.png differ diff --git a/app/src/main/res/drawable-hdpi/star_icon.png b/app/src/main/res/drawable-hdpi/star_icon.png new file mode 100644 index 00000000..8a708da7 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/star_icon.png differ diff --git a/app/src/main/res/drawable-hdpi/videos_by_google_banner.png b/app/src/main/res/drawable-hdpi/videos_by_google_banner.png new file mode 100644 index 00000000..bdcf41e4 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/videos_by_google_banner.png differ diff --git a/app/src/main/res/drawable-hdpi/videos_by_google_icon.png b/app/src/main/res/drawable-hdpi/videos_by_google_icon.png new file mode 100644 index 00000000..9bc48360 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/videos_by_google_icon.png differ diff --git a/app/src/main/res/drawable-mdpi/app_icon_quantum.png b/app/src/main/res/drawable-mdpi/app_icon_quantum.png new file mode 100644 index 00000000..6b621385 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/app_icon_quantum.png differ diff --git a/app/src/main/res/drawable-mdpi/app_icon_quantum_card.png b/app/src/main/res/drawable-mdpi/app_icon_quantum_card.png new file mode 100644 index 00000000..ac9cc307 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/app_icon_quantum_card.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_launcher.png b/app/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 00000000..359047df Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-mdpi/videos_by_google_banner.png b/app/src/main/res/drawable-mdpi/videos_by_google_banner.png new file mode 100644 index 00000000..b1916262 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/videos_by_google_banner.png differ diff --git a/app/src/main/res/drawable-mdpi/videos_by_google_icon.png b/app/src/main/res/drawable-mdpi/videos_by_google_icon.png new file mode 100644 index 00000000..8a7c6dc1 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/videos_by_google_icon.png differ diff --git a/app/src/main/res/drawable-xhdpi/app_icon_quantum.png b/app/src/main/res/drawable-xhdpi/app_icon_quantum.png new file mode 100644 index 00000000..825ef637 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/app_icon_quantum.png differ diff --git a/app/src/main/res/drawable-xhdpi/app_icon_quantum_card.png b/app/src/main/res/drawable-xhdpi/app_icon_quantum_card.png new file mode 100644 index 00000000..9b1703d8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/app_icon_quantum_card.png differ diff --git a/app/src/main/res/drawable-xhdpi/default_background.xml b/app/src/main/res/drawable-xhdpi/default_background.xml new file mode 100644 index 00000000..521a359c --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/default_background.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/grid_bg.png b/app/src/main/res/drawable-xhdpi/grid_bg.png new file mode 100644 index 00000000..476c698e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/grid_bg.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_launcher.png b/app/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 00000000..71c6d760 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_pause_playcontrol_focussed.png b/app/src/main/res/drawable-xhdpi/ic_pause_playcontrol_focussed.png new file mode 100644 index 00000000..63b45b94 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_pause_playcontrol_focussed.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_pause_playcontrol_normal.png b/app/src/main/res/drawable-xhdpi/ic_pause_playcontrol_normal.png new file mode 100644 index 00000000..9cf25826 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_pause_playcontrol_normal.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_pause_playcontrol_pressed.png b/app/src/main/res/drawable-xhdpi/ic_pause_playcontrol_pressed.png new file mode 100644 index 00000000..516ceca7 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_pause_playcontrol_pressed.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_play_action_focussed.png b/app/src/main/res/drawable-xhdpi/ic_play_action_focussed.png new file mode 100644 index 00000000..bf93814a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play_action_focussed.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_play_action_normal.png b/app/src/main/res/drawable-xhdpi/ic_play_action_normal.png new file mode 100644 index 00000000..966f754b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play_action_normal.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_play_action_pressed.png b/app/src/main/res/drawable-xhdpi/ic_play_action_pressed.png new file mode 100644 index 00000000..934ed894 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play_action_pressed.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_play_playcontrol_focussed.png b/app/src/main/res/drawable-xhdpi/ic_play_playcontrol_focussed.png new file mode 100644 index 00000000..687b4218 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play_playcontrol_focussed.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_play_playcontrol_normal.png b/app/src/main/res/drawable-xhdpi/ic_play_playcontrol_normal.png new file mode 100644 index 00000000..1eadfa47 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play_playcontrol_normal.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_play_playcontrol_pressed.png b/app/src/main/res/drawable-xhdpi/ic_play_playcontrol_pressed.png new file mode 100644 index 00000000..7f583f61 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_play_playcontrol_pressed.png differ diff --git a/app/src/main/res/drawable-xhdpi/movie.png b/app/src/main/res/drawable-xhdpi/movie.png new file mode 100644 index 00000000..cb5cb6d3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/movie.png differ diff --git a/app/src/main/res/drawable-xhdpi/star_icon.png b/app/src/main/res/drawable-xhdpi/star_icon.png new file mode 100644 index 00000000..8a708da7 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/star_icon.png differ diff --git a/app/src/main/res/drawable-xhdpi/videos_by_google_banner.png b/app/src/main/res/drawable-xhdpi/videos_by_google_banner.png new file mode 100644 index 00000000..bdcf41e4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/videos_by_google_banner.png differ diff --git a/app/src/main/res/drawable-xhdpi/videos_by_google_icon.png b/app/src/main/res/drawable-xhdpi/videos_by_google_icon.png new file mode 100644 index 00000000..9bc48360 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/videos_by_google_icon.png differ diff --git a/app/src/main/res/drawable-xxhdpi/app_icon_quantum.png b/app/src/main/res/drawable-xxhdpi/app_icon_quantum.png new file mode 100644 index 00000000..c82f94cd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/app_icon_quantum.png differ diff --git a/app/src/main/res/drawable-xxhdpi/app_icon_quantum_card.png b/app/src/main/res/drawable-xxhdpi/app_icon_quantum_card.png new file mode 100644 index 00000000..6c50e8ff Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/app_icon_quantum_card.png differ diff --git a/app/src/main/res/drawable-xxhdpi/videos_by_google_banner.png b/app/src/main/res/drawable-xxhdpi/videos_by_google_banner.png new file mode 100644 index 00000000..6c121e61 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/videos_by_google_banner.png differ diff --git a/app/src/main/res/drawable-xxhdpi/videos_by_google_icon.png b/app/src/main/res/drawable-xxhdpi/videos_by_google_icon.png new file mode 100644 index 00000000..4258160e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/videos_by_google_icon.png differ diff --git a/app/src/main/res/drawable/app_icon_quantum.png b/app/src/main/res/drawable/app_icon_quantum.png new file mode 100644 index 00000000..fda9a74a Binary files /dev/null and b/app/src/main/res/drawable/app_icon_quantum.png differ diff --git a/app/src/main/res/drawable/app_icon_quantum_card.png b/app/src/main/res/drawable/app_icon_quantum_card.png new file mode 100644 index 00000000..498cf669 Binary files /dev/null and b/app/src/main/res/drawable/app_icon_quantum_card.png differ diff --git a/app/src/main/res/drawable/app_icon_your_company.png b/app/src/main/res/drawable/app_icon_your_company.png new file mode 100644 index 00000000..0a47b018 Binary files /dev/null and b/app/src/main/res/drawable/app_icon_your_company.png differ diff --git a/app/src/main/res/drawable/details_img.png b/app/src/main/res/drawable/details_img.png new file mode 100644 index 00000000..7ea688b6 Binary files /dev/null and b/app/src/main/res/drawable/details_img.png differ diff --git a/app/src/main/res/drawable/ic_action_a.png b/app/src/main/res/drawable/ic_action_a.png new file mode 100644 index 00000000..3d555efa Binary files /dev/null and b/app/src/main/res/drawable/ic_action_a.png differ diff --git a/app/src/main/res/drawable/ic_title.png b/app/src/main/res/drawable/ic_title.png new file mode 100644 index 00000000..1c62b2e1 Binary files /dev/null and b/app/src/main/res/drawable/ic_title.png differ diff --git a/app/src/main/res/drawable/movie.png b/app/src/main/res/drawable/movie.png new file mode 100644 index 00000000..cb5cb6d3 Binary files /dev/null and b/app/src/main/res/drawable/movie.png differ diff --git a/app/src/main/res/drawable/player_bg_gradient_dark.xml b/app/src/main/res/drawable/player_bg_gradient_dark.xml new file mode 100644 index 00000000..613fad05 --- /dev/null +++ b/app/src/main/res/drawable/player_bg_gradient_dark.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shadow7.9.png b/app/src/main/res/drawable/shadow7.9.png new file mode 100644 index 00000000..6d00d096 Binary files /dev/null and b/app/src/main/res/drawable/shadow7.9.png differ diff --git a/app/src/main/res/drawable/videos_by_google_banner.png b/app/src/main/res/drawable/videos_by_google_banner.png new file mode 100644 index 00000000..4cedb526 Binary files /dev/null and b/app/src/main/res/drawable/videos_by_google_banner.png differ diff --git a/app/src/main/res/drawable/videos_by_google_icon.png b/app/src/main/res/drawable/videos_by_google_icon.png new file mode 100644 index 00000000..20fd898d Binary files /dev/null and b/app/src/main/res/drawable/videos_by_google_icon.png differ diff --git a/app/src/main/res/layout/activity_details.xml b/app/src/main/res/layout/activity_details.xml new file mode 100644 index 00000000..2b94ba87 --- /dev/null +++ b/app/src/main/res/layout/activity_details.xml @@ -0,0 +1,9 @@ + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..c5a8b1cf --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,10 @@ + + diff --git a/app/src/main/res/layout/playback_controls.xml b/app/src/main/res/layout/playback_controls.xml new file mode 100644 index 00000000..cfacaa8b --- /dev/null +++ b/app/src/main/res/layout/playback_controls.xml @@ -0,0 +1,34 @@ + + + + + + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..cde69bcc Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..c133a0cb Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..bfa42f0e Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..324e72cd Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..e9e5c7fc --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,18 @@ + + #000000 + #DDDDDD + #0096a6 + #ffaa3f + #ffaa3f + #0096a6 + #30000000 + #30FF0000 + #00000000 + #AA000000 + #59000000 + #FFFFFF + #AAFADCA7 + #FADCA7 + #EEFF41 + #3d3d3d + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..8eb92efd --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,40 @@ + + Chaosflix + 33c3 - Works for Me + Related Videos + + Error + OK + Pause + Play + Stop + 00:00 + Play Pause Button + Loading… + No video was found + About DemoCast Player + Version: %1$s + Popular Videos + PREFERENCES + Grid View + Error Fragment + Personal Settings + Watch trailer + FREE + Rent By Day + From $1.99 + Buy and Own + AT $9.99 + Movie + shouldStart + startPosition + Search Results + + + Media loading timed out + Media server was not reachable + Failed to load video + An error occurred + Dismiss + Oops + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..8cc6a634 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..90c0f86a --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,14 @@ + + + +