summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristofer Ã…kersten <akersten@google.com>2019-02-27 15:04:42 -0800
committerChristofer Ã…kersten <akersten@google.com>2019-03-05 18:23:44 -0800
commit4524682dc2b5beaf1e6b2e93c9cd407cd628aa34 (patch)
treefca0f25392dc46944f9ba6ac231427da38303df9
parentf928c1aadb26e643129743c41219b30add6d719e (diff)
downloadplatform_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.java3
-rw-r--r--java/com/android/pump/activity/ArtistDetailsActivity.java3
-rw-r--r--java/com/android/pump/activity/GenreDetailsActivity.java3
-rw-r--r--java/com/android/pump/activity/PlaylistDetailsActivity.java3
-rw-r--r--java/com/android/pump/activity/SeriesDetailsActivity.java225
-rw-r--r--java/com/android/pump/db/Episode.java4
-rw-r--r--java/com/android/pump/db/Series.java42
-rw-r--r--java/com/android/pump/provider/KnowledgeGraph.java2
-rw-r--r--res/layout/activity_series_details.xml114
-rw-r--r--res/layout/episode.xml52
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>