diff options
| author | Christofer Ã…kersten <akersten@google.com> | 2019-02-27 15:04:42 -0800 |
|---|---|---|
| committer | Christofer Ã…kersten <akersten@google.com> | 2019-03-05 18:23:44 -0800 |
| commit | 4524682dc2b5beaf1e6b2e93c9cd407cd628aa34 (patch) | |
| tree | fca0f25392dc46944f9ba6ac231427da38303df9 | |
| parent | f928c1aadb26e643129743c41219b30add6d719e (diff) | |
| download | platform_packages_apps_UniversalMediaPlayer-4524682dc2b5beaf1e6b2e93c9cd407cd628aa34.tar.gz platform_packages_apps_UniversalMediaPlayer-4524682dc2b5beaf1e6b2e93c9cd407cd628aa34.tar.bz2 platform_packages_apps_UniversalMediaPlayer-4524682dc2b5beaf1e6b2e93c9cd407cd628aa34.zip | |
Implement series detail activity
Bug: 123035784
Test: manual
Change-Id: I83593db6c9ed207d468d3dd13d1a14793c74da35
| -rw-r--r-- | java/com/android/pump/activity/AlbumDetailsActivity.java | 3 | ||||
| -rw-r--r-- | java/com/android/pump/activity/ArtistDetailsActivity.java | 3 | ||||
| -rw-r--r-- | java/com/android/pump/activity/GenreDetailsActivity.java | 3 | ||||
| -rw-r--r-- | java/com/android/pump/activity/PlaylistDetailsActivity.java | 3 | ||||
| -rw-r--r-- | java/com/android/pump/activity/SeriesDetailsActivity.java | 225 | ||||
| -rw-r--r-- | java/com/android/pump/db/Episode.java | 4 | ||||
| -rw-r--r-- | java/com/android/pump/db/Series.java | 42 | ||||
| -rw-r--r-- | java/com/android/pump/provider/KnowledgeGraph.java | 2 | ||||
| -rw-r--r-- | res/layout/activity_series_details.xml | 114 | ||||
| -rw-r--r-- | res/layout/episode.xml | 52 |
10 files changed, 426 insertions, 25 deletions
diff --git a/java/com/android/pump/activity/AlbumDetailsActivity.java b/java/com/android/pump/activity/AlbumDetailsActivity.java index 0d42828..f6a338a 100644 --- a/java/com/android/pump/activity/AlbumDetailsActivity.java +++ b/java/com/android/pump/activity/AlbumDetailsActivity.java @@ -139,7 +139,8 @@ public class AlbumDetailsActivity extends AppCompatActivity implements MediaDb.U imageView.setImageURI(mAlbum.getAlbumArtUri()); nameView.setText(mAlbum.getTitle()); - countView.setText(mAlbum.getAudios().size() + " songs"); // TODO Move to resource + // TODO(b/123037263) I18n -- Move to resource + countView.setText(mAlbum.getAudios().size() + " songs"); ImageView playView = findViewById(R.id.activity_album_details_play); playView.setOnClickListener((view) -> diff --git a/java/com/android/pump/activity/ArtistDetailsActivity.java b/java/com/android/pump/activity/ArtistDetailsActivity.java index 0ff9e53..dcc0a34 100644 --- a/java/com/android/pump/activity/ArtistDetailsActivity.java +++ b/java/com/android/pump/activity/ArtistDetailsActivity.java @@ -151,7 +151,8 @@ public class ArtistDetailsActivity extends AppCompatActivity implements MediaDb. } imageView.setImageURI(albumArtUri); nameView.setText(mArtist.getName()); - countView.setText(mArtist.getAudios().size() + " songs"); // TODO Move to resource + // TODO(b/123037263) I18n -- Move to resource + countView.setText(mArtist.getAudios().size() + " songs"); ImageView playView = findViewById(R.id.activity_artist_details_play); playView.setOnClickListener((view) -> diff --git a/java/com/android/pump/activity/GenreDetailsActivity.java b/java/com/android/pump/activity/GenreDetailsActivity.java index 6b6d86b..166ee4d 100644 --- a/java/com/android/pump/activity/GenreDetailsActivity.java +++ b/java/com/android/pump/activity/GenreDetailsActivity.java @@ -140,7 +140,8 @@ public class GenreDetailsActivity extends AppCompatActivity implements MediaDb.U // TODO imageView.setImageURI(???); nameView.setText(mGenre.getName()); - countView.setText(mGenre.getAudios().size() + " songs"); // TODO Move to resource + // TODO(b/123037263) I18n -- Move to resource + countView.setText(mGenre.getAudios().size() + " songs"); ImageView playView = findViewById(R.id.activity_genre_details_play); playView.setOnClickListener((view) -> diff --git a/java/com/android/pump/activity/PlaylistDetailsActivity.java b/java/com/android/pump/activity/PlaylistDetailsActivity.java index 9997a57..84f56a5 100644 --- a/java/com/android/pump/activity/PlaylistDetailsActivity.java +++ b/java/com/android/pump/activity/PlaylistDetailsActivity.java @@ -189,7 +189,8 @@ public class PlaylistDetailsActivity extends AppCompatActivity implements MediaD image3View.setVisibility(View.VISIBLE); } nameView.setText(mPlaylist.getName()); - countView.setText(mPlaylist.getAudios().size() + " songs"); // TODO Move to resource + // TODO(b/123037263) I18n -- Move to resource + countView.setText(mPlaylist.getAudios().size() + " songs"); ImageView playView = findViewById(R.id.activity_playlist_details_play); playView.setOnClickListener((view) -> diff --git a/java/com/android/pump/activity/SeriesDetailsActivity.java b/java/com/android/pump/activity/SeriesDetailsActivity.java index 39336ad..e9a85ca 100644 --- a/java/com/android/pump/activity/SeriesDetailsActivity.java +++ b/java/com/android/pump/activity/SeriesDetailsActivity.java @@ -18,20 +18,37 @@ package com.android.pump.activity; import android.content.Context; import android.content.Intent; +import android.net.Uri; import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.Spinner; +import android.widget.TextView; +import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.RecyclerView; import com.android.pump.R; +import com.android.pump.db.Episode; import com.android.pump.db.MediaDb; import com.android.pump.db.Series; import com.android.pump.util.Globals; +import java.util.List; + @UiThread -public class SeriesDetailsActivity extends AppCompatActivity { +public class SeriesDetailsActivity extends AppCompatActivity implements MediaDb.UpdateCallback { + private MediaDb mMediaDb; private Series mSeries; public static void start(@NonNull Context context, @NonNull Series series) { @@ -49,6 +66,17 @@ public class SeriesDetailsActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_series_details); + setSupportActionBar(findViewById(R.id.activity_series_details_toolbar)); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayShowTitleEnabled(false); + actionBar.setDisplayShowHomeEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + } + + mMediaDb = Globals.getMediaDb(this); + mMediaDb.addSeriesUpdateCallback(this); + handleIntent(); } @@ -60,6 +88,43 @@ public class SeriesDetailsActivity extends AppCompatActivity { handleIntent(); } + @Override + protected void onDestroy() { + mMediaDb.removeSeriesUpdateCallback(this); + + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(@NonNull Menu menu) { + getMenuInflater().inflate(R.menu.activity_pump, menu); // TODO activity_series_details ? + return true; + } + + @Override + public boolean onSupportNavigateUp() { + // TODO It should not be necessary to override this method + onBackPressed(); + return true; + } + + @Override + public void onItemsInserted(int index, int count) { } + + @Override + public void onItemsUpdated(int index, int count) { + for (int i = index; i < index + count; ++i) { + Series series = mMediaDb.getSeries().get(i); + if (series.equals(mSeries)) { + updateViews(); + break; + } + } + } + + @Override + public void onItemsRemoved(int index, int count) { } + private void handleIntent() { Intent intent = getIntent(); Bundle extras = intent != null ? intent.getExtras() : null; @@ -75,6 +140,164 @@ public class SeriesDetailsActivity extends AppCompatActivity { } } else { mSeries = null; + // TODO This shouldn't happen -- throw exception? + } + + mMediaDb.loadData(mSeries); + updateViews(); + } + + private void updateViews() { + // TODO ImageView imageView = findViewById(R.id.activity_series_details_image); + ImageView posterView = findViewById(R.id.activity_series_details_poster); + TextView titleView = findViewById(R.id.activity_series_details_title); + TextView attributesView = findViewById(R.id.activity_series_details_attributes); + TextView synopsisView = findViewById(R.id.activity_series_details_description); + + // TODO imageView.setImageURI(mSeries.get???()); + posterView.setImageURI(mSeries.getPosterUri()); + titleView.setText(mSeries.getTitle()); + attributesView.setText("American Drama Series"); // TODO(b/123707108) Implement + synopsisView.setText(mSeries.getDescription()); + + Spinner spinner = findViewById(R.id.activity_series_details_spinner); + spinner.setAdapter(new BaseAdapter() { + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public int getCount() { + return mSeries.getSeasons().size(); + } + + @Override + public @NonNull Integer getItem(int position) { + return mSeries.getSeasons().get(position).get(0).getSeason(); + } + + @Override + public long getItemId(int position) { + return getItem(position); + } + + @Override + public View getView(int position, @Nullable View convertView, + @NonNull ViewGroup parent) { + return getView(android.R.layout.simple_spinner_item, position, convertView, parent); + } + + @Override + public View getDropDownView(int position, @Nullable View convertView, + @NonNull ViewGroup parent) { + return getView(R.layout.support_simple_spinner_dropdown_item, + position, convertView, parent); + } + + private View getView(@LayoutRes int resource, int position, @Nullable View convertView, + @NonNull ViewGroup parent) { + View view = convertView; + if (view == null) { + view = getInflater().inflate(resource, parent, false); + } + + TextView textView = view.findViewById(android.R.id.text1); + // TODO(b/123037263) I18n -- Move to resource + textView.setText("Season " + getItem(position)); + return view; + } + + private @NonNull LayoutInflater getInflater() { + return LayoutInflater.from(SeriesDetailsActivity.this); + } + }); + + RecyclerView recyclerView = findViewById(R.id.activity_series_details_recycler_view); + recyclerView.setHasFixedSize(true); + recyclerView.setAdapter(new SeriesAdapter(mMediaDb, mSeries)); + spinner.setOnItemSelectedListener((SeriesAdapter) recyclerView.getAdapter()); + + // TODO(b/123707260) Enable view caching + //recyclerView.setItemViewCacheSize(0); + //recyclerView.setRecycledViewPool(Globals.getRecycledViewPool(requireContext())); + } + + private static class SeriesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> + implements AdapterView.OnItemSelectedListener{ + private final MediaDb mMediaDb; + private final Series mSeries; + private int mSeasonPosition; + + private SeriesAdapter(@NonNull MediaDb mediaDb, @NonNull Series series) { + setHasStableIds(true); + mMediaDb = mediaDb; + mSeries = series; + } + + @Override + public @NonNull RecyclerView.ViewHolder onCreateViewHolder( + @NonNull ViewGroup parent, int viewType) { + return new EpisodeViewHolder(LayoutInflater.from(parent.getContext()) + .inflate(viewType, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + Episode episode = getSeason().get(position); + mMediaDb.loadData(episode); // TODO Where should we call this? In bind()? + ((EpisodeViewHolder) holder).bind(episode); + } + + @Override + public int getItemCount() { + return getSeason().size(); + } + + @Override + public long getItemId(int position) { + return getSeason().get(position).getId(); + } + + @Override + public int getItemViewType(int position) { + return R.layout.episode; + } + + @Override + public void onItemSelected(@NonNull AdapterView<?> parent, @NonNull View view, + int position, long id) { + mSeasonPosition = position; + notifyDataSetChanged(); + } + + @Override + public void onNothingSelected(@NonNull AdapterView<?> parent) { } + + private @NonNull List<Episode> getSeason() { + return mSeries.getSeasons().get(mSeasonPosition); + } + } + + private static class EpisodeViewHolder extends RecyclerView.ViewHolder { + private EpisodeViewHolder(@NonNull View itemView) { + super(itemView); + } + + private void bind(@NonNull Episode episode) { + ImageView imageView = itemView.findViewById(R.id.episode_image); + TextView textView = itemView.findViewById(R.id.episode_text); + + Uri posterUri = episode.getPosterUri(); + if (posterUri == null) { + posterUri = episode.getThumbnailUri(); + } + imageView.setImageURI(posterUri); + // TODO(b/123037263) I18n -- Move to resource + textView.setText("Episode " + episode.getEpisode()); + + itemView.setOnClickListener((view) -> + VideoPlayerActivity.start(view.getContext(), episode)); } } } diff --git a/java/com/android/pump/db/Episode.java b/java/com/android/pump/db/Episode.java index c7396e1..7d119fc 100644 --- a/java/com/android/pump/db/Episode.java +++ b/java/com/android/pump/db/Episode.java @@ -94,10 +94,6 @@ public class Episode extends Video { return true; } - public @NonNull String getTitle() { - return mSeries.getTitle(); - } - boolean isLoaded() { return mLoaded; } diff --git a/java/com/android/pump/db/Series.java b/java/com/android/pump/db/Series.java index 59ab172..32f011d 100644 --- a/java/com/android/pump/db/Series.java +++ b/java/com/android/pump/db/Series.java @@ -26,6 +26,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static com.android.pump.util.Collections.binarySearch; + @AnyThread public class Series { private final String mTitle; @@ -33,8 +35,8 @@ public class Series { // TODO(b/123706949) Lock mutable fields to ensure consistent updates private Uri mPosterUri; - private final List<Episode> mEpisodes = new ArrayList<>(); private String mDescription; + private final List<List<Episode>> mSeasons = new ArrayList<>(); private boolean mLoaded; Series(@NonNull String title) { @@ -77,17 +79,6 @@ public class Series { return true; } - public @NonNull List<Episode> getEpisodes() { - return Collections.unmodifiableList(mEpisodes); - } - - boolean addEpisode(@NonNull Episode episode) { - if (mEpisodes.contains(episode)) { - return false; - } - return mEpisodes.add(episode); - } - public @Nullable String getDescription() { return mDescription; } @@ -100,6 +91,33 @@ public class Series { return true; } + public @NonNull List<List<Episode>> getSeasons() { + return Collections.unmodifiableList(mSeasons); + } + + boolean addEpisode(@NonNull Episode episode) { + int seriesLocation = binarySearch(mSeasons, episode.getSeason(), + (season) -> season.get(0).getSeason()); + if (seriesLocation >= 0) { + List<Episode> series = mSeasons.get(seriesLocation); + int episodeLocation = binarySearch(series, episode.getEpisode(), Episode::getEpisode); + if (episodeLocation >= 0) { + if (episode.equals(series.get(episodeLocation))) { + return false; + } + // TODO(b/127524752) This should kind of be okay (i.e. handle gracefully) + throw new IllegalStateException("Two episodes with the same season & episode #"); + } else { + series.add(~episodeLocation, episode); + } + } else { + List<Episode> series = new ArrayList<>(); + series.add(episode); + mSeasons.add(~seriesLocation, series); + } + return true; + } + boolean isLoaded() { return mLoaded; } diff --git a/java/com/android/pump/provider/KnowledgeGraph.java b/java/com/android/pump/provider/KnowledgeGraph.java index 049e4d7..33dc300 100644 --- a/java/com/android/pump/provider/KnowledgeGraph.java +++ b/java/com/android/pump/provider/KnowledgeGraph.java @@ -111,7 +111,7 @@ public final class KnowledgeGraph implements DataProvider { public boolean populateEpisode(@NonNull Episode episode) throws IOException { boolean updated = false; Pair<String, String> metadata = getMetadataFromResult( - getResultFromKG(episode.getTitle(), "TVEpisode")); + getResultFromKG(episode.getSeries().getTitle(), "TVEpisode")); if (metadata != null) { if (metadata.first != null) { updated |= episode.setPosterUri(Uri.parse(metadata.first)); diff --git a/res/layout/activity_series_details.xml b/res/layout/activity_series_details.xml index 6766635..f34876a 100644 --- a/res/layout/activity_series_details.xml +++ b/res/layout/activity_series_details.xml @@ -15,8 +15,116 @@ limitations under the License. --> -<android.view.View +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="#ffb6c1"/> + android:layout_height="match_parent"> + + <com.android.pump.widget.UriImageView + android:id="@+id/activity_series_details_image" + android:layout_width="0dp" + android:layout_height="309dp" + android:scaleType="centerCrop" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + tools:src="@tools:sample/backgrounds/scenic"/> + + <com.google.android.material.appbar.AppBarLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@null" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:elevation="0dp"> + + <androidx.appcompat.widget.Toolbar + android:id="@+id/activity_series_details_toolbar" + android:layout_width="match_parent" + android:layout_height="?actionBarSize"/> + + </com.google.android.material.appbar.AppBarLayout> + + <android.view.View + android:layout_width="0dp" + android:layout_height="77dp" + android:background="@drawable/shadow" + app:layout_constraintBottom_toBottomOf="@id/activity_series_details_image" + app:layout_constraintStart_toStartOf="@id/activity_series_details_image" + app:layout_constraintEnd_toEndOf="@id/activity_series_details_image"/> + + <com.android.pump.widget.UriImageView + android:id="@+id/activity_series_details_poster" + android:layout_width="108dp" + android:layout_height="162dp" + android:layout_marginStart="24dp" + android:layout_marginEnd="24dp" + android:scaleType="centerCrop" + app:layout_constraintBottom_toBottomOf="@id/activity_series_details_image" + app:layout_constraintStart_toStartOf="@id/activity_series_details_image" + tools:src="@tools:sample/avatars"/> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/activity_series_details_title" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="24dp" + android:layout_marginEnd="24dp" + android:layout_marginTop="24dp" + android:textSize="18sp" + android:maxLines="3" + android:ellipsize="end" + app:layout_constraintTop_toBottomOf="@id/activity_series_details_image" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + tools:text="Title"/> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/activity_series_details_attributes" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:textSize="12sp" + android:maxLines="1" + android:ellipsize="end" + app:layout_constraintTop_toBottomOf="@id/activity_series_details_title" + app:layout_constraintStart_toStartOf="@id/activity_series_details_title" + app:layout_constraintEnd_toEndOf="@id/activity_series_details_title" + tools:text="American Drama Series"/> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/activity_series_details_description" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:maxLines="3" + android:ellipsize="end" + app:layout_constraintTop_toBottomOf="@id/activity_series_details_attributes" + app:layout_constraintStart_toStartOf="@id/activity_series_details_title" + app:layout_constraintEnd_toEndOf="@id/activity_series_details_title" + tools:text="@tools:sample/lorem/random"/> + + <androidx.appcompat.widget.AppCompatSpinner + android:id="@+id/activity_series_details_spinner" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + app:layout_constraintTop_toBottomOf="@id/activity_series_details_description" + app:layout_constraintStart_toStartOf="@id/activity_series_details_description"/> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/activity_series_details_recycler_view" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + app:layout_constraintTop_toBottomOf="@id/activity_series_details_spinner" + app:layout_constraintStart_toStartOf="@id/activity_series_details_description" + app:layout_constraintEnd_toEndOf="@id/activity_series_details_description" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + android:orientation="horizontal" + tools:listitem="@layout/episode"/> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/res/layout/episode.xml b/res/layout/episode.xml new file mode 100644 index 0000000..532460d --- /dev/null +++ b/res/layout/episode.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright 2019 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. +--> + +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="176dp" + android:layout_height="wrap_content" + + android:clickable="true" + android:focusable="true" + android:foreground="?selectableItemBackground"> + + <com.android.pump.widget.UriImageView + android:id="@+id/episode_image" + android:layout_width="0dp" + android:layout_height="0dp" + android:scaleType="centerCrop" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintDimensionRatio="16:9" + tools:src="@tools:sample/avatars"/> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/episode_text" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:maxLines="1" + android:ellipsize="end" + app:layout_constraintTop_toBottomOf="@id/episode_image" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + tools:text="Title"/> + +</androidx.constraintlayout.widget.ConstraintLayout> |
