homepage somewhat working

This commit is contained in:
LagradOst 2021-07-29 02:19:42 +02:00
parent f270f9f551
commit 61323b5c56
15 changed files with 545 additions and 51 deletions

View file

@ -47,6 +47,13 @@ object APIHolder {
return apis[defProvider]
}
fun getApiFromNameNull(apiName: String?): MainAPI? {
for (api in apis) {
if (apiName == api.name)
return api
}
return null
}
fun LoadResponse.getId(): Int {
return url.replace(getApiFromName(apiName).mainUrl, "").hashCode()
@ -70,14 +77,19 @@ abstract class MainAPI {
/**If link is stored in the "data" string, so links can be instantly loaded*/
open val instantLinkLoading = false
open val hasQuickSearch = false
/**Set false if links require referer or for some reason cant be played on a chromecast*/
open val hasChromecastSupport = true
/**If all links are m3u8 then set this to false*/
open val hasDownloadSupport = true
open val hasMainPage = false
open val hasQuickSearch = false
open fun getMainPage() : HomePageResponse? {
return null
}
open fun search(query: String): ArrayList<SearchResponse>? {
return null
}
@ -161,6 +173,15 @@ fun TvType.isMovieType(): Boolean {
data class SubtitleFile(val lang: String, val url: String)
class HomePageResponse(
val items: List<HomePageList>
)
class HomePageList(
val name: String,
val list: List<SearchResponse>
)
interface SearchResponse {
val name: String
val url: String // PUBLIC URL FOR OPEN IN APP

View file

@ -9,6 +9,7 @@ import java.net.URLEncoder
import java.util.*
import kotlin.collections.ArrayList
const val SHIRO_TIMEOUT_TIME = 60.0
class ShiroProvider : MainAPI() {
companion object {
@ -55,6 +56,9 @@ class ShiroProvider : MainAPI() {
override val hasQuickSearch: Boolean
get() = true
override val hasMainPage: Boolean
get() = true
data class ShiroSearchResponseShow(
@JsonProperty("image") val image: String,
@JsonProperty("_id") val _id: String,
@ -134,6 +138,46 @@ class ShiroProvider : MainAPI() {
@JsonProperty("status") val status: String,
)
data class ShiroHomePageData(
@JsonProperty("trending_animes") val trending_animes: List<AnimePageData>,
@JsonProperty("ongoing_animes") val ongoing_animes: List<AnimePageData>,
@JsonProperty("latest_animes") val latest_animes: List<AnimePageData>,
@JsonProperty("latest_episodes") val latest_episodes: List<ShiroEpisodes>,
)
data class ShiroHomePage(
@JsonProperty("status") val status: String,
@JsonProperty("data") val data: ShiroHomePageData,
@JsonProperty("random") var random: AnimePage?,
)
private fun toHomePageList(list: List<AnimePageData>, name: String): HomePageList {
return HomePageList(name, list.map { data ->
val type = getType(data.type)
val isDubbed =
data.language == "dubbed"
val set: EnumSet<DubStatus> =
EnumSet.of(if (isDubbed) DubStatus.Dubbed else DubStatus.Subbed)
val episodeCount = data.episodeCount?.toIntOrNull()
return@map AnimeSearchResponse(
data.name.replace("Dubbed", ""), // i.english ?: i.canonicalTitle,
"$mainUrl/anime/${data.slug}",
data.slug,
this.name,
type,
"https://cdn.shiro.is/${data.image}",
data.year?.toIntOrNull(),
data.canonicalTitle,
set,
if (isDubbed) episodeCount else null,
if (!isDubbed) episodeCount else null,
)
}.toList())
}
private fun turnSearchIntoResponse(data: ShiroSearchResponseShow): AnimeSearchResponse {
val type = getType(data.type)
val isDubbed =
@ -141,12 +185,9 @@ class ShiroProvider : MainAPI() {
data.language == "dubbed"
else
data.slug.contains("dubbed")
val set: EnumSet<DubStatus> = EnumSet.noneOf(DubStatus::class.java)
val set: EnumSet<DubStatus> =
EnumSet.of(if (isDubbed) DubStatus.Dubbed else DubStatus.Subbed)
if (isDubbed)
set.add(DubStatus.Dubbed)
else
set.add(DubStatus.Subbed)
val episodeCount = data.episodeCount?.toIntOrNull()
return AnimeSearchResponse(
@ -164,7 +205,26 @@ class ShiroProvider : MainAPI() {
)
}
override fun quickSearch(query: String): ArrayList<SearchResponse> {
override fun getMainPage(): HomePageResponse? {
if (!autoLoadToken()) return null
val url = "https://tapi.shiro.is/latest?token=$token"
val response = khttp.get(url, timeout = SHIRO_TIMEOUT_TIME)
val res = response.text.let { mapper.readValue<ShiroHomePage>(it) }
val d = res.data
return HomePageResponse(
listOf(
toHomePageList(d.trending_animes, "Trending"),
toHomePageList(d.ongoing_animes, "Ongoing"),
toHomePageList(d.latest_animes, "Latest")
)
)
}
override fun quickSearch(query: String): ArrayList<SearchResponse>? {
if (!autoLoadToken()) return null
val returnValue: ArrayList<SearchResponse> = ArrayList()
val response = khttp.get(

View file

@ -5,6 +5,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import com.bumptech.glide.load.HttpException
import com.lagradost.cloudstream3.ui.ErrorLoadingException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.SocketTimeoutException
@ -67,6 +68,9 @@ suspend fun <T> safeApiCall(
is UnknownHostException -> {
Resource.Failure(true, null, null, "Cannot connect to server, try again later.")
}
is ErrorLoadingException -> {
Resource.Failure(true, null, null, "Error loading, try again later.")
}
else -> {
val stackTraceMsg = throwable.localizedMessage + "\n\n" + throwable.stackTrace.joinToString(
separator = "\n"

View file

@ -0,0 +1,47 @@
package com.lagradost.cloudstream3.ui
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.utils.ExtractorLink
class ErrorLoadingException(message: String) : Exception(message)
class APIRepository(val api: MainAPI) {
val name : String get() = api.name
val mainUrl : String get() = api.mainUrl
suspend fun load(url: String): Resource<LoadResponse> {
return safeApiCall {
api.load(url) ?: throw ErrorLoadingException("Error Loading")
}
}
suspend fun search(query: String): Resource<ArrayList<SearchResponse>> {
return safeApiCall {
api.search(query) ?: throw ErrorLoadingException("Error Loading")
}
}
suspend fun quickSearch(query: String): Resource<ArrayList<SearchResponse>> {
return safeApiCall {
api.quickSearch(query) ?: throw ErrorLoadingException("Error Loading")
}
}
suspend fun getMainPage(): Resource<HomePageResponse> {
return safeApiCall {
api.getMainPage() ?: throw ErrorLoadingException("Error Loading")
}
}
fun loadLinks(
data: String,
isCasting: Boolean,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
): Boolean {
return normalSafeApiCall { api.loadLinks(data, isCasting, subtitleCallback, callback) } ?: false
}
}

View file

@ -0,0 +1,105 @@
package com.lagradost.cloudstream3.ui.home
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.model.GlideUrl
import com.lagradost.cloudstream3.*
import kotlinx.android.synthetic.main.home_result_grid.view.*
class HomeChildItemAdapter(
var cardList: List<Any>,
private val clickCallback: (SearchResponse) -> Unit
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layout = R.layout.home_result_grid
return CardViewHolder(
LayoutInflater.from(parent.context).inflate(layout, parent, false), clickCallback
)
}
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, private val clickCallback: (SearchResponse) -> Unit) :
RecyclerView.ViewHolder(itemView) {
val cardView: ImageView = itemView.imageView
private val cardText: TextView = itemView.imageText
private val textType: TextView? = itemView.text_type
// val search_result_lang: ImageView? = itemView.search_result_lang
private val textIsDub: View? = itemView.text_is_dub
private val textIsSub: View? = itemView.text_is_sub
//val cardTextExtra: TextView? = itemView.imageTextExtra
//val imageTextProvider: TextView? = itemView.imageTextProvider
private val bg: CardView = itemView.backgroundCard
fun bind(card: Any) {
if (card is SearchResponse) { // GENERIC
textType?.text = when (card.type) {
TvType.Anime -> "Anime"
TvType.Movie -> "Movie"
TvType.ONA -> "ONA"
TvType.TvSeries -> "TV"
}
// search_result_lang?.visibility = View.GONE
textIsDub?.visibility = View.GONE
textIsSub?.visibility = View.GONE
cardText.text = card.name
//imageTextProvider.text = card.apiName
if (!card.posterUrl.isNullOrEmpty()) {
val glideUrl =
GlideUrl(card.posterUrl)
Glide.with(cardView.context)
.load(glideUrl)
.into(cardView)
}
bg.setOnClickListener {
clickCallback.invoke(card)
// (activity as AppCompatActivity).loadResult(card.url, card.slug, card.apiName)
}
when (card) {
is AnimeSearchResponse -> {
if (card.dubStatus?.size == 1) {
//search_result_lang?.visibility = View.VISIBLE
if (card.dubStatus.contains(DubStatus.Dubbed)) {
textIsDub?.visibility = View.VISIBLE
//search_result_lang?.setColorFilter(ContextCompat.getColor(activity, R.color.dubColor))
} else if (card.dubStatus.contains(DubStatus.Subbed)) {
//search_result_lang?.setColorFilter(ContextCompat.getColor(activity, R.color.subColor))
textIsSub?.visibility = View.VISIBLE
}
}
}
}
}
}
}
}

View file

@ -4,14 +4,20 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.HOMEPAGE_API
import kotlinx.android.synthetic.main.fragment_child_downloads.*
import kotlinx.android.synthetic.main.fragment_home.*
class HomeFragment : Fragment() {
private lateinit var homeViewModel: HomeViewModel
override fun onCreateView(
@ -21,11 +27,40 @@ class HomeFragment : Fragment() {
): View? {
homeViewModel =
ViewModelProvider(this).get(HomeViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_home, container, false)
val textView: TextView = root.findViewById(R.id.text_home)
homeViewModel.text.observe(viewLifecycleOwner, Observer {
textView.text = it
})
return root
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
observe(homeViewModel.apiName) {
context?.setKey(HOMEPAGE_API, it)
}
observe(homeViewModel.page) {
when (it) {
is Resource.Success -> {
val d = it.value
(home_master_recycler?.adapter as ParentItemAdapter?)?.itemList = d.items
home_master_recycler?.adapter?.notifyDataSetChanged()
}
is Resource.Failure -> {
}
is Resource.Loading -> {
}
}
}
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> = ParentItemAdapter(listOf()) {
}
home_master_recycler.adapter = adapter
home_master_recycler.layoutManager = GridLayoutManager(context, 1)
homeViewModel.load(context?.getKey(HOMEPAGE_API))
}
}

View file

@ -0,0 +1,51 @@
package com.lagradost.cloudstream3.ui.home
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.HomePageList
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.android.synthetic.main.homepage_parent.view.*
class ParentItemAdapter(
var itemList: List<HomePageList>,
private val clickCallback: (SearchResponse) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, i: Int): ParentViewHolder {
val layout = R.layout.homepage_parent
return ParentViewHolder(
LayoutInflater.from(parent.context).inflate(layout, parent, false), clickCallback
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ParentViewHolder -> {
holder.bind(itemList[position])
}
}
}
override fun getItemCount(): Int {
return itemList.size
}
class ParentViewHolder
constructor(itemView: View, private val clickCallback: (SearchResponse) -> Unit) :
RecyclerView.ViewHolder(itemView) {
val title: TextView = itemView.home_parent_item_title
val recyclerView: RecyclerView = itemView.home_child_recyclerview
fun bind(info: HomePageList) {
title.text = info.name
recyclerView.adapter = HomeChildItemAdapter(info.list, clickCallback)
recyclerView.layoutManager = GridLayoutManager(itemView.context, 1)
(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
}
}
}

View file

@ -3,11 +3,35 @@ package com.lagradost.cloudstream3.ui.home
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.HomePageResponse
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.ui.APIRepository
import kotlinx.coroutines.launch
class HomeViewModel : ViewModel() {
var repo: APIRepository? = null
private val _text = MutableLiveData<String>().apply {
value = "This is home Fragment"
private val _apiName = MutableLiveData<String>()
val apiName: LiveData<String> = _apiName
private val _page = MutableLiveData<Resource<HomePageResponse>>()
val page: LiveData<Resource<HomePageResponse>> = _page
private fun autoloadRepo(): APIRepository {
return APIRepository(apis.first { it.hasMainPage })
}
fun load(preferredApiName: String?) = viewModelScope.launch {
val api = getApiFromNameNull(preferredApiName)
repo = if (api?.hasMainPage == true) {
APIRepository(api)
} else {
autoloadRepo()
}
_page.postValue(Resource.Loading())
_page.postValue(repo?.getMainPage())
}
val text: LiveData<String> = _text
}

View file

@ -7,6 +7,7 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
@ -20,6 +21,8 @@ const val EPISODE_RANGE_SIZE = 50
const val EPISODE_RANGE_OVERLOAD = 60
class ResultViewModel : ViewModel() {
var repo : APIRepository? = null
private val _resultResponse: MutableLiveData<Resource<Any?>> = MutableLiveData()
private val _episodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
private val _publicEpisodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
@ -137,19 +140,14 @@ class ResultViewModel : ViewModel() {
updateEpisodes(context, null, copy, selectedSeason.value)
}
// THIS SHOULD AT LEAST CLEAN IT UP, SO APIS CAN SWITCH DOMAIN
private fun getId(url: String, api: MainAPI): Int {
return url.replace(api.mainUrl, "").hashCode()
}
fun load(context: Context, url: String, apiName: String) = viewModelScope.launch {
_resultResponse.postValue(Resource.Loading(url))
_apiName.postValue(apiName)
val api = getApiFromName(apiName)
val data = safeApiCall {
api.load(url)
}
repo = APIRepository(api)
val data = repo?.load(url)
_resultResponse.postValue(data)
@ -278,7 +276,7 @@ class ResultViewModel : ViewModel() {
val links = ArrayList<ExtractorLink>()
val subs = ArrayList<SubtitleFile>()
return safeApiCall {
getApiFromName(_apiName.value).loadLinks(data, isCasting, { subtitleFile ->
repo?.loadLinks(data, isCasting, { subtitleFile ->
if (!subs.any { it.url == subtitleFile.url }) {
subs.add(subtitleFile)
_allEpisodesSubs.value?.set(id, subs)

View file

@ -7,12 +7,14 @@ import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.APIHolder.allApi
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.ui.APIRepository
import kotlinx.coroutines.launch
class SearchViewModel : ViewModel() {
private val _searchResponse: MutableLiveData<Resource<ArrayList<Any>>> = MutableLiveData()
val searchResponse: LiveData<Resource<ArrayList<Any>>> get() = _searchResponse
var searchCounter = 0
private val repo = APIRepository(allApi)
private fun clearSearch() {
_searchResponse.postValue(Resource.Success(ArrayList()))
@ -26,9 +28,8 @@ class SearchViewModel : ViewModel() {
}
val localSearchCounter = searchCounter
_searchResponse.postValue(Resource.Loading())
val data = safeApiCall {
allApi.search(query)
}
val data = repo.search(query)
if(localSearchCounter != searchCounter) return@launch
_searchResponse.postValue(data as Resource<ArrayList<Any>>?)
}
@ -41,9 +42,7 @@ class SearchViewModel : ViewModel() {
}
val localSearchCounter = searchCounter
_searchResponse.postValue(Resource.Loading())
val data = safeApiCall {
allApi.quickSearch(query)
}
val data = repo.quickSearch(query)
if(localSearchCounter != searchCounter) return@launch
_searchResponse.postValue(data as Resource<ArrayList<Any>>?)

View file

@ -9,6 +9,7 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule
const val DOWNLOAD_HEADER_CACHE = "download_header_cache"
const val DOWNLOAD_EPISODE_CACHE = "download_episode_cache"
const val VIDEO_PLAYER_BRIGHTNESS = "video_player_alpha"
const val HOMEPAGE_API = "home_api_used"
const val PREFERENCES_NAME: String = "rebuild_preference"

View file

@ -7,17 +7,10 @@
android:layout_height="match_parent"
tools:context=".ui.home.HomeFragment">
<TextView
android:id="@+id/text_home"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_master_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
android:layout_height="match_parent"
tools:listitem="@layout/homepage_parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- android:layout_width="114dp"
android:layout_height="180dp"-->
<androidx.cardview.widget.CardView
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:layout_margin="2dp"
android:layout_width="114dp"
android:layout_height="180dp"
android:layout_marginBottom="2dp"
android:elevation="10dp"
app:cardCornerRadius="@dimen/roundedImageRadius"
android:id="@+id/backgroundCard"
app:cardBackgroundColor="@color/darkBackground"
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">
<ImageView
android:duplicateParentState="true"
android:id="@+id/imageView"
tools:src="@drawable/example_poster"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/search_poster_descript"/>
<ImageView
android:focusable="false"
android:clickable="false"
android:layout_width="match_parent"
android:layout_height="50dp"
android:src="@drawable/title_shadow"
android:layout_gravity="bottom" android:contentDescription="@string/shadow_descript">
</ImageView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_gravity="bottom"
android:paddingBottom="5dp"
android:paddingTop="5dp"
android:textColor="@color/textColor"
android:id="@+id/imageText"
android:textStyle="bold"
android:maxLines="2"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:ellipsize="end"
/>
<TextView
android:text="Movie"
android:visibility="gone"
android:id="@+id/text_type"
android:textColor="@color/textColor"
android:paddingRight="10dp"
android:paddingLeft="10dp"
android:paddingTop="4dp"
android:layout_marginBottom="5dp"
android:layout_gravity="start"
android:paddingBottom="8dp"
android:minWidth="50dp"
android:gravity="center"
android:background="@drawable/type_bg_color"
android:layout_width="wrap_content" android:layout_height="wrap_content">
</TextView>
<!--<View
android:id="@+id/search_result_lang"
android:layout_gravity="bottom"
android:layout_width="match_parent"
android:layout_height="4dp"
android:alpha="0.9">
</View>-->
<!--<ImageView
android:src="@drawable/ic_baseline_bookmark_24"
android:id="@+id/search_result_lang"
android:layout_gravity="right"
android:layout_marginTop="-5dp"
android:layout_marginRight="-6.5dp"
android:layout_width="30dp"
android:layout_height="30dp">
</ImageView>-->
<LinearLayout
android:orientation="vertical"
android:layout_gravity="end"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--
<ImageView android:id="@+id/text_is_dub" android:tint="@color/colorPrimary"
android:src="@drawable/ic_baseline_subtitles_24" android:layout_width="wrap_content"
android:layout_height="20dp">
</ImageView>-->
<TextView
android:text="@string/app_dubbed_text"
android:id="@+id/text_is_dub"
android:textColor="@color/textColor"
android:paddingRight="10dp"
android:paddingLeft="10dp"
android:paddingTop="4dp"
android:layout_marginBottom="5dp"
android:layout_gravity="end"
android:paddingBottom="4dp"
android:minWidth="50dp"
android:gravity="center"
android:background="@drawable/dub_bg_color"
android:layout_width="wrap_content" android:layout_height="wrap_content">
</TextView>
<TextView
android:id="@+id/text_is_sub"
android:text="@string/app_subbed_text"
android:layout_gravity="end"
android:textColor="@color/textColor"
android:paddingRight="10dp"
android:paddingLeft="10dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:minWidth="50dp"
android:gravity="center"
android:background="@drawable/sub_bg_color"
android:layout_width="wrap_content" android:layout_height="wrap_content"
>
</TextView>
</LinearLayout>
</androidx.cardview.widget.CardView>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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:orientation="vertical"
android:background="@color/colorPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/home_parent_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12sp"
android:textSize="18sp"
tools:text="Trending"
/>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="230dp"
android:layout_marginBottom="20dp"
>
<androidx.recyclerview.widget.RecyclerView
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:id="@+id/home_child_recyclerview"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:listitem="@layout/home_result_grid"
/>
</RelativeLayout>
</LinearLayout>

View file

@ -9,10 +9,8 @@
android:focusable="true"
android:clickable="true"
android:id="@+id/search_result_root"
>
<androidx.cardview.widget.CardView
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:layout_margin="2dp"
android:layout_width="match_parent"