This commit is contained in:
LagradOst 2021-05-18 15:43:32 +02:00
parent 0fa16e3bbc
commit 719b60db26
18 changed files with 530 additions and 21 deletions

View file

@ -26,5 +26,10 @@
<option name="name" value="maven" />
<option name="url" value="https://jitpack.io" />
</remote-repository>
<remote-repository>
<option name="id" value="maven" />
<option name="name" value="maven" />
<option name="url" value="https://github.com/psiegman/mvn-repo/raw/master/releases" />
</remote-repository>
</component>
</project>

View file

@ -34,6 +34,9 @@ android {
}
repositories {
maven {
url 'https://github.com/psiegman/mvn-repo/raw/master/releases'
}
maven { url 'https://jitpack.io' }
}

View file

@ -23,6 +23,14 @@ object APIHolder {
ShiroProvider()
)
fun getApiFromName(apiName: String): MainAPI {
for (api in apis) {
if (apiName == api.name)
return api
}
return apis[defProvider]
}
fun Activity.getApiSettings(): HashSet<String> {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
@ -39,11 +47,11 @@ abstract class MainAPI {
return null
}
open fun load(url: String): Any? { //LoadResponse
open fun load(slug: String): Any? { //LoadResponse
return null
}
open fun loadLinks(url: String, id: Int): Boolean {
open fun loadLinks(data: Any, id: Int): Boolean {
return false
}
}
@ -51,8 +59,7 @@ abstract class MainAPI {
fun MainAPI.fixUrl(url: String): String {
if (url.startsWith('/')) {
return mainUrl + url
}
else if(!url.startsWith("http") && !url.startsWith("//")) {
} else if (!url.startsWith("http") && !url.startsWith("//")) {
return "$mainUrl/$url"
}
return url
@ -90,7 +97,8 @@ enum class TvType {
interface SearchResponse {
val name: String
val url: String
val url: String // PUBLIC URL FOR OPEN IN APP
val slug: String // USED FOR INTERNAL DATA
val apiName: String
val type: TvType
val posterUrl: String?
@ -100,6 +108,7 @@ interface SearchResponse {
data class AnimeSearchResponse(
override val name: String,
override val url: String,
override val slug: String,
override val apiName: String,
override val type: TvType,
@ -115,6 +124,7 @@ data class AnimeSearchResponse(
data class MovieSearchResponse(
override val name: String,
override val url: String,
override val slug: String,
override val apiName: String,
override val type: TvType,
@ -125,6 +135,7 @@ data class MovieSearchResponse(
data class TvSeriesSearchResponse(
override val name: String,
override val url: String,
override val slug: String,
override val apiName: String,
override val type: TvType,

View file

@ -8,6 +8,26 @@ import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
class MainActivity : AppCompatActivity() {
private fun AppCompatActivity.backPressed(): Boolean {
val currentFragment = supportFragmentManager.fragments.last {
it.isVisible
}
if (currentFragment != null && supportFragmentManager.fragments.size > 2) {
//MainActivity.showNavbar()
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim, R.anim.pop_enter, R.anim.pop_exit)
.remove(currentFragment)
.commitAllowingStateLoss()
return true
}
return false
}
override fun onBackPressed() {
if (backPressed()) return
super.onBackPressed()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

View file

@ -3,7 +3,9 @@ package com.lagradost.cloudstream3
import android.app.Activity
import android.content.res.Resources
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.ui.result.ResultFragment
object UIHelper {
val Int.toPx: Int get() = (this * Resources.getSystem().displayMetrics.density).toInt()
@ -11,16 +13,16 @@ object UIHelper {
val Int.toDp: Int get() = (this / Resources.getSystem().displayMetrics.density).toInt()
val Float.toDp: Float get() = (this / Resources.getSystem().displayMetrics.density)
fun Activity.loadResult(url: String, apiName: String) {
/*this.runOnUiThread {
fun AppCompatActivity.loadResult(url: String, slug: String, apiName: String) {
this.runOnUiThread {
this.supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim, R.anim.pop_enter, R.anim.pop_exit)
.add(R.id.homeRoot, ResultFragment().newInstance(url, apiName))
.add(R.id.homeRoot, ResultFragment().newInstance(url, slug, apiName))
.commit()
}*/
}
}
private fun Activity.getStatusBarHeight(): Int {
fun Activity.getStatusBarHeight(): Int {
var result = 0
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {

View file

@ -152,6 +152,7 @@ class ShiroProvider : MainAPI() {
returnValue.add(AnimeSearchResponse(
i.english ?: i.canonicalTitle,
"$mainUrl/${i.slug}",
i.slug,
this.name,
type,
"https://cdn.shiro.is/${i.image}",
@ -165,9 +166,9 @@ class ShiroProvider : MainAPI() {
return returnValue
}
override fun load(url: String): Any? {
override fun load(slug: String): Any? {
if (!autoLoadToken()) return null
val rurl = "https://tapi.shiro.is/anime/slug/${url}?token=${token}"
val rurl = "https://tapi.shiro.is/anime/slug/${slug}?token=${token}"
val response = khttp.get(rurl, timeout = 120.0)
val mapped = response.let { mapper.readValue<AnimePage>(it.text) }
val data = mapped.data
@ -183,7 +184,7 @@ class ShiroProvider : MainAPI() {
data.english,
data.japanese,
data.canonicalTitle ?: data.name.replace("Dubbed", ""),
url,
"$mainUrl/${slug}",
this.name,
getType(data.type ?: ""),
"https://cdn.shiro.is/${data.image}",
@ -197,6 +198,5 @@ class ShiroProvider : MainAPI() {
null,
null,
)
}
}

View file

@ -0,0 +1,74 @@
package com.lagradost.cloudstream3.ui.result
import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getApiFromName
import kotlinx.android.synthetic.main.result_episode.view.*
class EpisodeAdapter(
activity: Activity,
animeList: ArrayList<ResultEpisode>,
resView: RecyclerView,
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var cardList = animeList
private var activity: Activity = activity
var resView: RecyclerView? = resView
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return CardViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.result_episode, parent, false),
activity,
resView!!
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is CardViewHolder -> {
holder.bind(cardList[position])
}
}
}
override fun getItemCount(): Int {
return cardList.size
}
class CardViewHolder
constructor(itemView: View, _activity: Activity, resView: RecyclerView) : RecyclerView.ViewHolder(itemView) {
val activity = _activity
val episode_view_procentage: View = itemView.episode_view_procentage
val episode_view_procentage_off: View = itemView.episode_view_procentage_off
val episode_text: TextView = itemView.episode_text
val episode_extra: ImageView = itemView.episode_extra
val episode_play: ImageView = itemView.episode_play
fun bind(card: ResultEpisode) {
episode_text.text = card.name ?: "Episode ${card.episode}"
fun setWidth(v: View, procentage: Float) {
val param = LinearLayout.LayoutParams(
v.layoutParams.width,
v.layoutParams.height,
procentage
)
v.layoutParams = param
}
setWidth(episode_view_procentage, card.watchProgress)
setWidth(episode_view_procentage_off, 1 - card.watchProgress)
episode_play.setOnClickListener {
getApiFromName(card.apiName).loadLinks(card.data, card.id)
}
}
}
}

View file

@ -1,21 +1,63 @@
package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.view.marginBottom
import androidx.core.view.marginLeft
import androidx.core.view.marginRight
import androidx.core.view.marginTop
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.request.RequestOptions.bitmapTransform
import com.lagradost.cloudstream3.AnimeLoadResponse
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.UIHelper.getStatusBarHeight
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe
import jp.wasabeef.glide.transformations.BlurTransformation
import kotlinx.android.synthetic.main.fragment_result.*
import kotlinx.android.synthetic.main.fragment_search.*
const val MAX_SYNO_LENGH = 600
data class ResultEpisode(
val name: String?,
val episode: Int,
val data: Any,
val apiName: String,
val id: Int,
val watchProgress: Float, // 0-1
)
class ResultFragment : Fragment() {
fun newInstance(url: String, slug: String, apiName: String) =
ResultFragment().apply {
arguments = Bundle().apply {
putString("url", url)
putString("slug", slug)
putString("apiName", apiName)
}
}
private lateinit var viewModel: ResultViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
savedInstanceState: Bundle?,
): View? {
viewModel =
ViewModelProvider(this).get(ResultViewModel::class.java)
@ -23,7 +65,121 @@ class ResultFragment : Fragment() {
return inflater.inflate(R.layout.fragment_result, container, false)
}
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity?.fixPaddingStatusbar(result_scroll)
activity?.fixPaddingStatusbar(result_barstatus)
// activity?.fixPaddingStatusbar(result_toolbar)
val url = arguments?.getString("url")
val slug = arguments?.getString("slug")
val apiName = arguments?.getString("apiName")
result_scroll.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if(result_poster_blur == null) return@OnScrollChangeListener
result_poster_blur.alpha = maxOf(0f, (0.3f - scrollY / 1000f))
result_barstatus.alpha = scrollY / 200f
result_barstatus.visibility = if(scrollY > 0) View.VISIBLE else View.GONE
})
result_toolbar.setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
result_toolbar.setNavigationOnClickListener {
activity?.onBackPressed()
}
observe(viewModel.resultResponse) { data ->
when (data) {
is Resource.Success -> {
val d = data.value
if (d is LoadResponse) {
result_bookmark_button.text = "Watching"
if (d.year != null) {
result_year.visibility = View.VISIBLE
result_year.text = d.year.toString()
} else {
result_year.visibility = View.GONE
}
if (d.posterUrl != null) {
val glideUrl =
GlideUrl(d.posterUrl)
context!!.let {
/*
Glide.with(it)
.load(glideUrl)
.into(result_poster)*/
Glide.with(it)
.load(glideUrl)
.apply(bitmapTransform(BlurTransformation(10, 3)))
.into(result_poster_blur)
}
}
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder>? = activity?.let {
EpisodeAdapter(
it,
ArrayList(),
result_episodes,
)
}
result_episodes.adapter = adapter
result_episodes.layoutManager = GridLayoutManager(context, 1)
if (d is AnimeLoadResponse) {
val preferEnglish = true
val titleName = (if (preferEnglish) d.engName else d.japName) ?: d.name
result_title.text = titleName
result_toolbar.title = titleName
if (d.plot != null) {
var syno = d.plot
if (syno.length > MAX_SYNO_LENGH) {
syno = syno.substring(0, MAX_SYNO_LENGH) + "..."
}
result_descript.setOnClickListener {
val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext())
builder.setMessage(d.plot).setTitle("Synopsis")
.show()
}
result_descript.text = syno
} else {
result_descript.text = "No Plot found"
}
result_tags.text = (d.tags ?: ArrayList()).joinToString(separator = " | ")
val isDub = d.dubEpisodes != null && d.dubEpisodes.size > 0
val dataList = (if (isDub) d.dubEpisodes else d.subEpisodes)
if (dataList != null && apiName != null) {
val episodes = ArrayList<ResultEpisode>()
for ((index, i) in dataList.withIndex()) {
episodes.add(ResultEpisode(
null, // TODO ADD NAMES
index + 1, //TODO MAKE ABLE TO NOT HAVE SOME EPISODE
i,
apiName,
(slug + index).hashCode(),
(index * 0.1f),//TODO TEST; REMOVE
))
}
(result_episodes.adapter as EpisodeAdapter).cardList = episodes
(result_episodes.adapter as EpisodeAdapter).notifyDataSetChanged()
}
} else {
result_title.text = d.name
}
}
}
is Resource.Failure -> {
}
}
}
if (viewModel.resultResponse.value == null && apiName != null && slug != null)
viewModel.load(slug, apiName)
}
}

View file

@ -1,7 +1,25 @@
package com.lagradost.cloudstream3.ui.result
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.safeApiCall
import kotlinx.coroutines.launch
class ResultViewModel : ViewModel() {
private val _resultResponse: MutableLiveData<Resource<Any?>> = MutableLiveData()
val resultResponse: LiveData<Resource<Any?>> get() = _resultResponse
fun load(url: String, apiName:String) = viewModelScope.launch {
val data = safeApiCall {
getApiFromName(apiName).load(url)
}
_resultResponse.postValue(data)
}
}

View file

@ -8,6 +8,7 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
@ -105,7 +106,7 @@ class SearchAdapter(
}
bg.setOnClickListener {
activity.loadResult(card.url, card.apiName)
(activity as AppCompatActivity).loadResult(card.url, card.slug, card.apiName)
}
when (card) {

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="-90"
android:startColor="@color/transparent"
android:endColor="@color/bitDarkerGrayBackground"/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#FFFFFF" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M8,5v14l11,-7z"/>
</vector>

View file

@ -1,6 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:id="@+id/result_root"
android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/bitDarkerGrayBackground"
android:clickable="true"
android:focusable="true"
>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/result_barstatus"
android:background="@color/grayBackground"
android:layout_width="match_parent"
android:alpha="0"
tools:alpha="1"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/result_toolbar"
android:paddingTop="@dimen/navbarHeight"
app:title="Perfect Run"
app:layout_scrollFlags="scroll|enterAlways"
android:layout_width="match_parent" android:layout_height="wrap_content">
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout android:layout_width="match_parent" android:layout_height="200dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:alpha="0.3"
tools:src="@drawable/example_poster"
android:background="@color/darkBackground"
android:id="@+id/result_poster_blur"
android:contentDescription=""/>
<ImageView android:src="@drawable/background_shadow" android:layout_gravity="bottom"
android:layout_width="match_parent" android:layout_height="100dp">
</ImageView>
</FrameLayout>
<androidx.core.widget.NestedScrollView android:id="@+id/result_scroll" android:layout_width="match_parent" android:layout_height="wrap_content">
<LinearLayout
android:orientation="vertical"
android:layout_marginTop="100dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<GridLayout android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content">
<LinearLayout android:gravity="center_vertical" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content">
<TextView
tools:text="The Perfect Run"
android:id="@+id/result_title"
android:textSize="15sp"
android:textStyle="bold"
android:textColor="@color/textColor" android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
<TextView
android:id="@+id/result_year"
tools:text="2021"
android:textSize="15sp"
android:textColor="@color/textColor" android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:layout_gravity="end"
app:cornerRadius="1000dp"
android:id="@+id/result_bookmark_button"
tools:text="Bookmark"
app:rippleColor="@color/colorPrimary"
app:strokeColor="@color/colorPrimary"
android:textColor="@color/textColor"
android:textAllCaps="false"
android:backgroundTint="@color/colorPrimary"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="45dp">
</com.google.android.material.button.MaterialButton>
</GridLayout>
<TextView
android:id="@+id/result_tags"
android:layout_marginTop="10dp"
tools:text="Adventure | Fantasy World | Comedy | Ecchi | Action | Science Fiction | Fantasy"
android:gravity="center"
android:layout_width="match_parent" android:layout_height="wrap_content">
</TextView>
<TextView
android:layout_marginTop="10dp"
android:id="@+id/result_descript"
tools:text="Ryan Quicksave Romano is an eccentric adventurer with a strange power: he can create a save-point in time and redo his life whenever he dies. Arriving in New Rome, the glitzy capital of sin of a rebuilding Europe, he finds the city torn between mega-corporations, sponsored heroes, superpowered criminals, and true monsters. It's a time of chaos, where potions can grant the power to rule the world and dangers lurk everywhere. "
android:layout_width="match_parent" android:layout_height="wrap_content">
</TextView>
<TextView
android:textColor="@color/textColor"
android:layout_marginTop="10dp"
android:id="@+id/result_episodes_text"
tools:text="8 Episodes"
android:gravity="start"
android:layout_width="match_parent" android:layout_height="wrap_content">
</TextView>
<androidx.recyclerview.widget.RecyclerView
tools:listitem="@layout/result_episode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/result_episodes"
/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<!--
<androidx.cardview.widget.CardView
android:layout_width="100dp"
android:layout_height="150dp"
app:cardCornerRadius="@dimen/roundedImageRadius"
android:elevation="10dp"
>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:id="@+id/result_poster"
android:clickable="true"
android:focusable="true"
tools:src="@drawable/example_poster"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/search_poster_descript"/>
</androidx.cardview.widget.CardView>-->
</FrameLayout>

View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
app:cardCornerRadius="@dimen/roundedImageRadius"
app:cardBackgroundColor="@color/itemBackground"
android:layout_marginBottom="2dp"
>
<!-- IDK BUT THIS DOES NOT SEAM LIKE A GOOD WAY OF DOING IT -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<View
android:layout_weight="0.5"
android:id="@+id/episode_view_procentage"
android:alpha="0.2"
android:background="@color/colorPrimary"
android:layout_width="0dp"
android:layout_height="match_parent">
</View>
<View
android:id="@+id/episode_view_procentage_off"
android:layout_weight="0.10"
android:alpha="0"
android:background="@color/transparent"
android:layout_width="0dp"
android:layout_height="match_parent">
</View>
</LinearLayout>
<GridLayout android:layout_width="match_parent" android:layout_height="match_parent">
<ImageView
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_gravity="center_vertical"
android:id="@+id/episode_play"
android:background="?selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_play_arrow_24"
android:contentDescription="@string/episode_play_descript"/>
<TextView
android:id="@+id/episode_text"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_gravity="center_vertical" android:gravity="center_vertical" tools:text="Episode 1"
android:textColor="@color/textColor" android:layout_width="wrap_content"
android:layout_height="match_parent">
</TextView>
<ImageView
android:layout_marginEnd="10dp"
android:layout_marginStart="10dp"
android:id="@+id/episode_extra"
android:background="?selectableItemBackgroundBorderless"
android:layout_gravity="center_vertical|end"
android:src="@drawable/ic_baseline_more_vert_24"
android:contentDescription="@string/episode_more_options_descript"/>
</GridLayout>
</androidx.cardview.widget.CardView>

View file

@ -10,4 +10,6 @@
<string name="search_poster_descript">Poster</string>
<string name="no_data">No Data</string>
<string name="shadow_descript">Shadow</string>
<string name="episode_more_options_descript">More Options</string>
<string name="episode_play_descript">Play Episode</string>
</resources>