+ Fixed ephemeral scroll

+ Fixed Unable to remove Subs
+ Fixed download 1 frame visual glitch
+ Maybe fixed worker
+ Updated layout API
+ Bump
This commit is contained in:
Osten 2024-03-17 03:37:09 +01:00
parent 638cc4fee9
commit ad67b9ddab
46 changed files with 665 additions and 451 deletions

View file

@ -60,7 +60,7 @@ android {
targetSdk = 33 /* Android 14 is Fu*ked targetSdk = 33 /* Android 14 is Fu*ked
^ https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading*/ ^ https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading*/
versionCode = 63 versionCode = 63
versionName = "4.3.1" versionName = "4.3.2"
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "") resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "")
@ -163,10 +163,10 @@ dependencies {
// Android Core & Lifecycle // Android Core & Lifecycle
implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.navigation:navigation-ui-ktx:2.7.6") implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0") implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6") implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")
// Design & UI // Design & UI
implementation("jp.wasabeef:glide-transformations:4.3.0") implementation("jp.wasabeef:glide-transformations:4.3.0")

View file

@ -11,7 +11,9 @@ import androidx.fragment.app.FragmentActivity
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKey
@ -31,7 +33,6 @@ import org.acra.sender.ReportSenderFactory
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.PrintStream import java.io.PrintStream
import java.lang.Exception
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -211,7 +212,7 @@ class AcraApplication : Application() {
fun openBrowser(url: String, activity: FragmentActivity?) { fun openBrowser(url: String, activity: FragmentActivity?) {
openBrowser( openBrowser(
url, url,
isTvSettings(), isLayout(TV or EMULATOR),
activity?.supportFragmentManager?.fragments?.lastOrNull() activity?.supportFragmentManager?.fragments?.lastOrNull()
) )
} }

View file

@ -34,7 +34,7 @@ import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.player.PlayerEventType import com.lagradost.cloudstream3.ui.player.PlayerEventType
import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.ui.result.UiText import com.lagradost.cloudstream3.ui.result.UiText
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv import com.lagradost.cloudstream3.ui.settings.Globals.updateTv
import com.lagradost.cloudstream3.utils.AppUtils.isRtl import com.lagradost.cloudstream3.utils.AppUtils.isRtl
import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.Event

View file

@ -18,6 +18,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.simklAp
import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.syncproviders.SyncIdName
import com.lagradost.cloudstream3.syncproviders.providers.SimklApi import com.lagradost.cloudstream3.syncproviders.providers.SimklApi
import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.ui.result.ResultViewModel2
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.Coroutines.mainWork import com.lagradost.cloudstream3.utils.Coroutines.mainWork
@ -119,7 +120,8 @@ object APIHolder {
} }
fun LoadResponse.getId(): Int { fun LoadResponse.getId(): Int {
return getLoadResponseIdFromUrl(url, apiName) // this fixes an issue with outdated api as getLoadResponseIdFromUrl might be fucked
return (if (this is ResultViewModel2.LoadResponseFromSearch) this.id else null) ?: getLoadResponseIdFromUrl(url, apiName)
} }
/** /**

View file

@ -86,6 +86,7 @@ import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.plugins.PluginManager.loadAllOnlinePlugins import com.lagradost.cloudstream3.plugins.PluginManager.loadAllOnlinePlugins
import com.lagradost.cloudstream3.plugins.PluginManager.loadSinglePlugin import com.lagradost.cloudstream3.plugins.PluginManager.loadSinglePlugin
import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
import com.lagradost.cloudstream3.services.SubscriptionWorkManager
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString
@ -112,11 +113,11 @@ import com.lagradost.cloudstream3.ui.result.setText
import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.search.SearchFragment import com.lagradost.cloudstream3.ui.search.SearchFragment
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTruePhone import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv import com.lagradost.cloudstream3.ui.settings.Globals.updateTv
import com.lagradost.cloudstream3.ui.settings.SettingsGeneral import com.lagradost.cloudstream3.ui.settings.SettingsGeneral
import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY
import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
@ -290,7 +291,8 @@ var app = Requests(responseParser = object : ResponseParser {
defaultHeaders = mapOf("user-agent" to USER_AGENT) defaultHeaders = mapOf("user-agent" to USER_AGENT)
} }
class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAuthenticator.BiometricAuthCallback { class MainActivity : AppCompatActivity(), ColorPickerDialogListener,
BiometricAuthenticator.BiometricAuthCallback {
companion object { companion object {
const val TAG = "MAINACT" const val TAG = "MAINACT"
const val ANIMATED_OUTLINE: Boolean = false const val ANIMATED_OUTLINE: Boolean = false
@ -336,10 +338,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
// kinda shitty solution, but cant com main->home otherwise for popups // kinda shitty solution, but cant com main->home otherwise for popups
val bookmarksUpdatedEvent = Event<Boolean>() val bookmarksUpdatedEvent = Event<Boolean>()
/** /**
* Used by DataStoreHelper to fully reload home when switching accounts * Used by DataStoreHelper to fully reload home when switching accounts
*/ */
val reloadHomeEvent = Event<Boolean>() val reloadHomeEvent = Event<Boolean>()
/** /**
* Used by DataStoreHelper to fully reload library when switching accounts * Used by DataStoreHelper to fully reload library when switching accounts
*/ */
@ -467,7 +471,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
} }
var lastPopup: SearchResponse? = null var lastPopup: SearchResponse? = null
fun loadPopup(result: SearchResponse, load : Boolean = true) { fun loadPopup(result: SearchResponse, load: Boolean = true) {
lastPopup = result lastPopup = result
val syncName = syncViewModel.syncName(result.apiName) val syncName = syncViewModel.syncName(result.apiName)
@ -488,8 +492,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
.contains(DubStatus.Dubbed) .contains(DubStatus.Dubbed)
) DubStatus.Dubbed else DubStatus.Subbed, null ) DubStatus.Dubbed else DubStatus.Subbed, null
) )
}else { } else {
viewModel.loadSmall(this,result) viewModel.loadSmall(this, result)
} }
} }
@ -554,7 +558,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
binding?.navHostFragment?.apply { binding?.navHostFragment?.apply {
val params = layoutParams as ConstraintLayout.LayoutParams val params = layoutParams as ConstraintLayout.LayoutParams
val push = val push =
if (!dontPush && isTvSettings()) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0 if (!dontPush && isLayout(TV or EMULATOR)) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0
if (!this.isLtr()) { if (!this.isLtr()) {
params.setMargins( params.setMargins(
@ -581,7 +585,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
} }
Configuration.ORIENTATION_PORTRAIT -> { Configuration.ORIENTATION_PORTRAIT -> {
isTvSettings() isLayout(TV or EMULATOR)
} }
else -> { else -> {
@ -787,9 +791,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
} }
lateinit var viewModel: ResultViewModel2 lateinit var viewModel: ResultViewModel2
lateinit var syncViewModel : SyncViewModel lateinit var syncViewModel: SyncViewModel
/** kinda dirty, however it signals that we should use the watch status as sync or not*/ /** kinda dirty, however it signals that we should use the watch status as sync or not*/
var isLocalList : Boolean = false var isLocalList: Boolean = false
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? { override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
viewModel = viewModel =
ViewModelProvider(this)[ResultViewModel2::class.java] ViewModelProvider(this)[ResultViewModel2::class.java]
@ -1105,8 +1110,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
} }
} }
private fun centerView(view : View?) { private fun centerView(view: View?) {
if(view == null) return if (view == null) return
try { try {
Log.v(TAG, "centerView: $view") Log.v(TAG, "centerView: $view")
val r = Rect(0, 0, 0, 0) val r = Rect(0, 0, 0, 0)
@ -1172,11 +1177,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
// just in case, MAIN SHOULD *NEVER* BOOT LOOP CRASH // just in case, MAIN SHOULD *NEVER* BOOT LOOP CRASH
binding = try { binding = try {
if (isTvSettings()) { if (isLayout(TV or EMULATOR)) {
val newLocalBinding = ActivityMainTvBinding.inflate(layoutInflater, null, false) val newLocalBinding = ActivityMainTvBinding.inflate(layoutInflater, null, false)
setContentView(newLocalBinding.root) setContentView(newLocalBinding.root)
if (isTrueTvSettings() && ANIMATED_OUTLINE) { if (isLayout(TV) && ANIMATED_OUTLINE) {
TvFocus.focusOutline = WeakReference(newLocalBinding.focusOutline) TvFocus.focusOutline = WeakReference(newLocalBinding.focusOutline)
newLocalBinding.root.viewTreeObserver.addOnScrollChangedListener { newLocalBinding.root.viewTreeObserver.addOnScrollChangedListener {
TvFocus.updateFocusView(TvFocus.lastFocus.get(), same = true) TvFocus.updateFocusView(TvFocus.lastFocus.get(), same = true)
@ -1188,7 +1193,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
newLocalBinding.focusOutline.isVisible = false newLocalBinding.focusOutline.isVisible = false
} }
if(isTrueTvSettings()) { if (isLayout(TV)) {
// Put here any button you don't want focusing it to center the view // Put here any button you don't want focusing it to center the view
val exceptionButtons = listOf( val exceptionButtons = listOf(
R.id.home_preview_play_btt, R.id.home_preview_play_btt,
@ -1205,7 +1210,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
R.id.result_search_Button, R.id.result_search_Button,
R.id.result_episodes_show_button, R.id.result_episodes_show_button,
) )
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus -> newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
if (exceptionButtons.contains(newFocus?.id)) return@addOnGlobalFocusChangeListener if (exceptionButtons.contains(newFocus?.id)) return@addOnGlobalFocusChangeListener
centerView(newFocus) centerView(newFocus)
@ -1223,18 +1228,21 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
null null
} }
changeStatusBarState(isEmulatorSettings()) changeStatusBarState(isLayout(EMULATOR))
/** Biometric stuff for users without accounts **/ /** Biometric stuff for users without accounts **/
val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_key), false) val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_key), false)
val noAccounts = settingsManager.getBoolean(getString(R.string.skip_startup_account_select_key), false) || accounts.count() <= 1 val noAccounts = settingsManager.getBoolean(
getString(R.string.skip_startup_account_select_key),
false
) || accounts.count() <= 1
if (isTruePhone() && authEnabled && noAccounts) { if (isLayout(PHONE) && authEnabled && noAccounts) {
if (deviceHasPasswordPinLock(this)) { if (deviceHasPasswordPinLock(this)) {
startBiometricAuthentication(this, R.string.biometric_authentication_title, false) startBiometricAuthentication(this, R.string.biometric_authentication_title, false)
BiometricAuthenticator.promptInfo?.let { BiometricAuthenticator.promptInfo?.let { promt ->
BiometricAuthenticator.biometricPrompt?.authenticate(it) BiometricAuthenticator.biometricPrompt?.authenticate(promt)
} }
// hide background while authenticating, Sorry moms & dads 🙏 // hide background while authenticating, Sorry moms & dads 🙏
@ -1326,7 +1334,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
} }
fun setUserData(status : Resource<SyncAPI.AbstractSyncStatus>?) { fun setUserData(status: Resource<SyncAPI.AbstractSyncStatus>?) {
if (isLocalList) return if (isLocalList) return
bottomPreviewBinding?.apply { bottomPreviewBinding?.apply {
when (status) { when (status) {
@ -1351,7 +1359,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
} }
} }
fun setWatchStatus(state : WatchType?) { fun setWatchStatus(state: WatchType?) {
if (!isLocalList || state == null) return if (!isLocalList || state == null) return
bottomPreviewBinding?.resultviewPreviewBookmark?.apply { bottomPreviewBinding?.resultviewPreviewBookmark?.apply {
@ -1360,13 +1368,42 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
} }
} }
observe(viewModel.watchStatus) { state -> fun setSubscribeStatus(state: Boolean?) {
setWatchStatus(state) bottomPreviewBinding?.resultviewPreviewSubscribe?.apply {
} if (state != null) {
observe(syncViewModel.userData) { status -> val drawable = if (state) {
setUserData(status) R.drawable.ic_baseline_notifications_active_24
} else {
R.drawable.baseline_notifications_none_24
}
setImageResource(drawable)
}
isVisible = state != null
setOnClickListener {
viewModel.toggleSubscriptionStatus(context) { newStatus: Boolean? ->
if (newStatus == null) return@toggleSubscriptionStatus
val message = if (newStatus) {
// Kinda icky to have this here, but it works.
SubscriptionWorkManager.enqueuePeriodicWork(context)
R.string.subscription_new
} else {
R.string.subscription_deleted
}
val name = (viewModel.page.value as? Resource.Success)?.value?.title
?: txt(R.string.no_data).asStringNull(context) ?: ""
showToast(txt(message, name), Toast.LENGTH_SHORT)
}
}
}
} }
observe(viewModel.watchStatus,::setWatchStatus)
observe(syncViewModel.userData, ::setUserData)
observeNullable(viewModel.subscribeStatus, ::setSubscribeStatus)
observeNullable(viewModel.page) { resource -> observeNullable(viewModel.page) { resource ->
if (resource == null) { if (resource == null) {
hidePreviewPopupDialog() hidePreviewPopupDialog()
@ -1408,6 +1445,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
setUserData(syncViewModel.userData.value) setUserData(syncViewModel.userData.value)
setWatchStatus(viewModel.watchStatus.value) setWatchStatus(viewModel.watchStatus.value)
setSubscribeStatus(viewModel.subscribeStatus.value)
resultviewPreviewBookmark.setOnClickListener { resultviewPreviewBookmark.setOnClickListener {
//viewModel.updateWatchStatus(WatchType.PLANTOWATCH) //viewModel.updateWatchStatus(WatchType.PLANTOWATCH)
@ -1426,7 +1464,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
) )
} }
} else { } else {
val value = (syncViewModel.userData.value as? Resource.Success)?.value?.status ?: SyncWatchType.NONE val value =
(syncViewModel.userData.value as? Resource.Success)?.value?.status
?: SyncWatchType.NONE
this@MainActivity.showBottomDialog( this@MainActivity.showBottomDialog(
SyncWatchType.values().map { getString(it.stringRes) }.toList(), SyncWatchType.values().map { getString(it.stringRes) }.toList(),
@ -1453,7 +1493,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
resultviewPreviewFavorite.setImageResource(drawable) resultviewPreviewFavorite.setImageResource(drawable)
} }
resultviewPreviewFavorite.setOnClickListener{ resultviewPreviewFavorite.setOnClickListener {
viewModel.toggleFavoriteStatus(this@MainActivity) { newStatus: Boolean? -> viewModel.toggleFavoriteStatus(this@MainActivity) { newStatus: Boolean? ->
if (newStatus == null) return@toggleFavoriteStatus if (newStatus == null) return@toggleFavoriteStatus
@ -1469,7 +1509,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
} }
} }
if (!isTvSettings()) // dont want this clickable on tv layout if (isLayout(PHONE)) // dont want this clickable on tv layout
resultviewPreviewDescription.setOnClickListener { view -> resultviewPreviewDescription.setOnClickListener { view ->
view.context?.let { ctx -> view.context?.let { ctx ->
val builder: AlertDialog.Builder = val builder: AlertDialog.Builder =
@ -1544,7 +1584,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
} }
} }
if (isTvSettings()) { if (isLayout(TV or EMULATOR)) {
if (navDestination.matchDestination(R.id.navigation_home)) { if (navDestination.matchDestination(R.id.navigation_home)) {
attachBackPressedCallback() attachBackPressedCallback()
} else detachBackPressedCallback() } else detachBackPressedCallback()
@ -1580,7 +1620,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
itemRippleColor = rippleColor itemRippleColor = rippleColor
itemActiveIndicatorColor = rippleColor itemActiveIndicatorColor = rippleColor
setupWithNavController(navController) setupWithNavController(navController)
if (isTvSettings()) { if (isLayout(TV or EMULATOR)) {
background?.alpha = 200 background?.alpha = 200
} else { } else {
background?.alpha = 255 background?.alpha = 255

View file

@ -1,5 +1,6 @@
package com.lagradost.cloudstream3.services package com.lagradost.cloudstream3.services
import android.annotation.SuppressLint
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
@ -12,7 +13,7 @@ import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel
@ -97,128 +98,138 @@ class SubscriptionWorkManager(val context: Context, workerParams: WorkerParamete
) )
} }
@SuppressLint("UnspecifiedImmutableFlag")
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
try {
// println("Update subscriptions!") // println("Update subscriptions!")
context.createNotificationChannel( context.createNotificationChannel(
SUBSCRIPTION_CHANNEL_ID, SUBSCRIPTION_CHANNEL_ID,
SUBSCRIPTION_CHANNEL_NAME, SUBSCRIPTION_CHANNEL_NAME,
SUBSCRIPTION_CHANNEL_DESCRIPTION SUBSCRIPTION_CHANNEL_DESCRIPTION
)
setForeground(
ForegroundInfo(
SUBSCRIPTION_NOTIFICATION_ID,
progressNotificationBuilder.build()
) )
)
val subscriptions = getAllSubscriptions() setForeground(
ForegroundInfo(
SUBSCRIPTION_NOTIFICATION_ID,
progressNotificationBuilder.build()
)
)
if (subscriptions.isEmpty()) { val subscriptions = getAllSubscriptions()
WorkManager.getInstance(context).cancelWorkById(this.id)
if (subscriptions.isEmpty()) {
WorkManager.getInstance(context).cancelWorkById(this.id)
return Result.success()
}
val max = subscriptions.size
var progress = 0
updateProgress(max, progress, true)
// We need all plugins loaded.
PluginManager.loadAllOnlinePlugins(context)
PluginManager.loadAllLocalPlugins(context, false)
subscriptions.apmap { savedData ->
try {
val id = savedData.id ?: return@apmap null
val api = getApiFromNameNull(savedData.apiName) ?: return@apmap null
// Reasonable timeout to prevent having this worker run forever.
val response = withTimeoutOrNull(60_000) {
api.load(savedData.url) as? EpisodeResponse
} ?: return@apmap null
val dubPreference =
getDub(id) ?: if (
context.getApiDubstatusSettings().contains(DubStatus.Dubbed)
) {
DubStatus.Dubbed
} else {
DubStatus.Subbed
}
val latestEpisodes = response.getLatestEpisodes()
val latestPreferredEpisode = latestEpisodes[dubPreference]
val (shouldUpdate, latestEpisode) = if (latestPreferredEpisode != null) {
val latestSeenEpisode =
savedData.lastSeenEpisodeCount[dubPreference] ?: Int.MIN_VALUE
val shouldUpdate = latestPreferredEpisode > latestSeenEpisode
shouldUpdate to latestPreferredEpisode
} else {
val latestEpisode = latestEpisodes[DubStatus.None] ?: Int.MIN_VALUE
val latestSeenEpisode =
savedData.lastSeenEpisodeCount[DubStatus.None] ?: Int.MIN_VALUE
val shouldUpdate = latestEpisode > latestSeenEpisode
shouldUpdate to latestEpisode
}
DataStoreHelper.updateSubscribedData(
id,
savedData,
response
)
if (shouldUpdate) {
val updateHeader = savedData.name
val updateDescription = txt(
R.string.subscription_episode_released,
latestEpisode,
savedData.name
).asString(context)
val intent = Intent(context, MainActivity::class.java).apply {
data = savedData.url.toUri()
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
} else {
PendingIntent.getActivity(context, 0, intent, 0)
}
val poster = ioWork {
savedData.posterUrl?.let { url ->
context.getImageBitmapFromUrl(
url,
savedData.posterHeaders
)
}
}
val updateNotification =
updateNotificationBuilder.setContentTitle(updateHeader)
.setContentText(updateDescription)
.setContentIntent(pendingIntent)
.setLargeIcon(poster)
.build()
notificationManager.notify(id, updateNotification)
}
// You can probably get some issues here since this is async but it does not matter much.
updateProgress(max, ++progress, false)
} catch (t: Throwable) {
logError(t)
}
}
return Result.success()
} catch (t: Throwable) {
logError(t)
// ye, while this is not correct, but because gods know why android just crashes
// and this causes major battery usage as it retries it inf times. This is better, just
// in case android decides to be android and fuck us
return Result.success() return Result.success()
} }
val max = subscriptions.size
var progress = 0
updateProgress(max, progress, true)
// We need all plugins loaded.
PluginManager.loadAllOnlinePlugins(context)
PluginManager.loadAllLocalPlugins(context, false)
subscriptions.apmap { savedData ->
try {
val id = savedData.id ?: return@apmap null
val api = getApiFromNameNull(savedData.apiName) ?: return@apmap null
// Reasonable timeout to prevent having this worker run forever.
val response = withTimeoutOrNull(60_000) {
api.load(savedData.url) as? EpisodeResponse
} ?: return@apmap null
val dubPreference =
getDub(id) ?: if (
context.getApiDubstatusSettings().contains(DubStatus.Dubbed)
) {
DubStatus.Dubbed
} else {
DubStatus.Subbed
}
val latestEpisodes = response.getLatestEpisodes()
val latestPreferredEpisode = latestEpisodes[dubPreference]
val (shouldUpdate, latestEpisode) = if (latestPreferredEpisode != null) {
val latestSeenEpisode =
savedData.lastSeenEpisodeCount[dubPreference] ?: Int.MIN_VALUE
val shouldUpdate = latestPreferredEpisode > latestSeenEpisode
shouldUpdate to latestPreferredEpisode
} else {
val latestEpisode = latestEpisodes[DubStatus.None] ?: Int.MIN_VALUE
val latestSeenEpisode =
savedData.lastSeenEpisodeCount[DubStatus.None] ?: Int.MIN_VALUE
val shouldUpdate = latestEpisode > latestSeenEpisode
shouldUpdate to latestEpisode
}
DataStoreHelper.updateSubscribedData(
id,
savedData,
response
)
if (shouldUpdate) {
val updateHeader = savedData.name
val updateDescription = txt(
R.string.subscription_episode_released,
latestEpisode,
savedData.name
).asString(context)
val intent = Intent(context, MainActivity::class.java).apply {
data = savedData.url.toUri()
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
} else {
PendingIntent.getActivity(context, 0, intent, 0)
}
val poster = ioWork {
savedData.posterUrl?.let { url ->
context.getImageBitmapFromUrl(
url,
savedData.posterHeaders
)
}
}
val updateNotification =
updateNotificationBuilder.setContentTitle(updateHeader)
.setContentText(updateDescription)
.setContentIntent(pendingIntent)
.setLargeIcon(poster)
.build()
notificationManager.notify(id, updateNotification)
}
// You can probably get some issues here since this is async but it does not matter much.
updateProgress(max, ++progress, false)
} catch (_: Throwable) {
}
}
return Result.success()
} }
} }

View file

@ -8,7 +8,8 @@ import com.lagradost.cloudstream3.syncproviders.SyncIdName
import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.ui.library.ListSorting
import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.Coroutines.ioWork
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions
@ -71,9 +72,9 @@ class LocalList : SyncAPI {
}?.distinctBy { it.first } ?: return null }?.distinctBy { it.first } ?: return null
val list = ioWork { val list = ioWork {
val isTrueTv = isTrueTvSettings() val isTrueTv = isLayout(TV)
val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate { val baseMap = WatchType.entries.filter { it != WatchType.NONE }.associate {
// None is not something to display // None is not something to display
it.stringRes to emptyList<SyncAPI.LibraryItem>() it.stringRes to emptyList<SyncAPI.LibraryItem>()
} + mapOf( } + mapOf(

View file

@ -12,7 +12,9 @@ import com.lagradost.cloudstream3.databinding.AccountListItemBinding
import com.lagradost.cloudstream3.databinding.AccountListItemEditBinding import com.lagradost.cloudstream3.databinding.AccountListItemEditBinding
import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountEditDialog import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountEditDialog
import com.lagradost.cloudstream3.ui.result.setImage import com.lagradost.cloudstream3.ui.result.setImage
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
@ -38,7 +40,7 @@ class AccountAdapter(
is AccountListItemBinding -> binding.apply { is AccountListItemBinding -> binding.apply {
if (account == null) return@apply if (account == null) return@apply
val isTv = isTvSettings() || !root.isInTouchMode val isTv = isLayout(TV or EMULATOR) || !root.isInTouchMode
val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex
@ -80,7 +82,7 @@ class AccountAdapter(
is AccountListItemEditBinding -> binding.apply { is AccountListItemEditBinding -> binding.apply {
if (account == null) return@apply if (account == null) return@apply
val isTv = isTvSettings() || !root.isInTouchMode val isTv = isLayout(TV or EMULATOR) || !root.isInTouchMode
val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex

View file

@ -18,8 +18,10 @@ import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.AutofitRecyclerView import com.lagradost.cloudstream3.ui.AutofitRecyclerView
import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_EDIT_ACCOUNT import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_EDIT_ACCOUNT
import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_SELECT_ACCOUNT import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_SELECT_ACCOUNT
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTruePhone import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.BiometricAuthenticator import com.lagradost.cloudstream3.utils.BiometricAuthenticator
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
@ -54,7 +56,7 @@ class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.Biomet
fun askBiometricAuth() { fun askBiometricAuth() {
if (isTruePhone() && authEnabled) { if (isLayout(PHONE) && authEnabled) {
if (deviceHasPasswordPinLock(this)) { if (deviceHasPasswordPinLock(this)) {
startBiometricAuthentication( startBiometricAuthentication(
this, this,
@ -62,8 +64,8 @@ class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.Biomet
false false
) )
BiometricAuthenticator.promptInfo?.let { BiometricAuthenticator.promptInfo?.let { promt ->
BiometricAuthenticator.biometricPrompt?.authenticate(it) BiometricAuthenticator.biometricPrompt?.authenticate(promt)
} }
} }
} }
@ -127,7 +129,7 @@ class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.Biomet
recyclerView.adapter = adapter recyclerView.adapter = adapter
if (isTvSettings()) { if (isLayout(TV or EMULATOR)) {
binding.editAccountButton.setBackgroundResource( binding.editAccountButton.setBackgroundResource(
R.drawable.player_button_tv_attr_no_bg R.drawable.player_button_tv_attr_no_bg
) )
@ -168,7 +170,7 @@ class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.Biomet
viewModel.toggleIsEditing() viewModel.toggleIsEditing()
} }
if (isTvSettings()) { if (isLayout(TV or EMULATOR)) {
recyclerView.spanCount = if (liveAccounts.count() + 1 <= 6) { recyclerView.spanCount = if (liveAccounts.count() + 1 <= 6) {
liveAccounts.count() + 1 liveAccounts.count() + 1
} else 6 } else 6

View file

@ -5,6 +5,7 @@ import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.format.Formatter.formatShortFileSize
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -13,17 +14,25 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding
import com.lagradost.cloudstream3.databinding.StreamInputBinding
import com.lagradost.cloudstream3.isMovieType import com.lagradost.cloudstream3.isMovieType
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
import com.lagradost.cloudstream3.ui.player.BasicLink
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.player.LinkGenerator import com.lagradost.cloudstream3.ui.player.LinkGenerator
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE
@ -34,15 +43,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager
import android.text.format.Formatter.formatShortFileSize
import androidx.core.widget.doOnTextChanged
import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding
import com.lagradost.cloudstream3.databinding.StreamInputBinding
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.ui.player.BasicLink
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import java.net.URI import java.net.URI
@ -200,7 +200,7 @@ class DownloadFragment : Fragment() {
} }
// Should be visible in emulator layout // Should be visible in emulator layout
binding?.downloadStreamButton?.isGone = isTrueTvSettings() binding?.downloadStreamButton?.isGone = isLayout(TV)
binding?.downloadStreamButton?.setOnClickListener { binding?.downloadStreamButton?.setOnClickListener {
val dialog = val dialog =
Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom) Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom)

View file

@ -2,16 +2,19 @@ package com.lagradost.cloudstream3.ui.download.button
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Looper
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.annotation.MainThread
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DELETE_FILE import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DELETE_FILE
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK
@ -241,40 +244,54 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
} }
}*/ }*/
@MainThread
private fun setStatusInternal(status : DownloadStatusTell?) {
val isPreActive = isZeroBytes && status == DownloadStatusTell.IsDownloading
if (animateWaiting && (status == DownloadStatusTell.IsPending || isPreActive)) {
val animation = AnimationUtils.loadAnimation(context, waitingAnimation)
progressBarBackground.startAnimation(animation)
} else {
progressBarBackground.clearAnimation()
}
val progressDrawable =
if (status == DownloadStatusTell.IsDownloading && !isPreActive) activeOutline else nonActiveOutline
progressBarBackground.background =
ContextCompat.getDrawable(context, progressDrawable)
val drawable = getDrawableFromStatus(status)
statusView.setImageDrawable(drawable)
val isDrawable = drawable != null
statusView.isVisible = isDrawable
val hide = hideWhenIcon && isDrawable
if (hide) {
progressBar.clearAnimation()
progressBarBackground.clearAnimation()
}
progressBarBackground.isGone = hide
progressBar.isGone = hide
}
/** Also sets currentStatus */ /** Also sets currentStatus */
override fun setStatus(status: DownloadStatusTell?) { override fun setStatus(status: DownloadStatusTell?) {
currentStatus = status currentStatus = status
//progressBar.isVisible = // runs on the main thread, but also instant if it already is
// status != null && status != DownloadStatusTell.Complete && status != DownloadStatusTell.Error if (Looper.myLooper() == Looper.getMainLooper()) {
//progressBarBackground.isVisible = status != null && status != DownloadStatusTell.Complete try {
progressBarBackground.post { setStatusInternal(status)
val isPreActive = isZeroBytes && status == DownloadStatusTell.IsDownloading } catch (t : Throwable) {
if (animateWaiting && (status == DownloadStatusTell.IsPending || isPreActive)) { logError(t) // just in case setStatusInternal throws because thread
val animation = AnimationUtils.loadAnimation(context, waitingAnimation) progressBarBackground.post {
progressBarBackground.startAnimation(animation) setStatusInternal(status)
} else { }
progressBarBackground.clearAnimation()
} }
} else {
val progressDrawable = progressBarBackground.post {
if (status == DownloadStatusTell.IsDownloading && !isPreActive) activeOutline else nonActiveOutline setStatusInternal(status)
progressBarBackground.background =
ContextCompat.getDrawable(context, progressDrawable)
val drawable = getDrawableFromStatus(status)
statusView.setImageDrawable(drawable)
val isDrawable = drawable != null
statusView.isVisible = isDrawable
val hide = hideWhenIcon && isDrawable
if (hide) {
progressBar.clearAnimation()
progressBarBackground.clearAnimation()
} }
progressBarBackground.isGone = hide
progressBar.isGone = hide
} }
} }

View file

@ -42,8 +42,10 @@ import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountSelectLine
import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.search.* import com.lagradost.cloudstream3.ui.search.*
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.AppUtils.ownHide import com.lagradost.cloudstream3.utils.AppUtils.ownHide
@ -311,7 +313,7 @@ class HomeFragment : Fragment() {
button?.isVisible = isValid button?.isVisible = isValid
button?.isChecked = isValid && selectedTypes.any { types.contains(it) } button?.isChecked = isValid && selectedTypes.any { types.contains(it) }
button?.isFocusable = true button?.isFocusable = true
if (isTrueTvSettings()) { if (isLayout(TV)) {
button?.isFocusableInTouchMode = true button?.isFocusableInTouchMode = true
} }
@ -435,7 +437,7 @@ class HomeFragment : Fragment() {
bottomSheetDialog?.ownShow() bottomSheetDialog?.ownShow()
val layout = val layout =
if (isTvSettings()) R.layout.fragment_home_tv else R.layout.fragment_home if (isLayout(TV or EMULATOR)) R.layout.fragment_home_tv else R.layout.fragment_home
val root = inflater.inflate(layout, container, false) val root = inflater.inflate(layout, container, false)
binding = try { binding = try {
FragmentHomeBinding.bind(root) FragmentHomeBinding.bind(root)
@ -449,6 +451,11 @@ class HomeFragment : Fragment() {
} }
override fun onDestroyView() { override fun onDestroyView() {
homeMasterAdapter?.onSaveInstanceState(
instanceState,
binding?.homeMasterRecycler
)
bottomSheetDialog?.ownHide() bottomSheetDialog?.ownHide()
binding = null binding = null
super.onDestroyView() super.onDestroyView()
@ -485,6 +492,10 @@ class HomeFragment : Fragment() {
private var bottomSheetDialog: BottomSheetDialog? = null private var bottomSheetDialog: BottomSheetDialog? = null
// https://github.com/vivchar/RendererRecyclerViewAdapter/blob/185251ee9d94fb6eb3e063b00d646b745186c365/example/src/main/java/com/github/vivchar/example/pages/github/GithubFragment.kt#L32
// cry about it, but this is android we are talking about, we cant do the most simple shit without making a global variable
private var instanceState: Bundle = Bundle()
private var homeMasterAdapter: HomeParentItemAdapterPreview? = null
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -505,15 +516,16 @@ class HomeFragment : Fragment() {
activity.loadSearchResult(listHomepageItems.random()) activity.loadSearchResult(listHomepageItems.random())
} }
} }
homeMasterAdapter = HomeParentItemAdapterPreview(
homeMasterRecycler.adapter = mutableListOf(),
HomeParentItemAdapterPreview( homeViewModel,
mutableListOf(), ).apply {
homeViewModel onRestoreInstanceState(instanceState)
) }
homeMasterRecycler.adapter = homeMasterAdapter
//fixPaddingStatusbar(homeLoadingStatusbar) //fixPaddingStatusbar(homeLoadingStatusbar)
homeApiFab.isVisible = !isTvSettings() homeApiFab.isVisible = isLayout(PHONE)
homeMasterRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { homeMasterRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
@ -521,7 +533,7 @@ class HomeFragment : Fragment() {
homeApiFab.shrink() // hide homeApiFab.shrink() // hide
homeRandom.shrink() homeRandom.shrink()
} else if (dy < -5) { } else if (dy < -5) {
if (!isTvSettings()) { if (isLayout(PHONE)) {
homeApiFab.extend() // show homeApiFab.extend() // show
homeRandom.extend() homeRandom.extend()
} }
@ -540,7 +552,7 @@ class HomeFragment : Fragment() {
settingsManager.getBoolean( settingsManager.getBoolean(
getString(R.string.random_button_key), getString(R.string.random_button_key),
false false
) && !isTvSettings() ) && isLayout(PHONE)
binding?.homeRandom?.visibility = View.GONE binding?.homeRandom?.visibility = View.GONE
} }

View file

@ -1,5 +1,7 @@
package com.lagradost.cloudstream3.ui.home package com.lagradost.cloudstream3.ui.home
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -7,16 +9,20 @@ import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListUpdateCallback import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.HomePageList
import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.HomepageParentBinding import com.lagradost.cloudstream3.databinding.HomepageParentBinding
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable
class LoadClickCallback( class LoadClickCallback(
@ -32,18 +38,90 @@ open class ParentItemAdapter(
private val clickCallback: (SearchClickCallback) -> Unit, private val clickCallback: (SearchClickCallback) -> Unit,
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit, private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
private val expandCallback: ((String) -> Unit)? = null, private val expandCallback: ((String) -> Unit)? = null,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { // Ok, this is fucked, but there is a reason for this as we want to resume 1. when scrolling up and down
// and 2. when doing into a thing and coming back. 1 is always active, but 2 requires doing it in the fragment
// as OnCreateView is called and this adapter is recreated losing the internal state to the GC
//
// 1. This works by having the adapter having a internal state "scrollStates" that keeps track of the states
// when a view recycles, it looks up this internal state
// 2. To solve the the coming back shit we have to save "scrollStates" to a Bundle inside the
// fragment via onSaveInstanceState, because this cant be easy for some reason as the adapter does
// not have a state but the layout-manager for no reason, then it is resumed via onRestoreInstanceState
//
// Even when looking at a real example they do this :skull:
// https://github.com/vivchar/RendererRecyclerViewAdapter/blob/185251ee9d94fb6eb3e063b00d646b745186c365/example/src/main/java/com/github/vivchar/example/pages/github/GithubFragment.kt#L32
private val scrollStates = mutableMapOf<Int, Parcelable?>()
companion object {
private const val SCROLL_KEY: String = "ParentItemAdapter::scrollStates.keys"
private const val SCROLL_VALUE: String = "ParentItemAdapter::scrollStates.values"
}
open fun onRestoreInstanceState(savedInstanceState: Bundle?) {
try {
val keys = savedInstanceState?.getIntArray(SCROLL_KEY) ?: intArrayOf()
val values = savedInstanceState?.getParcelableArray(SCROLL_VALUE) ?: arrayOf()
for ((k, v) in keys.zip(values)) {
this.scrollStates[k] = v
}
} catch (t: Throwable) {
logError(t)
}
}
open fun onSaveInstanceState(outState: Bundle, recyclerView: RecyclerView? = null) {
if (recyclerView != null) {
for (position in items.indices) {
val holder = recyclerView.findViewHolderForAdapterPosition(position) ?: continue
saveHolder(holder)
}
}
outState.putIntArray(SCROLL_KEY, scrollStates.keys.toIntArray())
outState.putParcelableArray(SCROLL_VALUE, scrollStates.values.toTypedArray())
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
when (holder) {
is ParentViewHolder -> {
holder.bind(items[position])
scrollStates[holder.absoluteAdapterPosition]?.let {
holder.binding.homeChildRecyclerview.layoutManager?.onRestoreInstanceState(it)
}
}
}
}
private fun saveHolder(holder : ViewHolder) {
when (holder) {
is ParentViewHolder -> {
scrollStates[holder.absoluteAdapterPosition] =
holder.binding.homeChildRecyclerview.layoutManager?.onSaveInstanceState()
}
}
}
override fun onViewRecycled(holder: ViewHolder) {
saveHolder(holder)
super.onViewRecycled(holder)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutResId = when { val layoutResId = when {
isTrueTvSettings() -> R.layout.homepage_parent_tv isLayout(TV) -> R.layout.homepage_parent_tv
parent.context.isEmulatorSettings() -> R.layout.homepage_parent_emulator isLayout(EMULATOR) -> R.layout.homepage_parent_emulator
else -> R.layout.homepage_parent else -> R.layout.homepage_parent
} }
val root = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false) val inflater = LayoutInflater.from(parent.context)
val binding = try {
val binding = HomepageParentBinding.bind(root) HomepageParentBinding.bind(inflater.inflate(layoutResId, parent, false))
} catch (t : Throwable) {
logError(t)
// just in case someone forgot we don't want to crash
HomepageParentBinding.inflate(inflater)
}
return ParentViewHolder( return ParentViewHolder(
binding, binding,
@ -53,14 +131,6 @@ open class ParentItemAdapter(
) )
} }
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ParentViewHolder -> {
holder.bind(items[position])
}
}
}
override fun getItemCount(): Int { override fun getItemCount(): Int {
return items.size return items.size
} }
@ -116,7 +186,6 @@ open class ParentItemAdapter(
} }
override fun onChanged(_position: Int, count: Int, payload: Any?) { override fun onChanged(_position: Int, count: Int, payload: Any?) {
val position = _position + delta val position = _position + delta
// I know kinda messy, what this does is using the update or bind instead of onCreateViewHolder -> bind // I know kinda messy, what this does is using the update or bind instead of onCreateViewHolder -> bind
@ -155,15 +224,15 @@ open class ParentItemAdapter(
//diffResult.dispatchUpdatesTo(this) //diffResult.dispatchUpdatesTo(this)
} }
class ParentViewHolder
constructor( class ParentViewHolder(
val binding: HomepageParentBinding, val binding: HomepageParentBinding,
// val viewModel: HomeViewModel, // val viewModel: HomeViewModel,
private val clickCallback: (SearchClickCallback) -> Unit, private val clickCallback: (SearchClickCallback) -> Unit,
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit, private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
private val expandCallback: ((String) -> Unit)? = null, private val expandCallback: ((String) -> Unit)? = null,
) : ) :
RecyclerView.ViewHolder(binding.root) { ViewHolder(binding.root) {
val title: TextView = binding.homeChildMoreInfo val title: TextView = binding.homeChildMoreInfo
private val recyclerView: RecyclerView = binding.homeChildRecyclerview private val recyclerView: RecyclerView = binding.homeChildRecyclerview
private val startFocus = R.id.nav_rail_view private val startFocus = R.id.nav_rail_view
@ -237,7 +306,7 @@ open class ParentItemAdapter(
}) })
//(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged() //(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
if (!isTrueTvSettings()) { if (isLayout(PHONE)) {
title.setOnClickListener { title.setOnClickListener {
moreInfoClickCallback.invoke(expand) moreInfoClickCallback.invoke(expand)
} }

View file

@ -36,8 +36,9 @@ import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD 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.SEARCH_ACTION_SHOW_METADATA
import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes
@ -48,7 +49,8 @@ import com.lagradost.cloudstream3.utils.UIHelper.populateChips
class HomeParentItemAdapterPreview( class HomeParentItemAdapterPreview(
items: MutableList<HomeViewModel.ExpandableHomepageList>, items: MutableList<HomeViewModel.ExpandableHomepageList>,
private val viewModel: HomeViewModel, private val viewModel: HomeViewModel,
) : ParentItemAdapter(items, clickCallback = { ) : ParentItemAdapter(items,
clickCallback = {
viewModel.click(it) viewModel.click(it)
}, moreInfoClickCallback = { }, moreInfoClickCallback = {
viewModel.popup(it) viewModel.popup(it)
@ -78,13 +80,13 @@ class HomeParentItemAdapterPreview(
return when (viewType) { return when (viewType) {
VIEW_TYPE_HEADER -> { VIEW_TYPE_HEADER -> {
val inflater = LayoutInflater.from(parent.context) val inflater = LayoutInflater.from(parent.context)
val binding = if (isTvSettings()) FragmentHomeHeadTvBinding.inflate( val binding = if (isLayout(TV or EMULATOR)) FragmentHomeHeadTvBinding.inflate(
inflater, inflater,
parent, parent,
false false
) else FragmentHomeHeadBinding.inflate(inflater, parent, false) ) else FragmentHomeHeadBinding.inflate(inflater, parent, false)
if (binding is FragmentHomeHeadTvBinding && parent.context.isEmulatorSettings()) { if (binding is FragmentHomeHeadTvBinding && isLayout(EMULATOR)) {
binding.homeBookmarkParentItemMoreInfo.isVisible = true binding.homeBookmarkParentItemMoreInfo.isVisible = true
val marginInDp = 50 val marginInDp = 50
@ -598,7 +600,7 @@ class HomeParentItemAdapterPreview(
if ( if (
binding is FragmentHomeHeadBinding || binding is FragmentHomeHeadBinding ||
binding is FragmentHomeHeadTvBinding && binding is FragmentHomeHeadTvBinding &&
binding.root.context.isEmulatorSettings() isLayout(EMULATOR)
) { ) {
val title = (binding as? FragmentHomeHeadBinding)?.homeWatchParentItemTitle val title = (binding as? FragmentHomeHeadBinding)?.homeWatchParentItemTitle
?: (binding as? FragmentHomeHeadTvBinding)?.homeWatchParentItemTitle ?: (binding as? FragmentHomeHeadTvBinding)?.homeWatchParentItemTitle
@ -628,7 +630,7 @@ class HomeParentItemAdapterPreview(
if ( if (
binding is FragmentHomeHeadBinding || binding is FragmentHomeHeadBinding ||
binding is FragmentHomeHeadTvBinding && binding is FragmentHomeHeadTvBinding &&
binding.root.context.isEmulatorSettings() isLayout(EMULATOR)
) { ) {
val title = (binding as? FragmentHomeHeadBinding)?.homeBookmarkParentItemTitle val title = (binding as? FragmentHomeHeadBinding)?.homeBookmarkParentItemTitle
?: (binding as? FragmentHomeHeadTvBinding)?.homeBookmarkParentItemTitle ?: (binding as? FragmentHomeHeadTvBinding)?.homeBookmarkParentItemTitle

View file

@ -10,7 +10,9 @@ import androidx.viewbinding.ViewBinding
import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.databinding.HomeScrollViewBinding import com.lagradost.cloudstream3.databinding.HomeScrollViewBinding
import com.lagradost.cloudstream3.databinding.HomeScrollViewTvBinding import com.lagradost.cloudstream3.databinding.HomeScrollViewTvBinding
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
class HomeScrollAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { class HomeScrollAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
@ -40,7 +42,7 @@ class HomeScrollAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context) val inflater = LayoutInflater.from(parent.context)
val binding = if (isTvSettings()) { val binding = if (isLayout(TV or EMULATOR)) {
HomeScrollViewTvBinding.inflate(inflater, parent, false) HomeScrollViewTvBinding.inflate(inflater, parent, false)
} else { } else {
HomeScrollViewBinding.inflate(inflater, parent, false) HomeScrollViewBinding.inflate(inflater, parent, false)

View file

@ -34,7 +34,8 @@ import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED
import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.search.SearchHelper import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppUtils.addProgramsToContinueWatching import com.lagradost.cloudstream3.utils.AppUtils.addProgramsToContinueWatching
import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
@ -132,7 +133,7 @@ class HomeViewModel : ViewModel() {
private fun loadResumeWatching() = viewModelScope.launchSafe { private fun loadResumeWatching() = viewModelScope.launchSafe {
val resumeWatchingResult = getResumeWatching() val resumeWatchingResult = getResumeWatching()
if (isTrueTvSettings() && resumeWatchingResult != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (isLayout(TV) && resumeWatchingResult != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ioSafe { ioSafe {
// this WILL crash on non tvs, so keep this inside a try catch // this WILL crash on non tvs, so keep this inside a try catch
activity?.addProgramsToContinueWatching(resumeWatchingResult) activity?.addProgramsToContinueWatching(resumeWatchingResult)

View file

@ -49,6 +49,11 @@ import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD 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.SEARCH_ACTION_SHOW_METADATA
import com.lagradost.cloudstream3.ui.settings.Globals
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment import com.lagradost.cloudstream3.ui.settings.SettingsFragment
import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
@ -101,7 +106,7 @@ class LibraryFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View { ): View {
val layout = val layout =
if (SettingsFragment.isTvSettings()) R.layout.fragment_library_tv else R.layout.fragment_library if (isLayout(TV or EMULATOR)) R.layout.fragment_library_tv else R.layout.fragment_library
val root = inflater.inflate(layout, container, false) val root = inflater.inflate(layout, container, false)
binding = try { binding = try {
FragmentLibraryBinding.bind(root) FragmentLibraryBinding.bind(root)
@ -220,7 +225,7 @@ class LibraryFragment : Fragment() {
settingsManager.getBoolean( settingsManager.getBoolean(
getString(R.string.random_button_key), getString(R.string.random_button_key),
false false
) && !SettingsFragment.isTvSettings() ) && isLayout(PHONE)
binding?.libraryRandom?.visibility = View.GONE binding?.libraryRandom?.visibility = View.GONE
} }

View file

@ -1,7 +1,6 @@
package com.lagradost.cloudstream3.ui.library package com.lagradost.cloudstream3.ui.library
import android.os.Build import android.os.Build
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.doOnAttach import androidx.core.view.doOnAttach
@ -12,7 +11,9 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.LibraryViewpagerPageBinding import com.lagradost.cloudstream3.databinding.LibraryViewpagerPageBinding
import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.settings.SettingsFragment import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
class ViewpagerAdapter( class ViewpagerAdapter(
@ -73,7 +74,7 @@ class ViewpagerAdapter(
val diff = scrollY - oldScrollY val diff = scrollY - oldScrollY
//Expand the top Appbar based on scroll direction up/down, simulate phone behavior //Expand the top Appbar based on scroll direction up/down, simulate phone behavior
if (SettingsFragment.isTvSettings()) { if (isLayout(TV or EMULATOR)) {
binding.root.rootView.findViewById<AppBarLayout>(R.id.search_bar) binding.root.rootView.findViewById<AppBarLayout>(R.id.search_bar)
.apply { .apply {
if (diff <= 0) if (diff <= 0)

View file

@ -46,6 +46,7 @@ import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvid
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
import com.lagradost.cloudstream3.ui.result.setText import com.lagradost.cloudstream3.ui.result.setText
import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.settings.Globals
import com.lagradost.cloudstream3.ui.settings.SettingsFragment import com.lagradost.cloudstream3.ui.settings.SettingsFragment
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper
@ -1514,7 +1515,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
} }
} }
// cs3 is peak media center // cs3 is peak media center
setRemainingTimeCounter(durationMode || SettingsFragment.isTrueTvSettings()) setRemainingTimeCounter(durationMode || Globals.isLayout(Globals.TV))
playerBinding?.exoPosition?.doOnTextChanged { _, _, _, _ -> playerBinding?.exoPosition?.doOnTextChanged { _, _, _, _ ->
updateRemainingTime() updateRemainingTime()
} }

View file

@ -39,7 +39,10 @@ import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSub
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
import com.lagradost.cloudstream3.ui.result.* import com.lagradost.cloudstream3.ui.result.*
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1 import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
@ -1275,8 +1278,7 @@ class GeneratorPlayer : FullScreenPlayer() {
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? { ): View? {
// this is used instead of layout-television to follow the settings and some TV devices are not classified as TV for some reason // this is used instead of layout-television to follow the settings and some TV devices are not classified as TV for some reason
isTv = isTvSettings() layout = if (isLayout(TV or EMULATOR)) R.layout.fragment_player_tv else R.layout.fragment_player
layout = if (isTv) R.layout.fragment_player_tv else R.layout.fragment_player
viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java] viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java]
sync = ViewModelProvider(this)[SyncViewModel::class.java] sync = ViewModelProvider(this)[SyncViewModel::class.java]

View file

@ -9,6 +9,9 @@ import android.util.Log
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.core.graphics.scale import androidx.core.graphics.scale
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.settings.Globals
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment import com.lagradost.cloudstream3.ui.settings.SettingsFragment
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
@ -63,7 +66,7 @@ interface IPreviewGenerator {
companion object { companion object {
fun new(): IPreviewGenerator { fun new(): IPreviewGenerator {
/** because TV has low ram + not show we disable this for now */ /** because TV has low ram + not show we disable this for now */
return if (SettingsFragment.isTrueTvSettings()) { return if (isLayout(TV)) {
empty() empty()
} else { } else {
PreviewGenerator() PreviewGenerator()

View file

@ -34,7 +34,8 @@ import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.search.SearchHelper import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.ui.search.SearchViewModel import com.lagradost.cloudstream3.ui.search.SearchViewModel
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppUtils.ownShow import com.lagradost.cloudstream3.utils.AppUtils.ownShow
import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
@ -277,7 +278,7 @@ class QuickSearchFragment : Fragment() {
activity?.popCurrentPage() activity?.popCurrentPage()
} }
if (isTrueTvSettings()) { if (isLayout(TV)) {
binding?.quickSearch?.requestFocus() binding?.quickSearch?.requestFocus()
} }

View file

@ -15,8 +15,10 @@ import com.lagradost.cloudstream3.databinding.ResultEpisodeLargeBinding
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.UIHelper.toPx
@ -172,15 +174,13 @@ class EpisodeAdapter(
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun bind(card: ResultEpisode) { fun bind(card: ResultEpisode) {
localCard = card localCard = card
val setWidth = val setWidth =
if (isTvSettings()) TV_EP_SIZE_LARGE.toPx else ViewGroup.LayoutParams.MATCH_PARENT if (isLayout(TV or EMULATOR)) TV_EP_SIZE_LARGE.toPx else ViewGroup.LayoutParams.MATCH_PARENT
binding.episodeLinHolder.layoutParams.width = setWidth binding.episodeLinHolder.layoutParams.width = setWidth
binding.episodeHolderLarge.layoutParams.width = setWidth binding.episodeHolderLarge.layoutParams.width = setWidth
binding.episodeHolder.layoutParams.width = setWidth binding.episodeHolder.layoutParams.width = setWidth
val isTrueTv = isTrueTvSettings()
binding.apply { binding.apply {
downloadButton.isVisible = hasDownloadSupport downloadButton.isVisible = hasDownloadSupport
@ -249,7 +249,7 @@ class EpisodeAdapter(
var isExpanded = false var isExpanded = false
setOnClickListener { setOnClickListener {
if (isTrueTv) { if (isLayout(TV)) {
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card)) clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
} else { } else {
isExpanded = !isExpanded isExpanded = !isExpanded
@ -260,7 +260,7 @@ class EpisodeAdapter(
} }
} }
if (!isTrueTv) { if (isLayout(EMULATOR or PHONE)) {
episodePoster.setOnClickListener { episodePoster.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
} }
@ -275,7 +275,7 @@ class EpisodeAdapter(
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
} }
if (isTrueTv) { if (isLayout(TV)) {
itemView.isFocusable = true itemView.isFocusable = true
itemView.isFocusableInTouchMode = true itemView.isFocusableInTouchMode = true
//itemView.touchscreenBlocksFocus = false //itemView.touchscreenBlocksFocus = false
@ -300,11 +300,9 @@ class EpisodeAdapter(
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun bind(card: ResultEpisode) { fun bind(card: ResultEpisode) {
val isTrueTv = isTrueTvSettings()
binding.episodeHolder.layoutParams.apply { binding.episodeHolder.layoutParams.apply {
width = width =
if (isTvSettings()) TV_EP_SIZE_SMALL.toPx else ViewGroup.LayoutParams.MATCH_PARENT if (isLayout(TV or EMULATOR)) TV_EP_SIZE_SMALL.toPx else ViewGroup.LayoutParams.MATCH_PARENT
} }
binding.apply { binding.apply {
@ -361,7 +359,7 @@ class EpisodeAdapter(
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card)) clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
} }
if (isTrueTv) { if (isLayout(TV)) {
itemView.isFocusable = true itemView.isFocusable = true
itemView.isFocusableInTouchMode = true itemView.isFocusableInTouchMode = true
//itemView.touchscreenBlocksFocus = false //itemView.touchscreenBlocksFocus = false

View file

@ -5,7 +5,8 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.databinding.ResultMiniImageBinding import com.lagradost.cloudstream3.databinding.ResultMiniImageBinding
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
/* /*
class ImageAdapter(context: Context, val resource: Int) : ArrayAdapter<Int>(context, resource) { class ImageAdapter(context: Context, val resource: Int) : ArrayAdapter<Int>(context, resource) {
@ -83,7 +84,7 @@ class ImageAdapter(
this.nextFocusUpId = nextFocusUp this.nextFocusUpId = nextFocusUp
} }
if (clickCallback != null) { if (clickCallback != null) {
if (isTrueTvSettings()) { if (isLayout(TV)) {
isClickable = true isClickable = true
isLongClickable = true isLongClickable = true
isFocusable = true isFocusable = true

View file

@ -39,8 +39,10 @@ import com.lagradost.cloudstream3.ui.result.ResultFragment.updateUIEvent
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED
import com.lagradost.cloudstream3.ui.search.SearchAdapter import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings import com.lagradost.cloudstream3.ui.settings.Globals
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.AppUtils.isRtl import com.lagradost.cloudstream3.utils.AppUtils.isRtl
import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.loadCache
@ -598,7 +600,7 @@ class ResultFragmentTv : Fragment() {
} }
observeNullable(viewModel.subscribeStatus) { isSubscribed -> observeNullable(viewModel.subscribeStatus) { isSubscribed ->
binding?.resultSubscribe?.isVisible = isSubscribed != null && requireContext().isEmulatorSettings() binding?.resultSubscribe?.isVisible = isSubscribed != null && isLayout(EMULATOR)
binding?.resultSubscribeButton?.apply { binding?.resultSubscribeButton?.apply {
if (isSubscribed == null) return@observeNullable if (isSubscribed == null) return@observeNullable
@ -752,7 +754,7 @@ class ResultFragmentTv : Fragment() {
setRecommendations(recommendations, null) setRecommendations(recommendations, null)
} }
if (isTrueTvSettings()) { if (isLayout(TV)) {
observe(viewModel.episodeSynopsis) { description -> observe(viewModel.episodeSynopsis) { description ->
view.context?.let { ctx -> view.context?.let { ctx ->
val builder: AlertDialog.Builder = val builder: AlertDialog.Builder =
@ -878,7 +880,7 @@ class ResultFragmentTv : Fragment() {
resultDescription.apply { resultDescription.apply {
setTextHtml(d.plotText) setTextHtml(d.plotText)
setOnClickListener { setOnClickListener {
if (context.isEmulatorSettings()) { if (isLayout(EMULATOR)) {
isExpanded = !isExpanded isExpanded = !isExpanded
maxLines = if (isExpanded) { maxLines = if (isExpanded) {
Integer.MAX_VALUE Integer.MAX_VALUE

View file

@ -928,15 +928,20 @@ class ResultViewModel2 : ViewModel() {
) { ) {
val isSubscribed = _subscribeStatus.value ?: return val isSubscribed = _subscribeStatus.value ?: return
val response = currentResponse ?: return val response = currentResponse ?: return
if (response !is EpisodeResponse) return
val currentId = currentId ?: return val currentId = currentId ?: return
// This might be a bit confusing, but even if the loadresponse is not a EpisodeResponse
// _subscribeStatus might be true.
if (isSubscribed) { if (isSubscribed) {
removeSubscribedData(currentId) removeSubscribedData(currentId)
statusChangedCallback?.invoke(false) statusChangedCallback?.invoke(false)
_subscribeStatus.postValue(false) _subscribeStatus.postValue(if (response is EpisodeResponse) false else null)
MainActivity.reloadLibraryEvent(true)
} else { } else {
if (response !is EpisodeResponse) {
return
}
checkAndWarnDuplicates( checkAndWarnDuplicates(
context, context,
LibraryListType.SUBSCRIPTIONS, LibraryListType.SUBSCRIPTIONS,
@ -981,8 +986,8 @@ class ResultViewModel2 : ViewModel() {
) )
_subscribeStatus.postValue(true) _subscribeStatus.postValue(true)
statusChangedCallback?.invoke(true) statusChangedCallback?.invoke(true)
MainActivity.reloadLibraryEvent(true)
} }
} }
} }
@ -2046,12 +2051,15 @@ class ResultViewModel2 : ViewModel() {
} }
private fun postSubscription(loadResponse: LoadResponse) { private fun postSubscription(loadResponse: LoadResponse) {
val id = loadResponse.getId()
val data = getSubscribedData(id)
if (loadResponse.isEpisodeBased()) { if (loadResponse.isEpisodeBased()) {
val id = loadResponse.getId()
val data = getSubscribedData(id)
updateSubscribedData(id, data, loadResponse as? EpisodeResponse) updateSubscribedData(id, data, loadResponse as? EpisodeResponse)
val isSubscribed = data != null _subscribeStatus.postValue(data != null)
_subscribeStatus.postValue(isSubscribed) }
// lets say that we have subscribed, then we must be able to unsubscribe no matter what
else if (data != null) {
_subscribeStatus.postValue(true)
} }
} }
@ -2585,6 +2593,7 @@ class ResultViewModel2 : ViewModel() {
override var posterHeaders: Map<String, String>? = null, override var posterHeaders: Map<String, String>? = null,
override var backgroundPosterUrl: String? = null, override var backgroundPosterUrl: String? = null,
override var contentRating: String? = null, override var contentRating: String? = null,
val id : Int?,
) : LoadResponse ) : LoadResponse
fun loadSmall(activity: Activity?, searchResponse : SearchResponse) = ioSafe { fun loadSmall(activity: Activity?, searchResponse : SearchResponse) = ioSafe {
@ -2594,7 +2603,7 @@ class ResultViewModel2 : ViewModel() {
val api = APIHolder.getApiFromNameNull(searchResponse.apiName) ?: APIHolder.getApiFromUrlNull(searchResponse.url) ?: APIRepository.noneApi val api = APIHolder.getApiFromNameNull(searchResponse.apiName) ?: APIHolder.getApiFromUrlNull(searchResponse.url) ?: APIRepository.noneApi
val repo = APIRepository(api) val repo = APIRepository(api)
val response = LoadResponseFromSearch(name = searchResponse.name, url = searchResponse.url, apiName = api.name, type = searchResponse.type ?: TvType.Others, val response = LoadResponseFromSearch(name = searchResponse.name, url = searchResponse.url, apiName = api.name, type = searchResponse.type ?: TvType.Others,
posterUrl = searchResponse.posterUrl).apply { posterUrl = searchResponse.posterUrl, id = searchResponse.id).apply {
if (searchResponse is SyncAPI.LibraryItem) { if (searchResponse is SyncAPI.LibraryItem) {
this.plot = searchResponse.plot this.plot = searchResponse.plot
this.rating = searchResponse.personalRating?.times(100) ?: searchResponse.rating this.rating = searchResponse.personalRating?.times(100) ?: searchResponse.rating
@ -2606,12 +2615,14 @@ class ResultViewModel2 : ViewModel() {
this.tags = searchResponse.tags this.tags = searchResponse.tags
} }
} }
val mainId = searchResponse.id ?: response.getId() val mainId = response.getId()
postSuccessful( postSuccessful(
loadResponse = response, loadResponse = response,
mainId = mainId, mainId = mainId,
apiRepository = repo, updateEpisodes = false, updateFillers = false) apiRepository = repo,
updateEpisodes = false,
updateFillers = false)
} }
fun load( fun load(

View file

@ -6,7 +6,8 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.databinding.ResultSelectionBinding import com.lagradost.cloudstream3.databinding.ResultSelectionBinding
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
typealias SelectData = Pair<UiText?, Any> typealias SelectData = Pair<UiText?, Any>
@ -72,8 +73,7 @@ class SelectAdaptor(val callback: (Any) -> Unit) : RecyclerView.Adapter<Recycler
fun bind( fun bind(
data: SelectData, isSelected: Boolean, callback: (Any) -> Unit data: SelectData, isSelected: Boolean, callback: (Any) -> Unit
) { ) {
val isTrueTv = isTrueTvSettings() if (isLayout(TV)) {
if (isTrueTv) {
item.isFocusable = true item.isFocusable = true
item.isFocusableInTouchMode = true item.isFocusableInTouchMode = true
} }

View file

@ -54,8 +54,9 @@ import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.updateChips
import com.lagradost.cloudstream3.ui.home.ParentItemAdapter import com.lagradost.cloudstream3.ui.home.ParentItemAdapter
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppUtils.ownHide import com.lagradost.cloudstream3.utils.AppUtils.ownHide
import com.lagradost.cloudstream3.utils.AppUtils.ownShow import com.lagradost.cloudstream3.utils.AppUtils.ownShow
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
@ -107,13 +108,16 @@ class SearchFragment : Fragment() {
) )
bottomSheetDialog?.ownShow() bottomSheetDialog?.ownShow()
val layout = if (isTvSettings()) R.layout.fragment_search_tv else R.layout.fragment_search
val root = inflater.inflate(layout, container, false) binding = try {
// TODO TRYCATCH val layout = if (isLayout(TV or EMULATOR)) R.layout.fragment_search_tv else R.layout.fragment_search
binding = FragmentSearchBinding.bind(root) val root = inflater.inflate(layout, container, false)
FragmentSearchBinding.bind(root)
} catch (t : Throwable) {
FragmentSearchBinding.inflate(inflater)
}
return root return binding?.root
} }
private fun fixGrid() { private fun fixGrid() {
@ -369,7 +373,7 @@ class SearchFragment : Fragment() {
selectedSearchTypes = DataStoreHelper.searchPreferenceTags.toMutableList() selectedSearchTypes = DataStoreHelper.searchPreferenceTags.toMutableList()
if (isTrueTvSettings()) { if (isLayout(TV)) {
binding?.searchFilter?.isFocusable = true binding?.searchFilter?.isFocusable = true
binding?.searchFilter?.isFocusableInTouchMode = true binding?.searchFilter?.isFocusableInTouchMode = true
} }

View file

@ -1,6 +1,5 @@
package com.lagradost.cloudstream3.ui.search package com.lagradost.cloudstream3.ui.search
import android.app.Activity
import android.widget.Toast import android.widget.Toast
import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.activity
import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.showToast
@ -10,7 +9,8 @@ import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_PLAY_FILE
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
import com.lagradost.cloudstream3.ui.result.START_ACTION_LOAD_EP import com.lagradost.cloudstream3.ui.result.START_ACTION_LOAD_EP
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
@ -56,7 +56,7 @@ object SearchHelper {
} }
} }
SEARCH_ACTION_SHOW_METADATA -> { SEARCH_ACTION_SHOW_METADATA -> {
if(!isTvSettings()) { // we only want this on phone as UI is not done yet on tv if(isLayout(PHONE)) { // we only want this on phone as UI is not done yet on tv
(activity as? MainActivity?)?.apply { (activity as? MainActivity?)?.apply {
loadPopup(callback.card) loadPopup(callback.card)
} ?: kotlin.run { } ?: kotlin.run {

View file

@ -17,7 +17,8 @@ import com.lagradost.cloudstream3.SearchQuality
import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.isMovieType import com.lagradost.cloudstream3.isMovieType
import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
@ -164,7 +165,7 @@ object SearchResultBuilder {
bg.isFocusable = false bg.isFocusable = false
bg.isFocusableInTouchMode = false bg.isFocusableInTouchMode = false
if(!isTrueTvSettings()) { if(!isLayout(TV)) {
bg.setOnClickListener { bg.setOnClickListener {
click(it) click(it)
} }
@ -207,7 +208,7 @@ object SearchResultBuilder {
*/ */
if (isTrueTvSettings()) { if (isLayout(TV)) {
// bg.isFocusable = true // bg.isFocusable = true
// bg.isFocusableInTouchMode = true // bg.isFocusableInTouchMode = true
// bg.touchscreenBlocksFocus = false // bg.touchscreenBlocksFocus = false

View file

@ -0,0 +1,56 @@
package com.lagradost.cloudstream3.ui.settings
import android.app.UiModeManager
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.R
object Globals {
var beneneCount = 0
const val PHONE : Int = 0b001
const val TV : Int = 0b010
const val EMULATOR : Int = 0b100
private const val INVALID = -1
private var layoutId = INVALID
private fun Context.getLayoutInt(): Int {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
return settingsManager.getInt(this.getString(R.string.app_layout_key), -1)
}
private fun Context.isAutoTv(): Boolean {
val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager?
// AFT = Fire TV
val model = Build.MODEL.lowercase()
return uiModeManager?.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION || Build.MODEL.contains(
"AFT"
) || model.contains("firestick") || model.contains("fire tv") || model.contains("chromecast")
}
private fun Context.layoutIntCorrected(): Int {
return when(getLayoutInt()) {
-1 -> if (isAutoTv()) TV else PHONE
0 -> PHONE
1 -> TV
2 -> EMULATOR
else -> PHONE
}
}
fun Context.updateTv() {
layoutId = layoutIntCorrected()
}
/** Returns true if the layout is any of the flags,
* so isLayout(TV or EMULATOR) is a valid statement for checking if the layout is in the emulator
* or tv. Auto will become the "TV" or the "PHONE" layout.
*
* Valid flags are: PHONE, TV, EMULATOR
* */
fun isLayout(flags: Int) : Boolean {
return (layoutId and flags) != 0
}
}

View file

@ -29,8 +29,10 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.simklAp
import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.AuthAPI
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI
import com.lagradost.cloudstream3.syncproviders.OAuth2API import com.lagradost.cloudstream3.syncproviders.OAuth2API
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
@ -76,7 +78,7 @@ class SettingsAccount : PreferenceFragmentCompat() {
showAccountSwitch(activity, api) showAccountSwitch(activity, api)
} }
if (isTvSettings()) { if (isLayout(TV or EMULATOR)) {
binding.accountSwitchAccount.requestFocus() binding.accountSwitchAccount.requestFocus()
} }
} }
@ -140,7 +142,7 @@ class SettingsAccount : PreferenceFragmentCompat() {
binding.loginUsernameInput to api.requiresUsername binding.loginUsernameInput to api.requiresUsername
) )
if (isTvSettings()) { if (isLayout(TV or EMULATOR)) {
visibilityMap.forEach { (input, isVisible) -> visibilityMap.forEach { (input, isVisible) ->
input.isVisible = isVisible input.isVisible = isVisible

View file

@ -1,9 +1,5 @@
package com.lagradost.cloudstream3.ui.settings package com.lagradost.cloudstream3.ui.settings
import android.app.UiModeManager
import android.content.Context
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -16,18 +12,19 @@ import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.MainSettingsBinding import com.lagradost.cloudstream3.databinding.MainSettingsBinding
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
import com.lagradost.cloudstream3.ui.home.HomeFragment import com.lagradost.cloudstream3.ui.home.HomeFragment
import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.UIHelper.toPx
@ -35,10 +32,6 @@ import java.io.File
class SettingsFragment : Fragment() { class SettingsFragment : Fragment() {
companion object { companion object {
var beneneCount = 0
private var isTv: Boolean = false
private var isTrueTv: Boolean = false
fun PreferenceFragmentCompat?.getPref(id: Int): Preference? { fun PreferenceFragmentCompat?.getPref(id: Int): Preference? {
if (this == null) return null if (this == null) return null
@ -55,12 +48,12 @@ class SettingsFragment : Fragment() {
* On TV you cannot properly scroll to the bottom of settings, this fixes that. * On TV you cannot properly scroll to the bottom of settings, this fixes that.
* */ * */
fun PreferenceFragmentCompat.setPaddingBottom() { fun PreferenceFragmentCompat.setPaddingBottom() {
if (isTvSettings()) { if (isLayout(TV or EMULATOR)) {
listView?.setPadding(0, 0, 0, 100.toPx) listView?.setPadding(0, 0, 0, 100.toPx)
} }
} }
fun PreferenceFragmentCompat.setToolBarScrollFlags() { fun PreferenceFragmentCompat.setToolBarScrollFlags() {
if (isTvSettings()) { if (isLayout(TV or EMULATOR)) {
val settingsAppbar = view?.findViewById<MaterialToolbar>(R.id.settings_toolbar) val settingsAppbar = view?.findViewById<MaterialToolbar>(R.id.settings_toolbar)
settingsAppbar?.updateLayoutParams<AppBarLayout.LayoutParams> { settingsAppbar?.updateLayoutParams<AppBarLayout.LayoutParams> {
@ -69,7 +62,7 @@ class SettingsFragment : Fragment() {
} }
} }
fun Fragment?.setToolBarScrollFlags() { fun Fragment?.setToolBarScrollFlags() {
if (isTvSettings()) { if (isLayout(TV or EMULATOR)) {
val settingsAppbar = this?.view?.findViewById<MaterialToolbar>(R.id.settings_toolbar) val settingsAppbar = this?.view?.findViewById<MaterialToolbar>(R.id.settings_toolbar)
settingsAppbar?.updateLayoutParams<AppBarLayout.LayoutParams> { settingsAppbar?.updateLayoutParams<AppBarLayout.LayoutParams> {
@ -88,7 +81,7 @@ class SettingsFragment : Fragment() {
activity?.onBackPressedDispatcher?.onBackPressed() activity?.onBackPressedDispatcher?.onBackPressed()
} }
} }
fixPaddingStatusbar(settingsToolbar) UIHelper.fixPaddingStatusbar(settingsToolbar)
} }
fun Fragment?.setUpToolbar(@StringRes title: Int) { fun Fragment?.setUpToolbar(@StringRes title: Int) {
@ -103,7 +96,7 @@ class SettingsFragment : Fragment() {
activity?.onBackPressedDispatcher?.onBackPressed() activity?.onBackPressedDispatcher?.onBackPressed()
} }
} }
fixPaddingStatusbar(settingsToolbar) UIHelper.fixPaddingStatusbar(settingsToolbar)
} }
fun getFolderSize(dir: File): Long { fun getFolderSize(dir: File): Long {
@ -119,60 +112,7 @@ class SettingsFragment : Fragment() {
return size return size
} }
private fun Context.getLayoutInt(): Int {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
return settingsManager.getInt(this.getString(R.string.app_layout_key), -1)
}
private fun Context.isTvSettings(): Boolean {
var value = getLayoutInt()
if (value == -1) {
value = if (isAutoTv()) 1 else 0
}
return value == 1 || value == 2
}
private fun Context.isTrueTvSettings(): Boolean {
var value = getLayoutInt()
if (value == -1) {
value = if (isAutoTv()) 1 else 0
}
return value == 1
}
fun Context.updateTv() {
isTrueTv = isTrueTvSettings()
isTv = isTvSettings()
}
fun isTrueTvSettings(): Boolean {
return isTrueTv
}
fun isTvSettings(): Boolean {
return isTv
}
fun Context.isEmulatorSettings(): Boolean {
return getLayoutInt() == 2
}
// phone exclusive
fun isTruePhone(): Boolean {
return !isTrueTvSettings() && !isTvSettings() && context?.isEmulatorSettings() != true
}
private fun Context.isAutoTv(): Boolean {
val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager?
// AFT = Fire TV
val model = Build.MODEL.lowercase()
return uiModeManager?.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION || Build.MODEL.contains(
"AFT"
) || model.contains("firestick") || model.contains("fire tv") || model.contains("chromecast")
}
} }
override fun onDestroyView() { override fun onDestroyView() {
binding = null binding = null
super.onDestroyView() super.onDestroyView()
@ -197,8 +137,6 @@ class SettingsFragment : Fragment() {
// used to debug leaks showToast(activity,"${VideoDownloadManager.downloadStatusEvent.size} : ${VideoDownloadManager.downloadProgressEvent.size}") // used to debug leaks showToast(activity,"${VideoDownloadManager.downloadStatusEvent.size} : ${VideoDownloadManager.downloadProgressEvent.size}")
val isTrueTv = isTrueTvSettings()
for (syncApi in accountManagers) { for (syncApi in accountManagers) {
val login = syncApi.loginInfo() val login = syncApi.loginInfo()
val pic = login?.profilePicture ?: continue val pic = login?.profilePicture ?: continue
@ -226,7 +164,7 @@ class SettingsFragment : Fragment() {
setOnClickListener { setOnClickListener {
navigate(navigationId) navigate(navigationId)
} }
if (isTrueTv) { if (isLayout(TV)) {
isFocusable = true isFocusable = true
isFocusableInTouchMode = true isFocusableInTouchMode = true
} }
@ -234,7 +172,7 @@ class SettingsFragment : Fragment() {
} }
// Default focus on TV // Default focus on TV
if (isTrueTv) { if (isLayout(TV)) {
settingsGeneral.requestFocus() settingsGeneral.requestFocus()
} }
} }

View file

@ -27,6 +27,7 @@ import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.network.initClient import com.lagradost.cloudstream3.network.initClient
import com.lagradost.cloudstream3.ui.EasterEggMonke import com.lagradost.cloudstream3.ui.EasterEggMonke
import com.lagradost.cloudstream3.ui.settings.Globals.beneneCount
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
@ -378,30 +379,30 @@ class SettingsGeneral : PreferenceFragmentCompat() {
} }
try { try {
SettingsFragment.beneneCount = beneneCount =
settingsManager.getInt(getString(R.string.benene_count), 0) settingsManager.getInt(getString(R.string.benene_count), 0)
getPref(R.string.benene_count)?.let { pref -> getPref(R.string.benene_count)?.let { pref ->
pref.summary = pref.summary =
if (SettingsFragment.beneneCount <= 0) getString(R.string.benene_count_text_none) else getString( if (beneneCount <= 0) getString(R.string.benene_count_text_none) else getString(
R.string.benene_count_text R.string.benene_count_text
).format( ).format(
SettingsFragment.beneneCount beneneCount
) )
pref.setOnPreferenceClickListener { pref.setOnPreferenceClickListener {
try { try {
SettingsFragment.beneneCount++ beneneCount++
if (SettingsFragment.beneneCount%20 == 0) { if (beneneCount%20 == 0) {
val intent = Intent(context, EasterEggMonke::class.java) val intent = Intent(context, EasterEggMonke::class.java)
startActivity(intent) startActivity(intent)
} }
settingsManager.edit().putInt( settingsManager.edit().putInt(
getString(R.string.benene_count), getString(R.string.benene_count),
SettingsFragment.beneneCount beneneCount
) )
.apply() .apply()
it.summary = it.summary =
getString(R.string.benene_count_text).format(SettingsFragment.beneneCount) getString(R.string.benene_count_text).format(beneneCount)
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
} }

View file

@ -9,11 +9,11 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchQuality import com.lagradost.cloudstream3.SearchQuality
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
import com.lagradost.cloudstream3.ui.settings.Globals.updateTv
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog

View file

@ -29,7 +29,8 @@ import com.lagradost.cloudstream3.plugins.RepositoryManager
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.result.setText import com.lagradost.cloudstream3.ui.result.setText
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.utils.AppUtils.downloadAllPluginsDialog import com.lagradost.cloudstream3.utils.AppUtils.downloadAllPluginsDialog
@ -97,7 +98,7 @@ class ExtensionsFragment : Fragment() {
nextLeft = R.id.nav_rail_view nextLeft = R.id.nav_rail_view
) )
if (!isTrueTvSettings()) if (!isLayout(TV))
binding?.addRepoButton?.let { button -> binding?.addRepoButton?.let { button ->
button.post { button.post {
setPadding( setPadding(
@ -286,7 +287,7 @@ class ExtensionsFragment : Fragment() {
} }
} }
val isTv = isTrueTvSettings() val isTv = isLayout(TV)
binding?.apply { binding?.apply {
addRepoButton.isGone = isTv addRepoButton.isGone = isTv
addRepoButtonImageviewHolder.isVisible = isTv addRepoButtonImageviewHolder.isVisible = isTv

View file

@ -17,7 +17,8 @@ import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.plugins.VotingApi.getVotes import com.lagradost.cloudstream3.plugins.VotingApi.getVotes
import com.lagradost.cloudstream3.ui.result.setText import com.lagradost.cloudstream3.ui.result.setText
import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
@ -44,7 +45,7 @@ class PluginAdapter(
private val plugins: MutableList<PluginViewData> = mutableListOf() private val plugins: MutableList<PluginViewData> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layout = if(isTrueTvSettings()) R.layout.repository_item_tv else R.layout.repository_item val layout = if(isLayout(TV)) R.layout.repository_item_tv else R.layout.repository_item
val inflated = LayoutInflater.from(parent.context).inflate(layout, parent, false) val inflated = LayoutInflater.from(parent.context).inflate(layout, parent, false)
return PluginViewHolder( return PluginViewHolder(

View file

@ -17,7 +17,9 @@ import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.ui.settings.appLanguages import com.lagradost.cloudstream3.ui.settings.appLanguages
@ -155,7 +157,7 @@ class PluginsFragment : Fragment() {
pluginViewModel.handlePluginAction(activity, url, it, isLocal) pluginViewModel.handlePluginAction(activity, url, it, isLocal)
} }
if (isTvSettings()) { if (isLayout(TV or EMULATOR)) {
// Scrolling down does not reveal the whole RecyclerView on TV, add to bypass that. // Scrolling down does not reveal the whole RecyclerView on TV, add to bypass that.
binding?.pluginRecyclerView?.setPadding(0, 0, 0, 200.toPx) binding?.pluginRecyclerView?.setPadding(0, 0, 0, 200.toPx)
} }

View file

@ -10,7 +10,8 @@ import com.lagradost.cloudstream3.databinding.RepositoryItemBinding
import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper
class RepoAdapter( class RepoAdapter(
@ -23,7 +24,7 @@ class RepoAdapter(
private val repositories: MutableList<RepositoryData> = mutableListOf() private val repositories: MutableList<RepositoryData> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layout = if (isTrueTvSettings()) RepositoryItemTvBinding.inflate( val layout = if (isLayout(TV)) RepositoryItemTvBinding.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
parent, parent,
false false

View file

@ -11,7 +11,8 @@ import com.lagradost.cloudstream3.databinding.FragmentTestingBinding
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
@ -62,7 +63,7 @@ class TestFragment : Fragment() {
} }
} }
if (isTrueTvSettings()) { if (isLayout(TV)) {
providerTest.playPauseButton?.isFocusableInTouchMode = true providerTest.playPauseButton?.isFocusableInTouchMode = true
providerTest.playPauseButton?.requestFocus() providerTest.playPauseButton?.requestFocus()
} }
@ -75,7 +76,7 @@ class TestFragment : Fragment() {
fun focusRecyclerView() { fun focusRecyclerView() {
// Hack to make it possible to focus the recyclerview. // Hack to make it possible to focus the recyclerview.
if (isTrueTvSettings()) { if (isLayout(TV)) {
providerTestRecyclerView.requestFocus() providerTestRecyclerView.requestFocus()
providerTestAppbar.setExpanded(false, true) providerTestAppbar.setExpanded(false, true)
} }

View file

@ -13,8 +13,8 @@ import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.fasterxml.jackson.annotation.JsonProperty
import androidx.media3.common.text.Cue import androidx.media3.common.text.Cue
import com.fasterxml.jackson.annotation.JsonProperty
import com.google.android.gms.cast.TextTrackStyle import com.google.android.gms.cast.TextTrackStyle
import com.google.android.gms.cast.TextTrackStyle.* import com.google.android.gms.cast.TextTrackStyle.*
import com.jaredrummler.android.colorpicker.ColorPickerDialog import com.jaredrummler.android.colorpicker.ColorPickerDialog
@ -24,7 +24,9 @@ import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.ChromecastSubtitleSettingsBinding import com.lagradost.cloudstream3.databinding.ChromecastSubtitleSettingsBinding
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
@ -173,7 +175,7 @@ class ChromecastSubtitlesFragment : Fragment() {
state = getCurrentSavedStyle() state = getCurrentSavedStyle()
context?.updateState() context?.updateState()
val isTvSettings = isTvSettings() val isTvSettings = isLayout(TV or EMULATOR)
fun View.setFocusableInTv() { fun View.setFocusableInTv() {
this.isFocusableInTouchMode = isTvSettings this.isFocusableInTouchMode = isTvSettings

View file

@ -28,7 +28,9 @@ import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.SubtitleSettingsBinding import com.lagradost.cloudstream3.databinding.SubtitleSettingsBinding
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
@ -252,7 +254,7 @@ class SubtitlesFragment : Fragment() {
state = getCurrentSavedStyle() state = getCurrentSavedStyle()
context?.updateState() context?.updateState()
val isTvTrueSettings = isTrueTvSettings() val isTvTrueSettings = isLayout(TV)
fun View.setFocusableInTv() { fun View.setFocusableInTv() {
this.isFocusableInTouchMode = isTvTrueSettings this.isFocusableInTouchMode = isTvTrueSettings

View file

@ -61,8 +61,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStri
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
import com.lagradost.cloudstream3.ui.WebviewFragment import com.lagradost.cloudstream3.ui.WebviewFragment
import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.Globals
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel.Companion.downloadAll import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel.Companion.downloadAll
import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
@ -78,7 +77,6 @@ import okhttp3.Cache
import java.io.* import java.io.*
import java.net.URL import java.net.URL
import java.net.URLDecoder import java.net.URLDecoder
import kotlin.system.measureTimeMillis
object AppUtils { object AppUtils {
fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) { fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) {
@ -583,7 +581,7 @@ object AppUtils {
//private val viewModel: ResultViewModel by activityViewModels() //private val viewModel: ResultViewModel by activityViewModels()
private fun getResultsId(): Int { private fun getResultsId(): Int {
return if (isTvSettings()) { return if (Globals.isLayout(Globals.TV or Globals.EMULATOR)) {
R.id.global_to_navigation_results_tv R.id.global_to_navigation_results_tv
} else { } else {
R.id.global_to_navigation_results_phone R.id.global_to_navigation_results_phone
@ -707,7 +705,7 @@ object AppUtils {
* Sets the focus to the negative button when in TV and Emulator layout. * Sets the focus to the negative button when in TV and Emulator layout.
**/ **/
fun AlertDialog.setDefaultFocus(buttonFocus: Int = DialogInterface.BUTTON_NEGATIVE) { fun AlertDialog.setDefaultFocus(buttonFocus: Int = DialogInterface.BUTTON_NEGATIVE) {
if (!isTvSettings()) return if (!Globals.isLayout(Globals.TV or Globals.EMULATOR)) return
this.getButton(buttonFocus).run { this.getButton(buttonFocus).run {
isFocusableInTouchMode = true isFocusableInTouchMode = true
requestFocus() requestFocus()

View file

@ -21,7 +21,9 @@ import com.lagradost.cloudstream3.databinding.BottomInputDialogBinding
import com.lagradost.cloudstream3.databinding.BottomSelectionDialogBinding import com.lagradost.cloudstream3.databinding.BottomSelectionDialogBinding
import com.lagradost.cloudstream3.databinding.BottomTextDialogBinding import com.lagradost.cloudstream3.databinding.BottomTextDialogBinding
import com.lagradost.cloudstream3.databinding.OptionsPopupTvBinding import com.lagradost.cloudstream3.databinding.OptionsPopupTvBinding
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
@ -54,7 +56,7 @@ object SingleSelectionHelper {
) { ) {
if (this == null) return if (this == null) return
if (isTvSettings()) { if (isLayout(TV or EMULATOR)) {
val binding = OptionsPopupTvBinding.inflate(layoutInflater) val binding = OptionsPopupTvBinding.inflate(layoutInflater)
val dialog = AlertDialog.Builder(this, R.style.AlertDialogCustom) val dialog = AlertDialog.Builder(this, R.style.AlertDialogCustom)
.setView(binding.root) .setView(binding.root)

View file

@ -69,8 +69,9 @@ import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.result.UiImage import com.lagradost.cloudstream3.ui.result.UiImage
import com.lagradost.cloudstream3.ui.result.UiText import com.lagradost.cloudstream3.ui.result.UiText
import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings import com.lagradost.cloudstream3.ui.settings.Globals
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import jp.wasabeef.glide.transformations.BlurTransformation import jp.wasabeef.glide.transformations.BlurTransformation
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -468,7 +469,7 @@ object UIHelper {
} }
fun Context.getStatusBarHeight(): Int { fun Context.getStatusBarHeight(): Int {
if (isTvSettings()) { if (isLayout(Globals.TV or EMULATOR)) {
return 0 return 0
} }
@ -570,7 +571,7 @@ object UIHelper {
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
//} //}
changeStatusBarState(isEmulatorSettings()) changeStatusBarState(isLayout(EMULATOR))
} }
fun Context.shouldShowPIPMode(isInPlayer: Boolean): Boolean { fun Context.shouldShowPIPMode(isInPlayer: Boolean): Boolean {

View file

@ -41,22 +41,34 @@
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
android:orientation="vertical"> android:orientation="vertical">
<FrameLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<TextView <TextView
android:id="@+id/resultview_preview_title" android:id="@+id/resultview_preview_title"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:textColor="?attr/textColor" android:textColor="?attr/textColor"
android:textSize="16sp" android:textSize="16sp"
android:layout_gravity="start|center_vertical"
android:textStyle="bold" android:textStyle="bold"
android:layout_marginEnd="25dp" tools:text="The Perfect Run" />
tools:text="The Perfect Run">
</TextView> <ImageView
android:id="@+id/resultview_preview_subscribe"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_gravity="end|center_vertical"
android:layout_margin="5dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/subscribe_tooltip"
android:elevation="10dp"
android:nextFocusDown="@id/resultview_preview_bookmark"
android:src="@drawable/baseline_notifications_none_24"
app:tint="?attr/textColor" />
<ImageView <ImageView
android:id="@+id/resultview_preview_favorite" android:id="@+id/resultview_preview_favorite"
@ -66,11 +78,13 @@
android:layout_margin="5dp" android:layout_margin="5dp"
android:background="?android:attr/selectableItemBackgroundBorderless" android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/favorite"
android:elevation="10dp" android:elevation="10dp"
android:nextFocusDown="@id/resultview_preview_bookmark" android:nextFocusDown="@id/resultview_preview_bookmark"
android:src="@drawable/ic_baseline_favorite_border_24" android:src="@drawable/ic_baseline_favorite_border_24"
app:tint="?attr/textColor" /> app:tint="?attr/textColor" />
</FrameLayout>
</LinearLayout>
<com.lagradost.cloudstream3.widget.FlowLayout <com.lagradost.cloudstream3.widget.FlowLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -134,38 +148,38 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:padding="7dp"
android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="7dp">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/resultview_preview_bookmark" android:id="@+id/resultview_preview_bookmark"
style="@style/BlackButton"
android:layout_width="50dp"
android:layout_weight="1" android:layout_weight="1"
android:nextFocusUp="@id/resultview_preview_favorite"
android:nextFocusRight="@id/resultview_preview_more_info" android:nextFocusRight="@id/resultview_preview_more_info"
tools:visibility="visible" android:nextFocusUp="@id/resultview_preview_favorite"
app:icon="@drawable/ic_baseline_bookmark_24" app:icon="@drawable/ic_baseline_bookmark_24"
tools:text="Bookmark" tools:text="Bookmark"
style="@style/BlackButton"
android:layout_width="50dp" /> tools:visibility="visible" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/resultview_preview_more_info" android:id="@+id/resultview_preview_more_info"
style="@style/WhiteButton"
android:layout_width="50dp"
android:layout_weight="1" android:layout_weight="1"
android:nextFocusUp="@id/resultview_preview_favorite"
android:nextFocusLeft="@id/resultview_preview_bookmark" android:nextFocusLeft="@id/resultview_preview_bookmark"
tools:visibility="visible" android:nextFocusUp="@id/resultview_preview_favorite"
app:icon="@drawable/ic_baseline_open_in_new_24"
android:text="@string/home_more_info" android:text="@string/home_more_info"
style="@style/WhiteButton" app:icon="@drawable/ic_baseline_open_in_new_24"
android:layout_width="50dp" /> tools:visibility="visible" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>