Merge branch 'recloudstream:master' into master

This commit is contained in:
self-similarity 2023-10-25 16:15:27 +00:00 committed by GitHub
commit 69d87b7b36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 714 additions and 206 deletions

View file

@ -51,7 +51,7 @@ android {
}
// https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading
compileSdk = 33 // android 14 is fucked
compileSdk = 34 // android 14 is fucked
buildToolsVersion = "34.0.0"
defaultConfig {
@ -157,7 +157,7 @@ dependencies {
implementation("androidx.test.ext:junit-ktx:1.1.5")
testImplementation("org.json:json:20230618")
implementation("androidx.core:core-ktx:1.10.1") // need 34 for higher
implementation("androidx.core:core-ktx:1.12.0") // need 34 for higher
implementation("androidx.appcompat:appcompat:1.6.1") // need target 32 for 1.5.0
// dont change this to 1.6.0 it looks ugly af
@ -165,10 +165,10 @@ dependencies {
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
// need 34 for higher
implementation("androidx.navigation:navigation-fragment-ktx:2.6.0")
implementation("androidx.navigation:navigation-ui-ktx:2.6.0")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.4")
implementation("androidx.navigation:navigation-ui-ktx:2.7.4")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
@ -232,7 +232,7 @@ dependencies {
// Networking
// implementation("com.squareup.okhttp3:okhttp:4.9.2")
// implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1")
implementation("com.github.Blatzar:NiceHttp:0.4.3")
implementation("com.github.Blatzar:NiceHttp:0.4.4") // http library
// To fix SSL fuckery on android 9
implementation("org.conscrypt:conscrypt-android:2.5.2")
// Util to skip the URI file fuckery 🙏

View file

@ -19,6 +19,7 @@ import com.lagradost.cloudstream3.databinding.FragmentSearchBinding
import com.lagradost.cloudstream3.databinding.FragmentSearchTvBinding
import com.lagradost.cloudstream3.databinding.HomeResultGridBinding
import com.lagradost.cloudstream3.databinding.HomepageParentBinding
import com.lagradost.cloudstream3.databinding.HomepageParentEmulatorBinding
import com.lagradost.cloudstream3.databinding.HomepageParentTvBinding
import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutBinding
import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutTvBinding
@ -119,8 +120,9 @@ class ExampleInstrumentedTest {
// testAllLayouts<HomeScrollViewBinding>(activity, R.layout.home_scroll_view, R.layout.home_scroll_view_tv)
// testAllLayouts<HomeScrollViewTvBinding>(activity, R.layout.home_scroll_view, R.layout.home_scroll_view_tv)
testAllLayouts<HomepageParentTvBinding>(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent)
testAllLayouts<HomepageParentBinding>(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent)
testAllLayouts<HomepageParentTvBinding>(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent_emulator, R.layout.homepage_parent)
testAllLayouts<HomepageParentEmulatorBinding>(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent_emulator, R.layout.homepage_parent)
testAllLayouts<HomepageParentBinding>(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent_emulator, R.layout.homepage_parent)
testAllLayouts<FragmentLibraryTvBinding>(activity, R.layout.fragment_library_tv, R.layout.fragment_library)
testAllLayouts<FragmentLibraryBinding>(activity, R.layout.fragment_library_tv, R.layout.fragment_library)

View file

@ -662,7 +662,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
val isAtHome =
navController?.currentDestination?.matchDestination(R.id.navigation_home) == true
if (isAtHome && isTrueTvSettings()) {
if (isAtHome && isTvSettings()) {
showConfirmExitDialog()
} else {
super.onBackPressed()

View file

@ -8,7 +8,9 @@ import com.lagradost.cloudstream3.syncproviders.SyncIdName
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.library.ListSorting
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
@ -69,29 +71,52 @@ class LocalList : SyncAPI {
}?.distinctBy { it.first } ?: return null
val list = ioWork {
watchStatusIds.groupBy {
it.second.stringRes
}.mapValues { group ->
val isTrueTv = isTrueTvSettings()
val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate {
// None is not something to display
it.stringRes to emptyList<SyncAPI.LibraryItem>()
} + mapOf(
R.string.favorites_list_name to emptyList()
) + if (!isTrueTv) {
mapOf(
R.string.subscription_list_name to emptyList()
)
} else {
emptyMap()
}
val watchStatusMap = watchStatusIds.groupBy { it.second.stringRes }.mapValues { group ->
group.value.mapNotNull {
getBookmarkedData(it.first)?.toLibraryItem(it.first.toString())
}
} + mapOf(R.string.subscription_list_name to getAllSubscriptions().mapNotNull {
}
val favoritesMap = mapOf(R.string.favorites_list_name to getAllFavorites().mapNotNull {
it.toLibraryItem()
})
// Don't show subscriptions on TV
val result = if (isTrueTv) {
baseMap + watchStatusMap + favoritesMap
} else {
val subscriptionsMap = mapOf(R.string.subscription_list_name to getAllSubscriptions().mapNotNull {
it.toLibraryItem()
})
baseMap + watchStatusMap + subscriptionsMap + favoritesMap
}
result
}
val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate {
// None is not something to display
it.stringRes to emptyList<SyncAPI.LibraryItem>()
} + mapOf(R.string.subscription_list_name to emptyList())
return SyncAPI.LibraryMetadata(
(baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) },
list.map { SyncAPI.LibraryList(txt(it.key), it.value) },
setOf(
ListSorting.AlphabeticalA,
ListSorting.AlphabeticalZ,
// ListSorting.UpdatedNew,
// ListSorting.UpdatedOld,
ListSorting.UpdatedNew,
ListSorting.UpdatedOld,
// ListSorting.RatingHigh,
// ListSorting.RatingLow,
)

View file

@ -15,7 +15,8 @@ import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable
class LoadClickCallback(
@ -34,11 +35,13 @@ open class ParentItemAdapter(
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val root = LayoutInflater.from(parent.context).inflate(
if (isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent,
parent,
false
)
val layoutResId = when {
isTrueTvSettings() -> R.layout.homepage_parent_tv
parent.context.isEmulatorSettings() -> R.layout.homepage_parent_emulator
else -> R.layout.homepage_parent
}
val root = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false)
val binding = HomepageParentBinding.bind(root)
@ -234,7 +237,7 @@ open class ParentItemAdapter(
})
//(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
if (!isTvSettings()) {
if (!isTrueTvSettings()) {
title.setOnClickListener {
moreInfoClickCallback.invoke(expand)
}

View file

@ -35,6 +35,7 @@ import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
@ -81,6 +82,28 @@ class HomeParentItemAdapterPreview(
parent,
false
) else FragmentHomeHeadBinding.inflate(inflater, parent, false)
if (binding is FragmentHomeHeadTvBinding && parent.context.isEmulatorSettings()) {
binding.homeBookmarkParentItemMoreInfo.isVisible = true
val marginInDp = 50
val density = binding.horizontalScrollChips.context.resources.displayMetrics.density
val marginInPixels = (marginInDp * density).toInt()
val params = binding.horizontalScrollChips.layoutParams as ViewGroup.MarginLayoutParams
params.marginEnd = marginInPixels
binding.horizontalScrollChips.layoutParams = params
binding.homeWatchParentItemTitle.setCompoundDrawablesWithIntrinsicBounds(
null,
null,
ContextCompat.getDrawable(
parent.context,
R.drawable.ic_baseline_arrow_forward_24
),
null
)
}
HeaderViewHolder(
binding,
viewModel,
@ -553,12 +576,19 @@ class HomeParentItemAdapterPreview(
resumeHolder.isVisible = resumeWatching.isNotEmpty()
resumeAdapter.updateList(resumeWatching)
if (binding is FragmentHomeHeadBinding) {
binding.homeWatchParentItemTitle.setOnClickListener {
if (
binding is FragmentHomeHeadBinding ||
binding is FragmentHomeHeadTvBinding &&
binding.root.context.isEmulatorSettings()
) {
val title = (binding as? FragmentHomeHeadBinding)?.homeWatchParentItemTitle
?: (binding as? FragmentHomeHeadTvBinding)?.homeWatchParentItemTitle
title?.setOnClickListener {
viewModel.popup(
HomeViewModel.ExpandableHomepageList(
HomePageList(
binding.homeWatchParentItemTitle.text.toString(),
title.text.toString(),
resumeWatching,
false
), 1, false
@ -576,8 +606,15 @@ class HomeParentItemAdapterPreview(
bookmarkHolder.isVisible = visible
bookmarkAdapter.updateList(list)
if (binding is FragmentHomeHeadBinding) {
binding.homeBookmarkParentItemTitle.setOnClickListener {
if (
binding is FragmentHomeHeadBinding ||
binding is FragmentHomeHeadTvBinding &&
binding.root.context.isEmulatorSettings()
) {
val title = (binding as? FragmentHomeHeadBinding)?.homeBookmarkParentItemTitle
?: (binding as? FragmentHomeHeadTvBinding)?.homeBookmarkParentItemTitle
title?.setOnClickListener {
val items = toggleList.map { it.first }.filter { it.isChecked }
if (items.isEmpty()) return@setOnClickListener // we don't want to show an empty dialog
val textSum = items

View file

@ -8,12 +8,14 @@ import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.FOCUS_AFTER_DESCENDANTS
import android.view.ViewGroup.FOCUS_BLOCK_DESCENDANTS
import android.view.animation.AlphaAnimation
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
@ -137,6 +139,13 @@ class LibraryFragment : Fragment() {
tag = "tv_no_focus_tag"
}
// Set the color for the search exit icon to the correct theme text color
val searchExitIcon = binding?.mainSearch?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
val searchExitIconColor = TypedValue()
activity?.theme?.resolveAttribute(android.R.attr.textColor, searchExitIconColor, true)
searchExitIcon?.setColorFilter(searchExitIconColor.data)
binding?.mainSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
libraryViewModel.sort(ListSorting.Query, query)

View file

@ -4,6 +4,7 @@ import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -215,10 +216,16 @@ class QuickSearchFragment : Fragment() {
binding?.quickSearch?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
//val searchMagIcon =
// binding.quickSearch.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
// binding?.quickSearch?.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
//searchMagIcon?.scaleX = 0.65f
//searchMagIcon?.scaleY = 0.65f
// searchMagIcon?.scaleX = 0.65f
// searchMagIcon?.scaleY = 0.65f
// Set the color for the search exit icon to the correct theme text color
val searchExitIconColor = TypedValue()
activity?.theme?.resolveAttribute(android.R.attr.textColor, searchExitIconColor, true)
searchExitIcon?.setColorFilter(searchExitIconColor.data)
binding?.quickSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {

View file

@ -17,7 +17,6 @@ import android.view.animation.DecelerateInterpolator
import android.widget.AbsListView
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
@ -66,6 +65,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogText
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
@ -445,6 +445,20 @@ open class ResultFragmentPhone : FullScreenPlayer() {
?: txt(R.string.no_data).asStringNull(context) ?: ""
CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT)
}
resultFavorite.setOnClickListener {
val isFavorite =
viewModel.toggleFavoriteStatus() ?: return@setOnClickListener
val message = if (isFavorite) {
R.string.favorite_added
} else {
R.string.favorite_removed
}
val name = (viewModel.page.value as? Resource.Success)?.value?.title
?: txt(R.string.no_data).asStringNull(context) ?: ""
CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT)
}
mediaRouteButton.apply {
val chromecastSupport = api?.hasChromecastSupport == true
alpha = if (chromecastSupport) 1f else 0.3f
@ -564,6 +578,19 @@ open class ResultFragmentPhone : FullScreenPlayer() {
binding?.resultSubscribe?.setImageResource(drawable)
}
observeNullable(viewModel.favoriteStatus) { isFavorite ->
binding?.resultFavorite?.isVisible = isFavorite != null
if (isFavorite == null) return@observeNullable
val drawable = if (isFavorite) {
R.drawable.ic_baseline_favorite_24
} else {
R.drawable.ic_baseline_favorite_border_24
}
binding?.resultFavorite?.setImageResource(drawable)
}
observe(viewModel.trailers) { trailers ->
setTrailers(trailers.flatMap { it.mirros }) // I dont care about subtitles yet!
}
@ -654,14 +681,13 @@ open class ResultFragmentPhone : FullScreenPlayer() {
resultPoster.setImage(d.posterImage)
resultPosterBackground.setImage(d.posterBackgroundImage)
resultDescription.setTextHtml(d.plotText)
resultDescription.setOnClickListener { view ->
// todo bottom view?
view.context?.let { ctx ->
val builder: AlertDialog.Builder =
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
builder.setMessage(d.plotText.asString(ctx).html())
.setTitle(d.plotHeaderText.asString(ctx))
.show()
resultDescription.setOnClickListener {
activity?.let { activity ->
activity.showBottomDialogText(
d.titleText.asString(activity),
d.plotText.asString(activity).html(),
{}
)
}
}
@ -852,16 +878,11 @@ open class ResultFragmentPhone : FullScreenPlayer() {
setRecommendations(recommendations, null)
}
observe(viewModel.episodeSynopsis) { description ->
// TODO bottom dialog
view.context?.let { ctx ->
val builder: AlertDialog.Builder =
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
builder.setMessage(description.html())
.setTitle(R.string.synopsis)
.setOnDismissListener {
viewModel.releaseEpisodeSynopsis()
}
.show()
activity?.let { activity ->
activity.showBottomDialogText(
activity.getString(R.string.synopsis),
description.html()
) { viewModel.releaseEpisodeSynopsis() }
}
}
context?.let { ctx ->

View file

@ -8,6 +8,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone
import androidx.core.view.isVisible
@ -17,6 +18,7 @@ import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
@ -245,7 +247,7 @@ class ResultFragmentTv : Fragment() {
binding?.apply {
//episodesShadow.rotationX = 180.0f//if(episodesShadow.isRtl()) 180.0f else 0.0f
val leftListener: View.OnFocusChangeListener =
View.OnFocusChangeListener { _, hasFocus ->
if (!hasFocus) return@OnFocusChangeListener
@ -265,6 +267,7 @@ class ResultFragmentTv : Fragment() {
resultEpisodesShow.onFocusChangeListener = rightListener
resultDescription.onFocusChangeListener = leftListener
resultBookmarkButton.onFocusChangeListener = leftListener
resultFavoriteButton.onFocusChangeListener = leftListener
resultEpisodesShow.setOnClickListener {
// toggle, to make it more touch accessable just in case someone thinks that a
// tv layout is better but is using a touch device
@ -283,7 +286,8 @@ class ResultFragmentTv : Fragment() {
resultPlaySeries,
resultResumeSeries,
resultPlayTrailer,
resultBookmarkButton
resultBookmarkButton,
resultFavoriteButton
)
for (requestView in views) {
if (!requestView.isVisible) continue
@ -424,6 +428,7 @@ class ResultFragmentTv : Fragment() {
val aboveCast = listOf(
binding?.resultEpisodesShow,
binding?.resultBookmarkButton,
binding?.resultFavoriteButton,
).firstOrNull {
it?.isVisible == true
}
@ -532,9 +537,45 @@ class ResultFragmentTv : Fragment() {
}
}
observeNullable(viewModel.favoriteStatus) { isFavorite ->
binding?.resultFavoriteButton?.apply {
isVisible = isFavorite != null
if (isFavorite == null) return@observeNullable
val drawable = if (isFavorite) {
R.drawable.ic_baseline_favorite_24
} else {
R.drawable.ic_baseline_favorite_border_24
}
val text = if (isFavorite) {
R.string.action_remove_from_favorites
} else {
R.string.action_add_to_favorites
}
setIconResource(drawable)
setText(text)
setOnClickListener {
val isFavorite = viewModel.toggleFavoriteStatus() ?: return@setOnClickListener
val message = if (isFavorite) {
R.string.favorite_added
} else {
R.string.favorite_removed
}
val name = (viewModel.page.value as? Resource.Success)?.value?.title
?: txt(R.string.no_data).asStringNull(context) ?: ""
CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT)
}
}
}
observeNullable(viewModel.movie) { data ->
binding?.apply {
resultPlayMovie.isVisible = data is Resource.Success
resultPlaySeries.isVisible = data == null
seriesHolder.isVisible = data == null
resultEpisodesShow.isVisible = data == null
@ -764,12 +805,14 @@ class ResultFragmentTv : Fragment() {
R.drawable.profile_bg_red,
R.drawable.profile_bg_teal
).random()
//Change poster crop area to 20% from Top
backgroundPoster.cropYCenterOffsetPct = 0.20F
backgroundPoster.setImage(
d.posterBackgroundImage ?: UiImage.Drawable(error),
radius = 0,
errorImageDrawable = error
)
resultComingSoon.isVisible = d.comingSoon
resultDataHolder.isGone = d.comingSoon
UIHelper.populateChips(resultTag, d.tags)

View file

@ -51,11 +51,14 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioWork
import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
import com.lagradost.cloudstream3.utils.DataStoreHelper.getFavoritesData
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultEpisode
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.DataStoreHelper.removeFavoritesData
import com.lagradost.cloudstream3.utils.DataStoreHelper.setDub
import com.lagradost.cloudstream3.utils.DataStoreHelper.setFavoritesData
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultEpisode
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason
import com.lagradost.cloudstream3.utils.UIHelper.navigate
@ -425,6 +428,9 @@ class ResultViewModel2 : ViewModel() {
private val _subscribeStatus: MutableLiveData<Boolean?> = MutableLiveData(null)
val subscribeStatus: LiveData<Boolean?> = _subscribeStatus
private val _favoriteStatus: MutableLiveData<Boolean?> = MutableLiveData(null)
val favoriteStatus: LiveData<Boolean?> = _favoriteStatus
companion object {
const val TAG = "RVM2"
//private const val EPISODE_RANGE_SIZE = 20
@ -868,6 +874,40 @@ class ResultViewModel2 : ViewModel() {
return !isSubscribed
}
/**
* @return true if added to favorites, false if not. Null if not possible to favorite.
**/
fun toggleFavoriteStatus(): Boolean? {
val isFavorite = _favoriteStatus.value ?: return null
val response = currentResponse ?: return null
val currentId = response.getId()
if (isFavorite) {
removeFavoritesData(currentId)
} else {
val current = getFavoritesData(currentId)
setFavoritesData(
currentId,
DataStoreHelper.FavoritesData(
currentId,
current?.favoritesTime ?: unixTimeMS,
unixTimeMS,
response.name,
response.url,
response.apiName,
response.type,
response.posterUrl,
response.year
)
)
}
_favoriteStatus.postValue(!isFavorite)
return !isFavorite
}
private fun startChromecast(
activity: Activity?,
result: ResultEpisode,
@ -1750,6 +1790,12 @@ class ResultViewModel2 : ViewModel() {
}
}
private fun postFavorites(loadResponse: LoadResponse) {
val id = loadResponse.getId()
val isFavorite = getFavoritesData(id) != null
_favoriteStatus.postValue(isFavorite)
}
private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) {
if (range == null || indexer == null) {
return
@ -1887,6 +1933,7 @@ class ResultViewModel2 : ViewModel() {
currentResponse = loadResponse
postPage(loadResponse, apiRepository)
postSubscription(loadResponse)
postFavorites(loadResponse)
if (updateEpisodes)
postEpisodes(loadResponse, updateFillers)
}

View file

@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.search
import android.content.DialogInterface
import android.content.res.Configuration
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -231,9 +232,15 @@ class SearchFragment : Fragment() {
val searchExitIcon =
binding?.mainSearch?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
// val searchMagIcon =
// main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
//searchMagIcon.scaleX = 0.65f
//searchMagIcon.scaleY = 0.65f
// binding?.mainSearch?.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
// searchMagIcon.scaleX = 0.65f
// searchMagIcon.scaleY = 0.65f
// Set the color for the search exit icon to the correct theme text color
val searchExitIconColor = TypedValue()
activity?.theme?.resolveAttribute(android.R.attr.textColor, searchExitIconColor, true)
searchExitIcon?.setColorFilter(searchExitIconColor.data)
selectedApis = DataStoreHelper.searchPreferenceProviders.toMutableSet()

View file

@ -187,13 +187,13 @@ class SettingsFragment : Fragment() {
}
binding?.apply {
listOf(
settingsGeneral to R.id.action_navigation_settings_to_navigation_settings_general,
settingsPlayer to R.id.action_navigation_settings_to_navigation_settings_player,
settingsCredits to R.id.action_navigation_settings_to_navigation_settings_account,
settingsUi to R.id.action_navigation_settings_to_navigation_settings_ui,
settingsProviders to R.id.action_navigation_settings_to_navigation_settings_providers,
settingsUpdates to R.id.action_navigation_settings_to_navigation_settings_updates,
settingsExtensions to R.id.action_navigation_settings_to_navigation_settings_extensions,
settingsGeneral to R.id.action_navigation_global_to_navigation_settings_general,
settingsPlayer to R.id.action_navigation_global_to_navigation_settings_player,
settingsCredits to R.id.action_navigation_global_to_navigation_settings_account,
settingsUi to R.id.action_navigation_global_to_navigation_settings_ui,
settingsProviders to R.id.action_navigation_global_to_navigation_settings_providers,
settingsUpdates to R.id.action_navigation_global_to_navigation_settings_updates,
settingsExtensions to R.id.action_navigation_global_to_navigation_settings_extensions,
).forEach { (view, navigationId) ->
view.apply {
setOnClickListener {
@ -212,4 +212,4 @@ class SettingsFragment : Fragment() {
}
}
}
}
}

View file

@ -42,6 +42,7 @@ const val VIDEO_WATCH_STATE = "video_watch_state"
const val RESULT_WATCH_STATE = "result_watch_state"
const val RESULT_WATCH_STATE_DATA = "result_watch_state_data"
const val RESULT_SUBSCRIBED_STATE_DATA = "result_subscribed_state_data"
const val RESULT_FAVORITES_STATE_DATA = "result_favorites_state_data"
const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes
const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching"
const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated"
@ -406,6 +407,33 @@ object DataStoreHelper {
}
}
data class FavoritesData(
@JsonProperty("id") override var id: Int?,
@JsonProperty("favoritesTime") val favoritesTime: Long,
@JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long,
@JsonProperty("name") override val name: String,
@JsonProperty("url") override val url: String,
@JsonProperty("apiName") override val apiName: String,
@JsonProperty("type") override var type: TvType? = null,
@JsonProperty("posterUrl") override var posterUrl: String?,
@JsonProperty("year") val year: Int?,
@JsonProperty("quality") override var quality: SearchQuality? = null,
@JsonProperty("posterHeaders") override var posterHeaders: Map<String, String>? = null,
) : SearchResponse {
fun toLibraryItem(): SyncAPI.LibraryItem? {
return SyncAPI.LibraryItem(
name,
url,
id?.toString() ?: return null,
null,
null,
null,
latestUpdatedTime,
apiName, type, posterUrl, posterHeaders, quality, this.id
)
}
}
data class ResumeWatchingResult(
@JsonProperty("name") override val name: String,
@JsonProperty("url") override val url: String,
@ -579,6 +607,29 @@ object DataStoreHelper {
return getKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString())
}
fun getAllFavorites(): List<FavoritesData> {
return getKeys("$currentAccount/$RESULT_FAVORITES_STATE_DATA")?.mapNotNull {
getKey(it)
} ?: emptyList()
}
fun removeFavoritesData(id: Int?) {
if (id == null) return
AccountManager.localListApi.requireLibraryRefresh = true
removeKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString())
}
fun setFavoritesData(id: Int?, data: FavoritesData) {
if (id == null) return
setKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString(), data)
AccountManager.localListApi.requireLibraryRefresh = true
}
fun getFavoritesData(id: Int?): FavoritesData? {
if (id == null) return null
return getKey("$currentAccount/$RESULT_FAVORITES_STATE_DATA", id.toString())
}
fun setViewPos(id: Int?, pos: Long, dur: Long) {
if (id == null) return
if (dur < 30_000) return // too short

View file

@ -0,0 +1,94 @@
package com.lagradost.cloudstream3.utils
//Reference: https://stackoverflow.com/a/29055283
import android.content.Context
import android.graphics.Matrix
import android.graphics.drawable.Drawable
import android.util.AttributeSet
class PercentageCropImageView : androidx.appcompat.widget.AppCompatImageView {
private var mCropYCenterOffsetPct: Float? = null
private var mCropXCenterOffsetPct: Float? = null
constructor(context: Context?) : super(context!!)
constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs)
constructor(
context: Context?, attrs: AttributeSet?,
defStyle: Int
) : super(context!!, attrs, defStyle)
var cropYCenterOffsetPct: Float
get() = mCropYCenterOffsetPct!!
set(cropYCenterOffsetPct) {
require(cropYCenterOffsetPct <= 1.0) { "Value too large: Must be <= 1.0" }
mCropYCenterOffsetPct = cropYCenterOffsetPct
}
var cropXCenterOffsetPct: Float
get() = mCropXCenterOffsetPct!!
set(cropXCenterOffsetPct) {
require(cropXCenterOffsetPct <= 1.0) { "Value too large: Must be <= 1.0" }
mCropXCenterOffsetPct = cropXCenterOffsetPct
}
private fun myConfigureBounds() {
if (this.scaleType == ScaleType.MATRIX) {
val d = this.drawable
if (d != null) {
val dWidth = d.intrinsicWidth
val dHeight = d.intrinsicHeight
val m = Matrix()
val vWidth = width - this.paddingLeft - this.paddingRight
val vHeight = height - this.paddingTop - this.paddingBottom
val scale: Float
var dx = 0f
var dy = 0f
if (dWidth * vHeight > vWidth * dHeight) {
val cropXCenterOffsetPct =
if (mCropXCenterOffsetPct != null) mCropXCenterOffsetPct!!.toFloat() else 0.5f
scale = vHeight.toFloat() / dHeight.toFloat()
dx = (vWidth - dWidth * scale) * cropXCenterOffsetPct
} else {
val cropYCenterOffsetPct =
if (mCropYCenterOffsetPct != null) mCropYCenterOffsetPct!!.toFloat() else 0f
scale = vWidth.toFloat() / dWidth.toFloat()
dy = (vHeight - dHeight * scale) * cropYCenterOffsetPct
}
m.setScale(scale, scale)
m.postTranslate((dx + 0.5f).toInt().toFloat(), (dy + 0.5f).toInt().toFloat())
this.imageMatrix = m
}
}
}
// These 3 methods call configureBounds in ImageView.java class, which
// adjusts the matrix in a call to center_crop (android's built-in
// scaling and centering crop method). We also want to trigger
// in the same place, but using our own matrix, which is then set
// directly at line 588 of ImageView.java and then copied over
// as the draw matrix at line 942 of ImageView.java
override fun setFrame(l: Int, t: Int, r: Int, b: Int): Boolean {
val changed = super.setFrame(l, t, r, b)
myConfigureBounds()
return changed
}
override fun setImageDrawable(d: Drawable?) {
super.setImageDrawable(d)
myConfigureBounds()
}
override fun setImageResource(resId: Int) {
super.setImageResource(resId)
myConfigureBounds()
}
// In case you can change the ScaleType in code you have to call redraw()
//fullsizeImageView.setScaleType(ScaleType.FIT_CENTER);
//fullsizeImageView.redraw();
fun redraw() {
val d = this.drawable
if (d != null) {
// Force toggle to recalculate our bounds
setImageDrawable(null)
setImageDrawable(d)
}
}
}

View file

@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.utils
import android.app.Activity
import android.app.Dialog
import android.text.Spanned
import android.view.LayoutInflater
import android.view.View
import android.widget.AbsListView
@ -19,6 +20,7 @@ import androidx.core.view.marginRight
import androidx.core.view.marginTop
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.BottomTextDialogBinding
import com.lagradost.cloudstream3.databinding.BottomSelectionDialogBinding
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
@ -363,4 +365,24 @@ object SingleSelectionHelper {
dismissCallback
)
}
fun Activity.showBottomDialogText(
title: String,
text: Spanned,
dismissCallback: () -> Unit
) {
val binding = BottomTextDialogBinding.inflate(layoutInflater)
val dialog = BottomSheetDialog(this)
dialog.setContentView(binding.root)
binding.dialogTitle.text = title
binding.dialogText.text = text
dialog.setOnDismissListener {
dismissCallback.invoke()
}
dialog.show()
}
}

View file

@ -178,9 +178,10 @@ object UIHelper {
fun Activity?.navigate(@IdRes navigation: Int, arguments: Bundle? = null) {
try {
if (this is FragmentActivity) {
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment?)?.navController?.navigate(
navigation, arguments
)
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment?
navHostFragment?.navController?.let {
it.navigate(navigation, arguments)
}
}
} catch (t: Throwable) {
logError(t)

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/dialog_title"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:layout_marginTop="20dp"
android:layout_marginBottom="10dp"
android:textStyle="bold"
android:textSize="20sp"
android:textColor="?attr/textColor"
android:layout_width="match_parent"
android:layout_rowWeight="1"
tools:text="Test"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/dialog_text"
android:textAppearance="?android:attr/textAppearanceListItem"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingTop="10dp"
android:requiresFadingEdge="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_rowWeight="1" />
</LinearLayout>

View file

@ -9,7 +9,7 @@
android:layout_margin="5dp"
android:foreground="@drawable/outline_drawable"
app:cardBackgroundColor="@color/transparent"
android:focusable="true"
app:cardCornerRadius="@dimen/rounded_image_radius"
app:cardElevation="0dp">

View file

@ -232,7 +232,9 @@
android:layout_marginStart="@dimen/navbar_width"
android:layout_marginEnd="0dp"
android:padding="12dp"
android:text="@string/continue_watching" />
android:text="@string/continue_watching"
android:background="?android:attr/selectableItemBackground"
app:drawableTint="?attr/white" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_watch_child_recyclerview"
@ -258,7 +260,15 @@
android:visibility="gone"
tools:visibility="visible">
<FrameLayout
android:id="@+id/home_bookmark_parent_item_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground">
<HorizontalScrollView
android:id="@+id/horizontal_scroll_chips"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fadingEdge="horizontal"
@ -344,6 +354,18 @@
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<ImageView
android:id="@+id/home_bookmark_parent_item_more_info"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_marginEnd="12dp"
android:contentDescription="@string/home_more_info"
android:src="@drawable/ic_baseline_arrow_forward_24"
android:visibility="gone"
app:drawableTint="?attr/white" />
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_bookmarked_child_recyclerview"
android:layout_width="wrap_content"

View file

@ -140,6 +140,7 @@
android:layout_gravity="end"
android:background="@drawable/player_button_tv_attr_no_bg"
android:contentDescription="@string/account"
android:focusable="true"
android:nextFocusLeft="@id/home_preview_search_button"
android:nextFocusRight="@id/home_switch_account"
android:nextFocusDown="@id/home_change_api"

View file

@ -68,6 +68,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:layout_marginEnd="25dp"
android:iconifiedByDefault="false"
android:imeOptions="actionSearch"
@ -81,6 +82,7 @@
app:queryBackground="@color/transparent"
app:queryHint="@string/search_hint"
app:searchIcon="@drawable/search_icon"
app:closeIcon="@drawable/ic_baseline_close_24"
tools:ignore="RtlSymmetry">
</androidx.appcompat.widget.SearchView>

View file

@ -38,6 +38,7 @@
android:padding="2dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/change_providers_img_des"
android:focusable="true"
android:nextFocusLeft="@id/nav_rail_view"
android:nextFocusRight="@id/library_sort"
android:tag="@string/tv_no_focus_tag"
@ -52,6 +53,7 @@
android:layout_marginStart="10dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/change_providers_img_des"
android:focusable="true"
android:nextFocusLeft="@id/provider_selector"
android:nextFocusRight="@id/list_selector"
android:tag="@string/tv_no_focus_tag"
@ -67,6 +69,7 @@
android:layout_marginStart="10dp"
android:background="?selectableItemBackgroundBorderless"
android:contentDescription="@string/change_providers_img_des"
android:focusable="true"
android:nextFocusLeft="@id/library_sort"
android:nextFocusRight="@id/main_search"
android:tag="@string/tv_no_focus_tag"
@ -99,6 +102,7 @@
app:queryBackground="@color/transparent"
app:queryHint="@string/search_hint"
app:searchIcon="@drawable/search_icon"
app:closeIcon="@drawable/ic_baseline_close_24"
tools:ignore="RtlSymmetry">
</androidx.appcompat.widget.SearchView>

View file

@ -74,7 +74,7 @@
android:nextFocusUp="@id/result_back"
android:nextFocusDown="@id/result_description"
android:nextFocusLeft="@id/result_add_sync"
android:nextFocusRight="@id/result_share"
android:nextFocusRight="@id/result_favorite"
tools:visibility="visible"
@ -89,10 +89,27 @@
android:layout_gravity="end|center_vertical"
app:tint="?attr/textColor" />
<ImageView
android:nextFocusUp="@id/result_back"
android:nextFocusDown="@id/result_description"
android:nextFocusLeft="@id/result_subscribe"
android:nextFocusRight="@id/result_share"
android:id="@+id/result_favorite"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_margin="5dp"
android:elevation="10dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_favorite_border_24"
android:layout_gravity="end|center_vertical"
app:tint="?attr/textColor" />
<ImageView
android:nextFocusUp="@id/result_back"
android:nextFocusDown="@id/result_description"
android:nextFocusLeft="@id/result_subscribe"
android:nextFocusLeft="@id/result_favorite"
android:nextFocusRight="@id/result_open_in_browser"
android:id="@+id/result_share"

View file

@ -124,28 +124,27 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
android:textColor="?attr/textColor" />
</LinearLayout>
<FrameLayout
android:id="@+id/background_poster_holder"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_height="250dp"
android:visibility="visible">
<ImageView
<com.lagradost.cloudstream3.utils.PercentageCropImageView
android:id="@+id/background_poster"
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_height="275dp"
android:layout_gravity="center"
android:alpha="0.8"
android:scaleType="centerCrop"
tools:src="@drawable/profile_bg_dark_blue" />
android:scaleType="matrix"
tools:src="@drawable/profile_bg_dark_blue" >
</com.lagradost.cloudstream3.utils.PercentageCropImageView>
<ImageView
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_height="120dp"
android:layout_gravity="bottom"
android:src="@drawable/background_shadow">
</ImageView>
</FrameLayout>
@ -155,7 +154,6 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -166,8 +164,70 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="100dp"
android:orientation="horizontal">
android:orientation="vertical"
android:layout_marginTop="175dp">
<TextView
android:id="@+id/result_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:gravity="center_vertical"
android:singleLine="true"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold"
tools:text="The Perfect Run The Perfect Run" />
<LinearLayout
android:id="@+id/result_next_airing_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:orientation="vertical">
<TextView
android:id="@+id/result_episodes_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="20dp"
android:textColor="?attr/textColor"
android:textSize="17sp"
android:textStyle="normal"
android:visibility="gone"
tools:text="8 Episodes" />
<TextView
android:id="@+id/result_next_airing"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center"
android:textColor="?attr/grayTextColor"
android:textSize="17sp"
android:textStyle="normal"
tools:text="Episode 1022 will be released in" />
<TextView
android:id="@+id/result_next_airing_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="start"
android:textColor="?attr/textColor"
android:textSize="17sp"
android:textStyle="normal"
tools:text="5d 3h 30m" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="10dp">
<LinearLayout
android:layout_width="250dp"
@ -176,67 +236,11 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
android:layout_weight="0"
android:orientation="vertical">
<TextView
android:id="@+id/result_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:gravity="center_vertical"
android:singleLine="true"
android:textColor="?attr/textColor"
android:textSize="20sp"
android:textStyle="bold"
tools:text="The Perfect Run The Perfect Run" />
<LinearLayout
android:id="@+id/result_next_airing_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:orientation="vertical">
<TextView
android:id="@+id/result_episodes_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="20dp"
android:textColor="?attr/textColor"
android:textSize="17sp"
android:textStyle="normal"
android:visibility="gone"
tools:text="8 Episodes" />
<TextView
android:id="@+id/result_next_airing"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center"
android:textColor="?attr/grayTextColor"
android:textSize="17sp"
android:textStyle="normal"
tools:text="Episode 1022 will be released in" />
<TextView
android:id="@+id/result_next_airing_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="start"
android:textColor="?attr/textColor"
android:textSize="17sp"
android:textStyle="normal"
tools:text="5d 3h 30m" />
</LinearLayout>
<LinearLayout
android:id="@+id/result_movie_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginTop="10dp"
android:animateLayoutChanges="true"
android:orientation="vertical"
tools:visibility="visible">
@ -310,19 +314,30 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
style="@style/ResultButtonTV"
android:nextFocusRight="@id/result_description"
android:nextFocusUp="@id/result_play_trailer"
android:nextFocusDown="@id/result_episodes_show"
android:nextFocusDown="@id/result_favorite_button"
android:text="@string/type_none"
android:visibility="visible"
app:icon="@drawable/ic_baseline_bookmark_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/result_favorite_button"
style="@style/ResultButtonTV"
android:nextFocusRight="@id/result_description"
android:nextFocusUp="@id/result_bookmark_button"
android:nextFocusDown="@id/result_episodes_show"
android:text="@string/action_add_to_favorites"
android:visibility="visible"
app:icon="@drawable/ic_baseline_favorite_border_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/result_episodes_show"
style="@style/ResultButtonTV"
android:nextFocusRight="@id/redirect_to_episodes"
android:nextFocusUp="@id/result_bookmark_button"
android:nextFocusUp="@id/result_favorite_button"
android:nextFocusDown="@id/result_cast_items"
android:text="@string/episodes"
@ -404,6 +419,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
android:fadingEdgeLength="30dp"
android:foreground="@drawable/outline_drawable"
android:maxLines="7"
android:focusable="true"
android:nextFocusUp="@id/result_back"
android:nextFocusDown="@id/result_bookmark_button"
android:padding="5dp"

View file

@ -49,6 +49,7 @@
app:queryBackground="@color/transparent"
app:queryHint="@string/search_hint"
app:searchIcon="@drawable/search_icon"
app:closeIcon="@drawable/ic_baseline_close_24"
tools:ignore="RtlSymmetry">
<requestFocus />

View file

@ -50,6 +50,7 @@
app:queryBackground="@color/transparent"
app:queryHint="@string/search_hint"
app:searchIcon="@drawable/search_icon"
app:closeIcon="@drawable/ic_baseline_close_24"
tools:ignore="RtlSymmetry">
<requestFocus />

View file

@ -0,0 +1,35 @@
<?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:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/home_child_more_info"
style="@style/WatchHeaderText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/navbar_width"
android:layout_marginEnd="0dp"
android:padding="12dp"
tools:text="@string/continue_watching"
app:drawableRightCompat="@drawable/ic_baseline_arrow_forward_24"
app:drawableTint="?attr/white"
android:background="?android:attr/selectableItemBackground"
android:contentDescription="@string/home_more_info"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_child_recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:nextFocusUp="@id/home_child_more_info"
android:orientation="horizontal"
android:paddingStart="@dimen/navbar_width"
android:paddingEnd="5dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/home_result_grid" />
</LinearLayout>

View file

@ -17,14 +17,15 @@
android:layout_height="wrap_content">
<ImageView
android:id="@+id/quick_search_back"
android:layout_gravity="center"
android:foregroundGravity="center"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_arrow_back_24"
app:tint="@android:color/white"
android:layout_width="25dp"
android:layout_height="wrap_content">
android:id="@+id/quick_search_back"
android:layout_gravity="center"
android:foregroundGravity="center"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_baseline_arrow_back_24"
app:tint="@android:color/white"
android:focusable="true"
android:layout_width="25dp"
android:layout_height="wrap_content">
<requestFocus />
</ImageView>
@ -48,6 +49,8 @@
app:queryBackground="@color/transparent"
app:searchIcon="@drawable/search_icon"
app:closeIcon="@drawable/ic_baseline_close_24"
android:paddingStart="-10dp"
android:iconifiedByDefault="false"
app:queryHint="@string/search_hint"

View file

@ -331,57 +331,56 @@
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim"
tools:layout="@layout/main_settings">
<action
android:id="@+id/action_navigation_settings_to_navigation_settings_ui"
app:destination="@id/navigation_settings_ui"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_settings_to_navigation_settings_providers"
app:destination="@id/navigation_settings_providers"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_settings_to_navigation_settings_player"
app:destination="@id/navigation_settings_player"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_settings_to_navigation_settings_updates"
app:destination="@id/navigation_settings_updates"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_settings_to_navigation_settings_account"
app:destination="@id/navigation_settings_account"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_settings_to_navigation_settings_general"
app:destination="@id/navigation_settings_general"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_settings_to_navigation_settings_extensions"
app:destination="@id/navigation_settings_extensions"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
</fragment>
tools:layout="@layout/main_settings" />
<action
android:id="@+id/action_navigation_global_to_navigation_settings_ui"
app:destination="@id/navigation_settings_ui"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_global_to_navigation_settings_providers"
app:destination="@id/navigation_settings_providers"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_global_to_navigation_settings_player"
app:destination="@id/navigation_settings_player"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_global_to_navigation_settings_updates"
app:destination="@id/navigation_settings_updates"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_global_to_navigation_settings_account"
app:destination="@id/navigation_settings_account"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_global_to_navigation_settings_general"
app:destination="@id/navigation_settings_general"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_global_to_navigation_settings_extensions"
app:destination="@id/navigation_settings_extensions"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<fragment
android:id="@+id/navigation_subtitles"
@ -638,4 +637,4 @@
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim"
tools:layout="@layout/fragment_setup_media" />
</navigation>
</navigation>

View file

@ -690,4 +690,9 @@
<string name="tv_no_focus_tag" translatable="false">tv_no_focus_tag</string>
<string name="already_voted">You have already voted</string>
<string name="favorites_list_name">Favorites</string>
<string name="favorite_added">%s added to favorites</string>
<string name="favorite_removed">%s removed from favorites</string>
<string name="action_add_to_favorites">Add to favorites</string>
<string name="action_remove_from_favorites">Remove from favorites</string>
</resources>