+ 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
^ https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading*/
versionCode = 63
versionName = "4.3.1"
versionName = "4.3.2"
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "")
@ -163,10 +163,10 @@ dependencies {
// Android Core & Lifecycle
implementation("androidx.core:core-ktx:1.12.0")
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-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
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.suspendSafeApiCall
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.Coroutines.runOnMainThread
import com.lagradost.cloudstream3.utils.DataStore.getKey
@ -31,7 +33,6 @@ import org.acra.sender.ReportSenderFactory
import java.io.File
import java.io.FileNotFoundException
import java.io.PrintStream
import java.lang.Exception
import java.lang.ref.WeakReference
import kotlin.concurrent.thread
import kotlin.system.exitProcess
@ -211,7 +212,7 @@ class AcraApplication : Application() {
fun openBrowser(url: String, activity: FragmentActivity?) {
openBrowser(
url,
isTvSettings(),
isLayout(TV or EMULATOR),
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.result.ResultFragment
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.DataStoreHelper
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.providers.SimklApi
import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.ui.result.ResultViewModel2
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.toJson
import com.lagradost.cloudstream3.utils.Coroutines.mainWork
@ -119,7 +120,8 @@ object APIHolder {
}
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.loadSinglePlugin
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.accountManagers
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.search.SearchFragment
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTruePhone
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
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.Globals.updateTv
import com.lagradost.cloudstream3.ui.settings.SettingsGeneral
import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY
import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
@ -290,7 +291,8 @@ var app = Requests(responseParser = object : ResponseParser {
defaultHeaders = mapOf("user-agent" to USER_AGENT)
}
class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAuthenticator.BiometricAuthCallback {
class MainActivity : AppCompatActivity(), ColorPickerDialogListener,
BiometricAuthenticator.BiometricAuthCallback {
companion object {
const val TAG = "MAINACT"
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
val bookmarksUpdatedEvent = Event<Boolean>()
/**
* Used by DataStoreHelper to fully reload home when switching accounts
*/
val reloadHomeEvent = Event<Boolean>()
/**
* Used by DataStoreHelper to fully reload library when switching accounts
*/
@ -467,7 +471,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
}
var lastPopup: SearchResponse? = null
fun loadPopup(result: SearchResponse, load : Boolean = true) {
fun loadPopup(result: SearchResponse, load: Boolean = true) {
lastPopup = result
val syncName = syncViewModel.syncName(result.apiName)
@ -488,8 +492,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
.contains(DubStatus.Dubbed)
) DubStatus.Dubbed else DubStatus.Subbed, null
)
}else {
viewModel.loadSmall(this,result)
} else {
viewModel.loadSmall(this, result)
}
}
@ -554,7 +558,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
binding?.navHostFragment?.apply {
val params = layoutParams as ConstraintLayout.LayoutParams
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()) {
params.setMargins(
@ -581,7 +585,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
}
Configuration.ORIENTATION_PORTRAIT -> {
isTvSettings()
isLayout(TV or EMULATOR)
}
else -> {
@ -787,9 +791,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
}
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*/
var isLocalList : Boolean = false
var isLocalList: Boolean = false
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
viewModel =
ViewModelProvider(this)[ResultViewModel2::class.java]
@ -1105,8 +1110,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
}
}
private fun centerView(view : View?) {
if(view == null) return
private fun centerView(view: View?) {
if (view == null) return
try {
Log.v(TAG, "centerView: $view")
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
binding = try {
if (isTvSettings()) {
if (isLayout(TV or EMULATOR)) {
val newLocalBinding = ActivityMainTvBinding.inflate(layoutInflater, null, false)
setContentView(newLocalBinding.root)
if (isTrueTvSettings() && ANIMATED_OUTLINE) {
if (isLayout(TV) && ANIMATED_OUTLINE) {
TvFocus.focusOutline = WeakReference(newLocalBinding.focusOutline)
newLocalBinding.root.viewTreeObserver.addOnScrollChangedListener {
TvFocus.updateFocusView(TvFocus.lastFocus.get(), same = true)
@ -1188,7 +1193,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
newLocalBinding.focusOutline.isVisible = false
}
if(isTrueTvSettings()) {
if (isLayout(TV)) {
// Put here any button you don't want focusing it to center the view
val exceptionButtons = listOf(
R.id.home_preview_play_btt,
@ -1205,7 +1210,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
R.id.result_search_Button,
R.id.result_episodes_show_button,
)
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
if (exceptionButtons.contains(newFocus?.id)) return@addOnGlobalFocusChangeListener
centerView(newFocus)
@ -1223,18 +1228,21 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
null
}
changeStatusBarState(isEmulatorSettings())
changeStatusBarState(isLayout(EMULATOR))
/** Biometric stuff for users without accounts **/
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)) {
startBiometricAuthentication(this, R.string.biometric_authentication_title, false)
BiometricAuthenticator.promptInfo?.let {
BiometricAuthenticator.biometricPrompt?.authenticate(it)
BiometricAuthenticator.promptInfo?.let { promt ->
BiometricAuthenticator.biometricPrompt?.authenticate(promt)
}
// 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
bottomPreviewBinding?.apply {
when (status) {
@ -1351,7 +1359,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
}
}
fun setWatchStatus(state : WatchType?) {
fun setWatchStatus(state: WatchType?) {
if (!isLocalList || state == null) return
bottomPreviewBinding?.resultviewPreviewBookmark?.apply {
@ -1360,13 +1368,42 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
}
}
observe(viewModel.watchStatus) { state ->
setWatchStatus(state)
}
observe(syncViewModel.userData) { status ->
setUserData(status)
fun setSubscribeStatus(state: Boolean?) {
bottomPreviewBinding?.resultviewPreviewSubscribe?.apply {
if (state != null) {
val drawable = if (state) {
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 ->
if (resource == null) {
hidePreviewPopupDialog()
@ -1408,6 +1445,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
setUserData(syncViewModel.userData.value)
setWatchStatus(viewModel.watchStatus.value)
setSubscribeStatus(viewModel.subscribeStatus.value)
resultviewPreviewBookmark.setOnClickListener {
//viewModel.updateWatchStatus(WatchType.PLANTOWATCH)
@ -1426,7 +1464,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
)
}
} 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(
SyncWatchType.values().map { getString(it.stringRes) }.toList(),
@ -1453,7 +1493,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
resultviewPreviewFavorite.setImageResource(drawable)
}
resultviewPreviewFavorite.setOnClickListener{
resultviewPreviewFavorite.setOnClickListener {
viewModel.toggleFavoriteStatus(this@MainActivity) { newStatus: Boolean? ->
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 ->
view.context?.let { ctx ->
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)) {
attachBackPressedCallback()
} else detachBackPressedCallback()
@ -1580,7 +1620,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
itemRippleColor = rippleColor
itemActiveIndicatorColor = rippleColor
setupWithNavController(navController)
if (isTvSettings()) {
if (isLayout(TV or EMULATOR)) {
background?.alpha = 200
} else {
background?.alpha = 255

View File

@ -1,5 +1,6 @@
package com.lagradost.cloudstream3.services
import android.annotation.SuppressLint
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
@ -12,7 +13,7 @@ import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
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.ui.result.txt
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 {
try {
// println("Update subscriptions!")
context.createNotificationChannel(
SUBSCRIPTION_CHANNEL_ID,
SUBSCRIPTION_CHANNEL_NAME,
SUBSCRIPTION_CHANNEL_DESCRIPTION
)
setForeground(
ForegroundInfo(
SUBSCRIPTION_NOTIFICATION_ID,
progressNotificationBuilder.build()
context.createNotificationChannel(
SUBSCRIPTION_CHANNEL_ID,
SUBSCRIPTION_CHANNEL_NAME,
SUBSCRIPTION_CHANNEL_DESCRIPTION
)
)
val subscriptions = getAllSubscriptions()
setForeground(
ForegroundInfo(
SUBSCRIPTION_NOTIFICATION_ID,
progressNotificationBuilder.build()
)
)
if (subscriptions.isEmpty()) {
WorkManager.getInstance(context).cancelWorkById(this.id)
val subscriptions = getAllSubscriptions()
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()
}
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.library.ListSorting
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.DataStoreHelper.getAllFavorites
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions
@ -71,9 +72,9 @@ class LocalList : SyncAPI {
}?.distinctBy { it.first } ?: return null
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
it.stringRes to emptyList<SyncAPI.LibraryItem>()
} + mapOf(

View File

@ -12,7 +12,9 @@ import com.lagradost.cloudstream3.databinding.AccountListItemBinding
import com.lagradost.cloudstream3.databinding.AccountListItemEditBinding
import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountEditDialog
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.UIHelper.setImage
@ -38,7 +40,7 @@ class AccountAdapter(
is AccountListItemBinding -> binding.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
@ -80,7 +82,7 @@ class AccountAdapter(
is AccountListItemEditBinding -> binding.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

View File

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

View File

@ -5,6 +5,7 @@ import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.text.format.Formatter.formatShortFileSize
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -13,17 +14,25 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.CommonActivity.showToast
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.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.observe
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.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.Coroutines.main
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.VideoDownloadHelper
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
@ -200,7 +200,7 @@ class DownloadFragment : Fragment() {
}
// Should be visible in emulator layout
binding?.downloadStreamButton?.isGone = isTrueTvSettings()
binding?.downloadStreamButton?.isGone = isLayout(TV)
binding?.downloadStreamButton?.setOnClickListener {
val dialog =
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.graphics.drawable.Drawable
import android.os.Looper
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.animation.AnimationUtils
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.MainThread
import androidx.core.content.ContextCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
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_DOWNLOAD
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 */
override fun setStatus(status: DownloadStatusTell?) {
currentStatus = status
//progressBar.isVisible =
// status != null && status != DownloadStatusTell.Complete && status != DownloadStatusTell.Error
//progressBarBackground.isVisible = status != null && status != DownloadStatusTell.Complete
progressBarBackground.post {
val isPreActive = isZeroBytes && status == DownloadStatusTell.IsDownloading
if (animateWaiting && (status == DownloadStatusTell.IsPending || isPreActive)) {
val animation = AnimationUtils.loadAnimation(context, waitingAnimation)
progressBarBackground.startAnimation(animation)
} else {
progressBarBackground.clearAnimation()
// runs on the main thread, but also instant if it already is
if (Looper.myLooper() == Looper.getMainLooper()) {
try {
setStatusInternal(status)
} catch (t : Throwable) {
logError(t) // just in case setStatusInternal throws because thread
progressBarBackground.post {
setStatusInternal(status)
}
}
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()
} else {
progressBarBackground.post {
setStatusInternal(status)
}
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.search.*
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
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.utils.AppUtils.isRecyclerScrollable
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.AppUtils.ownHide
@ -311,7 +313,7 @@ class HomeFragment : Fragment() {
button?.isVisible = isValid
button?.isChecked = isValid && selectedTypes.any { types.contains(it) }
button?.isFocusable = true
if (isTrueTvSettings()) {
if (isLayout(TV)) {
button?.isFocusableInTouchMode = true
}
@ -435,7 +437,7 @@ class HomeFragment : Fragment() {
bottomSheetDialog?.ownShow()
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)
binding = try {
FragmentHomeBinding.bind(root)
@ -449,6 +451,11 @@ class HomeFragment : Fragment() {
}
override fun onDestroyView() {
homeMasterAdapter?.onSaveInstanceState(
instanceState,
binding?.homeMasterRecycler
)
bottomSheetDialog?.ownHide()
binding = null
super.onDestroyView()
@ -485,6 +492,10 @@ class HomeFragment : Fragment() {
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")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -505,15 +516,16 @@ class HomeFragment : Fragment() {
activity.loadSearchResult(listHomepageItems.random())
}
}
homeMasterRecycler.adapter =
HomeParentItemAdapterPreview(
mutableListOf(),
homeViewModel
)
homeMasterAdapter = HomeParentItemAdapterPreview(
mutableListOf(),
homeViewModel,
).apply {
onRestoreInstanceState(instanceState)
}
homeMasterRecycler.adapter = homeMasterAdapter
//fixPaddingStatusbar(homeLoadingStatusbar)
homeApiFab.isVisible = !isTvSettings()
homeApiFab.isVisible = isLayout(PHONE)
homeMasterRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
@ -521,7 +533,7 @@ class HomeFragment : Fragment() {
homeApiFab.shrink() // hide
homeRandom.shrink()
} else if (dy < -5) {
if (!isTvSettings()) {
if (isLayout(PHONE)) {
homeApiFab.extend() // show
homeRandom.extend()
}
@ -540,7 +552,7 @@ class HomeFragment : Fragment() {
settingsManager.getBoolean(
getString(R.string.random_button_key),
false
) && !isTvSettings()
) && isLayout(PHONE)
binding?.homeRandom?.visibility = View.GONE
}

View File

@ -1,5 +1,7 @@
package com.lagradost.cloudstream3.ui.home
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -7,16 +9,20 @@ import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.lagradost.cloudstream3.HomePageList
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R
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.setLinearListLayout
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
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.utils.AppUtils.isRecyclerScrollable
class LoadClickCallback(
@ -32,18 +38,90 @@ open class ParentItemAdapter(
private val clickCallback: (SearchClickCallback) -> Unit,
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
private val expandCallback: ((String) -> Unit)? = null,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
) : RecyclerView.Adapter<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 {
isTrueTvSettings() -> R.layout.homepage_parent_tv
parent.context.isEmulatorSettings() -> R.layout.homepage_parent_emulator
isLayout(TV) -> R.layout.homepage_parent_tv
isLayout(EMULATOR) -> R.layout.homepage_parent_emulator
else -> R.layout.homepage_parent
}
val root = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false)
val binding = HomepageParentBinding.bind(root)
val inflater = LayoutInflater.from(parent.context)
val binding = try {
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(
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 {
return items.size
}
@ -116,7 +186,6 @@ open class ParentItemAdapter(
}
override fun onChanged(_position: Int, count: Int, payload: Any?) {
val position = _position + delta
// 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)
}
class ParentViewHolder
constructor(
class ParentViewHolder(
val binding: HomepageParentBinding,
// val viewModel: HomeViewModel,
private val clickCallback: (SearchClickCallback) -> Unit,
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
private val expandCallback: ((String) -> Unit)? = null,
) :
RecyclerView.ViewHolder(binding.root) {
ViewHolder(binding.root) {
val title: TextView = binding.homeChildMoreInfo
private val recyclerView: RecyclerView = binding.homeChildRecyclerview
private val startFocus = R.id.nav_rail_view
@ -237,7 +306,7 @@ open class ParentItemAdapter(
})
//(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
if (!isTrueTvSettings()) {
if (isLayout(PHONE)) {
title.setOnClickListener {
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_SHOW_METADATA
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.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.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes
@ -48,7 +49,8 @@ import com.lagradost.cloudstream3.utils.UIHelper.populateChips
class HomeParentItemAdapterPreview(
items: MutableList<HomeViewModel.ExpandableHomepageList>,
private val viewModel: HomeViewModel,
) : ParentItemAdapter(items, clickCallback = {
) : ParentItemAdapter(items,
clickCallback = {
viewModel.click(it)
}, moreInfoClickCallback = {
viewModel.popup(it)
@ -78,13 +80,13 @@ class HomeParentItemAdapterPreview(
return when (viewType) {
VIEW_TYPE_HEADER -> {
val inflater = LayoutInflater.from(parent.context)
val binding = if (isTvSettings()) FragmentHomeHeadTvBinding.inflate(
val binding = if (isLayout(TV or EMULATOR)) FragmentHomeHeadTvBinding.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
val marginInDp = 50
@ -598,7 +600,7 @@ class HomeParentItemAdapterPreview(
if (
binding is FragmentHomeHeadBinding ||
binding is FragmentHomeHeadTvBinding &&
binding.root.context.isEmulatorSettings()
isLayout(EMULATOR)
) {
val title = (binding as? FragmentHomeHeadBinding)?.homeWatchParentItemTitle
?: (binding as? FragmentHomeHeadTvBinding)?.homeWatchParentItemTitle
@ -628,7 +630,7 @@ class HomeParentItemAdapterPreview(
if (
binding is FragmentHomeHeadBinding ||
binding is FragmentHomeHeadTvBinding &&
binding.root.context.isEmulatorSettings()
isLayout(EMULATOR)
) {
val title = (binding as? FragmentHomeHeadBinding)?.homeBookmarkParentItemTitle
?: (binding as? FragmentHomeHeadTvBinding)?.homeBookmarkParentItemTitle

View File

@ -10,7 +10,9 @@ import androidx.viewbinding.ViewBinding
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.databinding.HomeScrollViewBinding
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
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 {
val inflater = LayoutInflater.from(parent.context)
val binding = if (isTvSettings()) {
val binding = if (isLayout(TV or EMULATOR)) {
HomeScrollViewTvBinding.inflate(inflater, parent, false)
} else {
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.SearchClickCallback
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.loadResult
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
@ -132,7 +133,7 @@ class HomeViewModel : ViewModel() {
private fun loadResumeWatching() = viewModelScope.launchSafe {
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 {
// this WILL crash on non tvs, so keep this inside a try catch
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.search.SEARCH_ACTION_LOAD
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.utils.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
@ -101,7 +106,7 @@ class LibraryFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
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)
binding = try {
FragmentLibraryBinding.bind(root)
@ -220,7 +225,7 @@ class LibraryFragment : Fragment() {
settingsManager.getBoolean(
getString(R.string.random_button_key),
false
) && !SettingsFragment.isTvSettings()
) && isLayout(PHONE)
binding?.libraryRandom?.visibility = View.GONE
}

View File

@ -1,7 +1,6 @@
package com.lagradost.cloudstream3.ui.library
import android.os.Build
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.doOnAttach
@ -12,7 +11,9 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.LibraryViewpagerPageBinding
import com.lagradost.cloudstream3.syncproviders.SyncAPI
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
class ViewpagerAdapter(
@ -73,7 +74,7 @@ class ViewpagerAdapter(
val diff = scrollY - oldScrollY
//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)
.apply {
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.result.setText
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.utils.AppUtils.isUsingMobileData
import com.lagradost.cloudstream3.utils.DataStoreHelper
@ -1514,7 +1515,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
}
}
// cs3 is peak media center
setRemainingTimeCounter(durationMode || SettingsFragment.isTrueTvSettings())
setRemainingTimeCounter(durationMode || Globals.isLayout(Globals.TV))
playerBinding?.exoPosition?.doOnTextChanged { _, _, _, _ ->
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.QualityProfileDialog
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.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1
import com.lagradost.cloudstream3.utils.*
@ -1275,8 +1278,7 @@ class GeneratorPlayer : FullScreenPlayer() {
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
// 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 (isTv) R.layout.fragment_player_tv else R.layout.fragment_player
layout = if (isLayout(TV or EMULATOR)) R.layout.fragment_player_tv else R.layout.fragment_player
viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java]
sync = ViewModelProvider(this)[SyncViewModel::class.java]

View File

@ -9,6 +9,9 @@ import android.util.Log
import androidx.annotation.WorkerThread
import androidx.core.graphics.scale
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.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.ExtractorLink
@ -63,7 +66,7 @@ interface IPreviewGenerator {
companion object {
fun new(): IPreviewGenerator {
/** because TV has low ram + not show we disable this for now */
return if (SettingsFragment.isTrueTvSettings()) {
return if (isLayout(TV)) {
empty()
} else {
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.SearchHelper
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.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
@ -277,7 +278,7 @@ class QuickSearchFragment : Fragment() {
activity?.popCurrentPage()
}
if (isTrueTvSettings()) {
if (isLayout(TV)) {
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_LONG_CLICK
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
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.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.toPx
@ -172,15 +174,13 @@ class EpisodeAdapter(
@SuppressLint("SetTextI18n")
fun bind(card: ResultEpisode) {
localCard = card
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.episodeHolderLarge.layoutParams.width = setWidth
binding.episodeHolder.layoutParams.width = setWidth
val isTrueTv = isTrueTvSettings()
binding.apply {
downloadButton.isVisible = hasDownloadSupport
@ -249,7 +249,7 @@ class EpisodeAdapter(
var isExpanded = false
setOnClickListener {
if (isTrueTv) {
if (isLayout(TV)) {
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
} else {
isExpanded = !isExpanded
@ -260,7 +260,7 @@ class EpisodeAdapter(
}
}
if (!isTrueTv) {
if (isLayout(EMULATOR or PHONE)) {
episodePoster.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
}
@ -275,7 +275,7 @@ class EpisodeAdapter(
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
}
if (isTrueTv) {
if (isLayout(TV)) {
itemView.isFocusable = true
itemView.isFocusableInTouchMode = true
//itemView.touchscreenBlocksFocus = false
@ -300,11 +300,9 @@ class EpisodeAdapter(
) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n")
fun bind(card: ResultEpisode) {
val isTrueTv = isTrueTvSettings()
binding.episodeHolder.layoutParams.apply {
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 {
@ -361,7 +359,7 @@ class EpisodeAdapter(
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
}
if (isTrueTv) {
if (isLayout(TV)) {
itemView.isFocusable = true
itemView.isFocusableInTouchMode = true
//itemView.touchscreenBlocksFocus = false

View File

@ -5,7 +5,8 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
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) {
@ -83,7 +84,7 @@ class ImageAdapter(
this.nextFocusUpId = nextFocusUp
}
if (clickCallback != null) {
if (isTrueTvSettings()) {
if (isLayout(TV)) {
isClickable = true
isLongClickable = 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.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
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.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.AppUtils.isRtl
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
@ -598,7 +600,7 @@ class ResultFragmentTv : Fragment() {
}
observeNullable(viewModel.subscribeStatus) { isSubscribed ->
binding?.resultSubscribe?.isVisible = isSubscribed != null && requireContext().isEmulatorSettings()
binding?.resultSubscribe?.isVisible = isSubscribed != null && isLayout(EMULATOR)
binding?.resultSubscribeButton?.apply {
if (isSubscribed == null) return@observeNullable
@ -752,7 +754,7 @@ class ResultFragmentTv : Fragment() {
setRecommendations(recommendations, null)
}
if (isTrueTvSettings()) {
if (isLayout(TV)) {
observe(viewModel.episodeSynopsis) { description ->
view.context?.let { ctx ->
val builder: AlertDialog.Builder =
@ -878,7 +880,7 @@ class ResultFragmentTv : Fragment() {
resultDescription.apply {
setTextHtml(d.plotText)
setOnClickListener {
if (context.isEmulatorSettings()) {
if (isLayout(EMULATOR)) {
isExpanded = !isExpanded
maxLines = if (isExpanded) {
Integer.MAX_VALUE

View File

@ -928,15 +928,20 @@ class ResultViewModel2 : ViewModel() {
) {
val isSubscribed = _subscribeStatus.value ?: return
val response = currentResponse ?: return
if (response !is EpisodeResponse) 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) {
removeSubscribedData(currentId)
statusChangedCallback?.invoke(false)
_subscribeStatus.postValue(false)
_subscribeStatus.postValue(if (response is EpisodeResponse) false else null)
MainActivity.reloadLibraryEvent(true)
} else {
if (response !is EpisodeResponse) {
return
}
checkAndWarnDuplicates(
context,
LibraryListType.SUBSCRIPTIONS,
@ -981,8 +986,8 @@ class ResultViewModel2 : ViewModel() {
)
_subscribeStatus.postValue(true)
statusChangedCallback?.invoke(true)
MainActivity.reloadLibraryEvent(true)
}
}
}
@ -2046,12 +2051,15 @@ class ResultViewModel2 : ViewModel() {
}
private fun postSubscription(loadResponse: LoadResponse) {
val id = loadResponse.getId()
val data = getSubscribedData(id)
if (loadResponse.isEpisodeBased()) {
val id = loadResponse.getId()
val data = getSubscribedData(id)
updateSubscribedData(id, data, loadResponse as? EpisodeResponse)
val isSubscribed = data != null
_subscribeStatus.postValue(isSubscribed)
_subscribeStatus.postValue(data != null)
}
// 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 backgroundPosterUrl: String? = null,
override var contentRating: String? = null,
val id : Int?,
) : LoadResponse
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 repo = APIRepository(api)
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) {
this.plot = searchResponse.plot
this.rating = searchResponse.personalRating?.times(100) ?: searchResponse.rating
@ -2606,12 +2615,14 @@ class ResultViewModel2 : ViewModel() {
this.tags = searchResponse.tags
}
}
val mainId = searchResponse.id ?: response.getId()
val mainId = response.getId()
postSuccessful(
loadResponse = response,
mainId = mainId,
apiRepository = repo, updateEpisodes = false, updateFillers = false)
apiRepository = repo,
updateEpisodes = false,
updateFillers = false)
}
fun load(

View File

@ -6,7 +6,8 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
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>
@ -72,8 +73,7 @@ class SelectAdaptor(val callback: (Any) -> Unit) : RecyclerView.Adapter<Recycler
fun bind(
data: SelectData, isSelected: Boolean, callback: (Any) -> Unit
) {
val isTrueTv = isTrueTvSettings()
if (isTrueTv) {
if (isLayout(TV)) {
item.isFocusable = 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.result.FOCUS_SELF
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
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.ownHide
import com.lagradost.cloudstream3.utils.AppUtils.ownShow
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
@ -107,13 +108,16 @@ class SearchFragment : Fragment() {
)
bottomSheetDialog?.ownShow()
val layout = if (isTvSettings()) R.layout.fragment_search_tv else R.layout.fragment_search
val root = inflater.inflate(layout, container, false)
// TODO TRYCATCH
binding = FragmentSearchBinding.bind(root)
binding = try {
val layout = if (isLayout(TV or EMULATOR)) R.layout.fragment_search_tv else R.layout.fragment_search
val root = inflater.inflate(layout, container, false)
FragmentSearchBinding.bind(root)
} catch (t : Throwable) {
FragmentSearchBinding.inflate(inflater)
}
return root
return binding?.root
}
private fun fixGrid() {
@ -369,7 +373,7 @@ class SearchFragment : Fragment() {
selectedSearchTypes = DataStoreHelper.searchPreferenceTags.toMutableList()
if (isTrueTvSettings()) {
if (isLayout(TV)) {
binding?.searchFilter?.isFocusable = true
binding?.searchFilter?.isFocusableInTouchMode = true
}

View File

@ -1,6 +1,5 @@
package com.lagradost.cloudstream3.ui.search
import android.app.Activity
import android.widget.Toast
import com.lagradost.cloudstream3.CommonActivity.activity
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.DownloadClickEvent
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.DataStoreHelper
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
@ -56,7 +56,7 @@ object SearchHelper {
}
}
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 {
loadPopup(callback.card)
} ?: kotlin.run {

View File

@ -17,7 +17,8 @@ import com.lagradost.cloudstream3.SearchQuality
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.isMovieType
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.DataStoreHelper
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
@ -164,7 +165,7 @@ object SearchResultBuilder {
bg.isFocusable = false
bg.isFocusableInTouchMode = false
if(!isTrueTvSettings()) {
if(!isLayout(TV)) {
bg.setOnClickListener {
click(it)
}
@ -207,7 +208,7 @@ object SearchResultBuilder {
*/
if (isTrueTvSettings()) {
if (isLayout(TV)) {
// bg.isFocusable = true
// bg.isFocusableInTouchMode = true
// 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.InAppAuthAPI
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.isTvSettings
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.setUpToolbar
@ -76,7 +78,7 @@ class SettingsAccount : PreferenceFragmentCompat() {
showAccountSwitch(activity, api)
}
if (isTvSettings()) {
if (isLayout(TV or EMULATOR)) {
binding.accountSwitchAccount.requestFocus()
}
}
@ -140,7 +142,7 @@ class SettingsAccount : PreferenceFragmentCompat() {
binding.loginUsernameInput to api.requiresUsername
)
if (isTvSettings()) {
if (isLayout(TV or EMULATOR)) {
visibilityMap.forEach { (input, isVisible) ->
input.isVisible = isVisible

View File

@ -1,9 +1,5 @@
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.view.LayoutInflater
import android.view.View
@ -16,18 +12,19 @@ import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar
import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.MainSettingsBinding
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
import com.lagradost.cloudstream3.ui.home.HomeFragment
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.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.toPx
@ -35,10 +32,6 @@ import java.io.File
class SettingsFragment : Fragment() {
companion object {
var beneneCount = 0
private var isTv: Boolean = false
private var isTrueTv: Boolean = false
fun PreferenceFragmentCompat?.getPref(id: Int): Preference? {
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.
* */
fun PreferenceFragmentCompat.setPaddingBottom() {
if (isTvSettings()) {
if (isLayout(TV or EMULATOR)) {
listView?.setPadding(0, 0, 0, 100.toPx)
}
}
fun PreferenceFragmentCompat.setToolBarScrollFlags() {
if (isTvSettings()) {
if (isLayout(TV or EMULATOR)) {
val settingsAppbar = view?.findViewById<MaterialToolbar>(R.id.settings_toolbar)
settingsAppbar?.updateLayoutParams<AppBarLayout.LayoutParams> {
@ -69,7 +62,7 @@ class SettingsFragment : Fragment() {
}
}
fun Fragment?.setToolBarScrollFlags() {
if (isTvSettings()) {
if (isLayout(TV or EMULATOR)) {
val settingsAppbar = this?.view?.findViewById<MaterialToolbar>(R.id.settings_toolbar)
settingsAppbar?.updateLayoutParams<AppBarLayout.LayoutParams> {
@ -88,7 +81,7 @@ class SettingsFragment : Fragment() {
activity?.onBackPressedDispatcher?.onBackPressed()
}
}
fixPaddingStatusbar(settingsToolbar)
UIHelper.fixPaddingStatusbar(settingsToolbar)
}
fun Fragment?.setUpToolbar(@StringRes title: Int) {
@ -103,7 +96,7 @@ class SettingsFragment : Fragment() {
activity?.onBackPressedDispatcher?.onBackPressed()
}
}
fixPaddingStatusbar(settingsToolbar)
UIHelper.fixPaddingStatusbar(settingsToolbar)
}
fun getFolderSize(dir: File): Long {
@ -119,60 +112,7 @@ class SettingsFragment : Fragment() {
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() {
binding = null
super.onDestroyView()
@ -197,8 +137,6 @@ class SettingsFragment : Fragment() {
// used to debug leaks showToast(activity,"${VideoDownloadManager.downloadStatusEvent.size} : ${VideoDownloadManager.downloadProgressEvent.size}")
val isTrueTv = isTrueTvSettings()
for (syncApi in accountManagers) {
val login = syncApi.loginInfo()
val pic = login?.profilePicture ?: continue
@ -226,7 +164,7 @@ class SettingsFragment : Fragment() {
setOnClickListener {
navigate(navigationId)
}
if (isTrueTv) {
if (isLayout(TV)) {
isFocusable = true
isFocusableInTouchMode = true
}
@ -234,7 +172,7 @@ class SettingsFragment : Fragment() {
}
// Default focus on TV
if (isTrueTv) {
if (isLayout(TV)) {
settingsGeneral.requestFocus()
}
}

View File

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

View File

@ -9,11 +9,11 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchQuality
import com.lagradost.cloudstream3.mvvm.logError
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.setPaddingBottom
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.updateTv
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
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.setLinearListLayout
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.setUpToolbar
import com.lagradost.cloudstream3.utils.AppUtils.downloadAllPluginsDialog
@ -97,7 +98,7 @@ class ExtensionsFragment : Fragment() {
nextLeft = R.id.nav_rail_view
)
if (!isTrueTvSettings())
if (!isLayout(TV))
binding?.addRepoButton?.let { button ->
button.post {
setPadding(
@ -286,7 +287,7 @@ class ExtensionsFragment : Fragment() {
}
}
val isTv = isTrueTvSettings()
val isTv = isLayout(TV)
binding?.apply {
addRepoButton.isGone = 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.ui.result.setText
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.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
@ -44,7 +45,7 @@ class PluginAdapter(
private val plugins: MutableList<PluginViewData> = mutableListOf()
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)
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.result.FOCUS_SELF
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.setUpToolbar
import com.lagradost.cloudstream3.ui.settings.appLanguages
@ -155,7 +157,7 @@ class PluginsFragment : Fragment() {
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.
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.plugins.RepositoryManager.PREBUILT_REPOSITORIES
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
class RepoAdapter(
@ -23,7 +24,7 @@ class RepoAdapter(
private val repositories: MutableList<RepositoryData> = mutableListOf()
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),
parent,
false

View File

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

View File

@ -13,8 +13,8 @@ import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment
import com.fasterxml.jackson.annotation.JsonProperty
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.jaredrummler.android.colorpicker.ColorPickerDialog
@ -24,7 +24,9 @@ import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
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.Event
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
@ -173,7 +175,7 @@ class ChromecastSubtitlesFragment : Fragment() {
state = getCurrentSavedStyle()
context?.updateState()
val isTvSettings = isTvSettings()
val isTvSettings = isLayout(TV or EMULATOR)
fun View.setFocusableInTv() {
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.R
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.Event
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
@ -252,7 +254,7 @@ class SubtitlesFragment : Fragment() {
state = getCurrentSavedStyle()
context?.updateState()
val isTvTrueSettings = isTrueTvSettings()
val isTvTrueSettings = isLayout(TV)
fun View.setFocusableInTv() {
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.ui.WebviewFragment
import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.settings.Globals
import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel.Companion.downloadAll
import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
@ -78,7 +77,6 @@ import okhttp3.Cache
import java.io.*
import java.net.URL
import java.net.URLDecoder
import kotlin.system.measureTimeMillis
object AppUtils {
fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) {
@ -583,7 +581,7 @@ object AppUtils {
//private val viewModel: ResultViewModel by activityViewModels()
private fun getResultsId(): Int {
return if (isTvSettings()) {
return if (Globals.isLayout(Globals.TV or Globals.EMULATOR)) {
R.id.global_to_navigation_results_tv
} else {
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.
**/
fun AlertDialog.setDefaultFocus(buttonFocus: Int = DialogInterface.BUTTON_NEGATIVE) {
if (!isTvSettings()) return
if (!Globals.isLayout(Globals.TV or Globals.EMULATOR)) return
this.getButton(buttonFocus).run {
isFocusableInTouchMode = true
requestFocus()

View File

@ -21,7 +21,9 @@ import com.lagradost.cloudstream3.databinding.BottomInputDialogBinding
import com.lagradost.cloudstream3.databinding.BottomSelectionDialogBinding
import com.lagradost.cloudstream3.databinding.BottomTextDialogBinding
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.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.UIHelper.setImage
@ -54,7 +56,7 @@ object SingleSelectionHelper {
) {
if (this == null) return
if (isTvSettings()) {
if (isLayout(TV or EMULATOR)) {
val binding = OptionsPopupTvBinding.inflate(layoutInflater)
val dialog = AlertDialog.Builder(this, R.style.AlertDialogCustom)
.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.UiText
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
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.isLayout
import jp.wasabeef.glide.transformations.BlurTransformation
import kotlin.math.roundToInt
@ -468,7 +469,7 @@ object UIHelper {
}
fun Context.getStatusBarHeight(): Int {
if (isTvSettings()) {
if (isLayout(Globals.TV or EMULATOR)) {
return 0
}
@ -570,7 +571,7 @@ object UIHelper {
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
//}
changeStatusBarState(isEmulatorSettings())
changeStatusBarState(isLayout(EMULATOR))
}
fun Context.shouldShowPIPMode(isInPlayer: Boolean): Boolean {

View File

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