diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 08ccd38b..57c228bd 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -62,7 +62,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() ?: "")
@@ -181,7 +181,7 @@ dependencies {
// Design & UI
implementation("jp.wasabeef:glide-transformations:4.3.0")
implementation("androidx.preference:preference-ktx:1.2.1")
- implementation("com.google.android.material:material:1.10.0")
+ implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
@@ -222,6 +222,7 @@ dependencies {
implementation("androidx.palette:palette-ktx:1.0.0") // Palette For Images -> Colors
implementation("androidx.tvprovider:tvprovider:1.0.0")
implementation("com.github.discord:OverlappingPanels:0.1.5") // Gestures
+ implementation ("androidx.biometric:biometric:1.2.0-alpha05") // Fingerprint Authentication
implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0") // SeekBar Preview
// Extensions & Other Libs
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e665c3bc..a23ef725 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -14,7 +14,7 @@
-
+
diff --git a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt
index c93f0f9b..1680d698 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt
@@ -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()
)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
index 759f99d4..4dc78dc7 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
@@ -11,11 +11,9 @@ import android.util.DisplayMetrics
import android.util.Log
import android.view.Gravity
import android.view.KeyEvent
-import android.view.LayoutInflater
import android.view.View
import android.view.View.NO_ID
import android.view.ViewGroup
-import android.widget.TextView
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
@@ -31,11 +29,12 @@ import com.google.android.material.chip.ChipGroup
import com.google.android.material.navigationrail.NavigationRailView
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
+import com.lagradost.cloudstream3.databinding.ToastBinding
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
@@ -99,8 +98,7 @@ object CommonActivity {
var playerEventListener: ((PlayerEventType) -> Unit)? = null
var keyEventListener: ((Pair) -> Boolean)? = null
-
- var currentToast: Toast? = null
+ private var currentToast: Toast? = null
fun showToast(@StringRes message: Int, duration: Int? = null) {
val act = activity ?: return
@@ -156,25 +154,19 @@ object CommonActivity {
} catch (e: Exception) {
logError(e)
}
+
try {
- val inflater =
- act.getSystemService(AppCompatActivity.LAYOUT_INFLATER_SERVICE) as LayoutInflater
-
- val layout: View = inflater.inflate(
- R.layout.toast,
- act.findViewById(R.id.toast_layout_root) as ViewGroup?
- )
-
- val text = layout.findViewById(R.id.text) as TextView
- text.text = message.trim()
+ val binding = ToastBinding.inflate(act.layoutInflater)
+ binding.text.text = message.trim()
+ // custom toasts are deprecated and won't appear when cs3 sets minSDK to api30 (A11)
val toast = Toast(act)
- toast.setGravity(Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM, 0, 5.toPx)
toast.duration = duration ?: Toast.LENGTH_SHORT
- toast.view = layout
- //https://github.com/PureWriter/ToastCompat
- toast.show()
+ toast.setGravity(Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM, 0, 5.toPx)
+ toast.view = binding.root
currentToast = toast
+ toast.show()
+
} catch (e: Exception) {
logError(e)
}
@@ -319,6 +311,7 @@ object CommonActivity {
"Banana" -> R.style.OverlayPrimaryColorBanana
"Party" -> R.style.OverlayPrimaryColorParty
"Pink" -> R.style.OverlayPrimaryColorPink
+ "Lavender" -> R.style.OverlayPrimaryColorLavender
"Monet" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
R.style.OverlayPrimaryColorMonet else R.style.OverlayPrimaryColorNormal
diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt
index 7a25b738..273e267b 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt
@@ -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)
}
/**
diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
index afb2f76f..5a7e72ef 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
@@ -28,6 +28,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.children
import androidx.core.view.isGone
+import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.marginStart
import androidx.fragment.app.FragmentActivity
@@ -85,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
@@ -111,10 +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.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
@@ -131,11 +134,15 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.BackupUtils.backup
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
+import com.lagradost.cloudstream3.utils.BiometricAuthenticator
+import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
+import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper
+import com.lagradost.cloudstream3.utils.DataStoreHelper.accounts
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
@@ -166,7 +173,6 @@ import kotlin.math.absoluteValue
import kotlin.reflect.KClass
import kotlin.system.exitProcess
-
//https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898
//https://wiki.videolan.org/Android_Player_Intents/
@@ -285,7 +291,8 @@ var app = Requests(responseParser = object : ResponseParser {
defaultHeaders = mapOf("user-agent" to USER_AGENT)
}
-class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
+class MainActivity : AppCompatActivity(), ColorPickerDialogListener,
+ BiometricAuthenticator.BiometricAuthCallback {
companion object {
const val TAG = "MAINACT"
const val ANIMATED_OUTLINE: Boolean = false
@@ -331,10 +338,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
// kinda shitty solution, but cant com main->home otherwise for popups
val bookmarksUpdatedEvent = Event()
+
/**
* Used by DataStoreHelper to fully reload home when switching accounts
*/
val reloadHomeEvent = Event()
+
/**
* Used by DataStoreHelper to fully reload library when switching accounts
*/
@@ -462,7 +471,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
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)
@@ -483,8 +492,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
.contains(DubStatus.Dubbed)
) DubStatus.Dubbed else DubStatus.Subbed, null
)
- }else {
- viewModel.loadSmall(this,result)
+ } else {
+ viewModel.loadSmall(this, result)
}
}
@@ -549,7 +558,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
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(
@@ -576,7 +585,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
Configuration.ORIENTATION_PORTRAIT -> {
- isTvSettings()
+ isLayout(TV or EMULATOR)
}
else -> {
@@ -782,9 +791,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
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]
@@ -1100,8 +1110,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
}
- 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)
@@ -1167,11 +1177,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
// 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)
@@ -1183,14 +1193,30 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
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,
+ R.id.home_preview_info_btt,
+ R.id.home_preview_hidden_next_focus,
+ R.id.home_preview_hidden_prev_focus,
+ R.id.result_play_movie_button,
+ R.id.result_play_series_button,
+ R.id.result_resume_series_button,
+ R.id.result_play_trailer_button,
+ R.id.result_bookmark_Button,
+ R.id.result_favorite_Button,
+ R.id.result_subscribe_Button,
+ 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)
}
}
-
-
ActivityMainBinding.bind(newLocalBinding.root) // this may crash
} else {
val newLocalBinding = ActivityMainBinding.inflate(layoutInflater, null, false)
@@ -1202,7 +1228,27 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
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
+
+ if (isLayout(PHONE) && authEnabled && noAccounts) {
+ if (deviceHasPasswordPinLock(this)) {
+ startBiometricAuthentication(this, R.string.biometric_authentication_title, false)
+
+ BiometricAuthenticator.promptInfo?.let { promt ->
+ BiometricAuthenticator.biometricPrompt?.authenticate(promt)
+ }
+
+ // hide background while authenticating, Sorry moms & dads 🙏
+ binding?.navHostFragment?.isInvisible = true
+ }
+ }
// Automatically enable jsdelivr if cant connect to raw.githubusercontent.com
if (this.getKey(getString(R.string.jsdelivr_proxy_key)) == null && isNetworkAvailable()) {
@@ -1288,7 +1334,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
- fun setUserData(status : Resource?) {
+ fun setUserData(status: Resource?) {
if (isLocalList) return
bottomPreviewBinding?.apply {
when (status) {
@@ -1313,7 +1359,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
}
- fun setWatchStatus(state : WatchType?) {
+ fun setWatchStatus(state: WatchType?) {
if (!isLocalList || state == null) return
bottomPreviewBinding?.resultviewPreviewBookmark?.apply {
@@ -1322,13 +1368,42 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
}
- 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()
@@ -1370,6 +1445,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
setUserData(syncViewModel.userData.value)
setWatchStatus(viewModel.watchStatus.value)
+ setSubscribeStatus(viewModel.subscribeStatus.value)
resultviewPreviewBookmark.setOnClickListener {
//viewModel.updateWatchStatus(WatchType.PLANTOWATCH)
@@ -1388,7 +1464,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
)
}
} 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(),
@@ -1415,7 +1493,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
resultviewPreviewFavorite.setImageResource(drawable)
}
- resultviewPreviewFavorite.setOnClickListener{
+ resultviewPreviewFavorite.setOnClickListener {
viewModel.toggleFavoriteStatus(this@MainActivity) { newStatus: Boolean? ->
if (newStatus == null) return@toggleFavoriteStatus
@@ -1431,7 +1509,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
}
- 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 =
@@ -1506,7 +1584,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
}
- if (isTvSettings()) {
+ if (isLayout(TV or EMULATOR)) {
if (navDestination.matchDestination(R.id.navigation_home)) {
attachBackPressedCallback()
} else detachBackPressedCallback()
@@ -1542,7 +1620,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
itemRippleColor = rippleColor
itemActiveIndicatorColor = rippleColor
setupWithNavController(navController)
- if (isTvSettings()) {
+ if (isLayout(TV or EMULATOR)) {
background?.alpha = 200
} else {
background?.alpha = 255
@@ -1743,6 +1821,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
)
}
+ /** Biometric stuff **/
+ override fun onAuthenticationSuccess() {
+ // make background (nav host fragment) visible again
+ binding?.navHostFragment?.isInvisible = false
+ }
+
private var backPressedCallback: OnBackPressedCallback? = null
private fun attachBackPressedCallback() {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt
index eb575775..817d7db3 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt
@@ -49,11 +49,15 @@ inline fun debugWarning(assert: () -> Boolean, message: () -> String) {
}
}
+/** NOTE: Only one observer at a time per value */
fun LifecycleOwner.observe(liveData: LiveData, action: (t: T) -> Unit) {
+ liveData.removeObservers(this)
liveData.observe(this) { it?.let { t -> action(t) } }
}
+/** NOTE: Only one observer at a time per value */
fun LifecycleOwner.observeNullable(liveData: LiveData, action: (t: T) -> Unit) {
+ liveData.removeObservers(this)
liveData.observe(this) { action(it) }
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt b/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt
index adf5abfa..e2bcd6e1 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt
@@ -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()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt
index 99723e90..7552fe9d 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt
@@ -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()
} + mapOf(
diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt
index e0b13ba6..08c8588b 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt
@@ -440,9 +440,9 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
interceptor = interceptor
).isSuccessful
} else {
- val statusResponse = status?.let { setStatus ->
+ val statusResponse = this.status?.let { setStatus ->
val newStatus =
- SimklListStatusType.values()
+ SimklListStatusType.entries
.firstOrNull { it.value == setStatus }?.originalName
?: SimklListStatusType.Watching.originalName!!
@@ -479,9 +479,14 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
).isSuccessful
} ?: true
+ // You cannot rate if you are planning to watch it.
+ val shouldRate =
+ score != null && status != SimklListStatusType.Planning.value
+ val realScore = if (shouldRate) score else null
+
val historyResponse =
// Only post if there are episodes or score to upload
- if (addEpisodes != null || score != null) {
+ if (addEpisodes != null || shouldRate) {
app.post(
"${this.url}/sync/history",
json = StatusRequest(
@@ -492,8 +497,8 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
ids,
addEpisodes?.first,
addEpisodes?.second,
- score,
- score?.let { time },
+ realScore,
+ realScore?.let { time },
)
), movies = emptyList()
),
@@ -827,7 +832,13 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
if (foundItem != null) {
return SimklSyncStatus(
- status = foundItem.status?.let { SyncWatchType.fromInternalId(SimklListStatusType.fromString(it)?.value) }
+ status = foundItem.status?.let {
+ SyncWatchType.fromInternalId(
+ SimklListStatusType.fromString(
+ it
+ )?.value
+ )
+ }
?: return null,
score = foundItem.user_rating,
watchedEpisodes = foundItem.watched_episodes_count,
@@ -839,7 +850,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
)
} else {
return SimklSyncStatus(
- status = SyncWatchType.fromInternalId(SimklListStatusType.None.value) ,
+ status = SyncWatchType.fromInternalId(SimklListStatusType.None.value),
score = 0,
watchedEpisodes = 0,
maxEpisodes = if (searchResult.type == "movie") 0 else searchResult.total_episodes,
@@ -859,11 +870,13 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
val builder = SimklScoreBuilder.Builder()
.apiUrl(this.mainUrl)
.score(status.score, simklStatus?.oldScore)
- .status(status.status.internalId, (status as? SimklSyncStatus)?.oldStatus?.let { oldStatus ->
- SimklListStatusType.values().firstOrNull {
- it.originalName == oldStatus
- }?.value
- })
+ .status(
+ status.status.internalId,
+ (status as? SimklSyncStatus)?.oldStatus?.let { oldStatus ->
+ SimklListStatusType.entries.firstOrNull {
+ it.originalName == oldStatus
+ }?.value
+ })
.interceptor(interceptor)
.ids(MediaObject.Ids.fromMap(parsedId))
@@ -996,7 +1009,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
val list = getSyncListSmart() ?: return null
val baseMap =
- SimklListStatusType.values()
+ SimklListStatusType.entries
.filter { it.value >= 0 && it.value != SimklListStatusType.ReWatching.value }
.associate {
it.stringRes to emptyList()
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt
index 60260edf..de0b5c05 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt
@@ -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
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt
index 23071f59..41aef176 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt
@@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.account
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
+import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
@@ -17,13 +18,19 @@ 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.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
import com.lagradost.cloudstream3.utils.DataStoreHelper.accounts
import com.lagradost.cloudstream3.utils.DataStoreHelper.selectedKeyIndex
import com.lagradost.cloudstream3.utils.DataStoreHelper.setAccount
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
-class AccountSelectActivity : AppCompatActivity() {
+class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.BiometricAuthCallback {
lateinit var viewModel: AccountViewModel
@@ -41,13 +48,36 @@ class AccountSelectActivity : AppCompatActivity() {
)
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
- val skipStartup = settingsManager.getBoolean(
- getString(R.string.skip_startup_account_select_key),
- false
+ val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_key), false)
+ val skipStartup = settingsManager.getBoolean(getString(R.string.skip_startup_account_select_key), false
) || accounts.count() <= 1
viewModel = ViewModelProvider(this)[AccountViewModel::class.java]
+ fun askBiometricAuth() {
+
+ if (isLayout(PHONE) && authEnabled) {
+ if (deviceHasPasswordPinLock(this)) {
+ startBiometricAuthentication(
+ this,
+ R.string.biometric_authentication_title,
+ false
+ )
+
+ BiometricAuthenticator.promptInfo?.let { promt ->
+ BiometricAuthenticator.biometricPrompt?.authenticate(promt)
+ }
+ }
+ }
+ }
+
+ observe(viewModel.isAllowedLogin) { isAllowedLogin ->
+ if (isAllowedLogin) {
+ // We are allowed to continue to MainActivity
+ navigateToMainActivity()
+ }
+ }
+
// Don't show account selection if there is only
// one account that exists
if (!isEditingFromMainActivity && skipStartup) {
@@ -55,12 +85,6 @@ class AccountSelectActivity : AppCompatActivity() {
if (currentAccount?.lockPin != null) {
CommonActivity.init(this)
viewModel.handleAccountSelect(currentAccount, this, true)
- observe(viewModel.isAllowedLogin) { isAllowedLogin ->
- if (isAllowedLogin) {
- // We are allowed to continue to MainActivity
- navigateToMainActivity()
- }
- }
} else {
if (accounts.count() > 1) {
showToast(this, getString(
@@ -88,12 +112,6 @@ class AccountSelectActivity : AppCompatActivity() {
// Handle the selected account
accountSelectCallback = {
viewModel.handleAccountSelect(it, this)
- observe(viewModel.isAllowedLogin) { isAllowedLogin ->
- if (isAllowedLogin) {
- // We are allowed to continue to MainActivity
- navigateToMainActivity()
- }
- }
},
accountCreateCallback = { viewModel.handleAccountUpdate(it, this) },
accountEditCallback = {
@@ -111,7 +129,7 @@ class AccountSelectActivity : AppCompatActivity() {
recyclerView.adapter = adapter
- if (isTvSettings()) {
+ if (isLayout(TV or EMULATOR)) {
binding.editAccountButton.setBackgroundResource(
R.drawable.player_button_tv_attr_no_bg
)
@@ -152,12 +170,14 @@ class AccountSelectActivity : AppCompatActivity() {
viewModel.toggleIsEditing()
}
- if (isTvSettings()) {
+ if (isLayout(TV or EMULATOR)) {
recyclerView.spanCount = if (liveAccounts.count() + 1 <= 6) {
liveAccounts.count() + 1
} else 6
}
}
+
+ askBiometricAuth()
}
private fun navigateToMainActivity() {
@@ -165,4 +185,8 @@ class AccountSelectActivity : AppCompatActivity() {
startActivity(mainIntent)
finish() // Finish the account selection activity
}
+
+ override fun onAuthenticationSuccess() {
+ Log.i(BiometricAuthenticator.TAG,"Authentication successful in AccountSelectActivity")
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt
index 27c2e1a3..e08eb772 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt
@@ -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)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt
index d20fcf93..a729f33a 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt
@@ -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
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt
index d54ea488..7a68330f 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt
@@ -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()
}
@@ -529,6 +541,7 @@ class HomeFragment : Fragment() {
super.onScrolled(recyclerView, dx, dy)
}
})
+
}
@@ -539,7 +552,7 @@ class HomeFragment : Fragment() {
settingsManager.getBoolean(
getString(R.string.random_button_key),
false
- ) && !isTvSettings()
+ ) && isLayout(PHONE)
binding?.homeRandom?.visibility = View.GONE
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt
index 443278a9..50111428 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt
@@ -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() {
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+) : RecyclerView.Adapter() {
+ // 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()
+ 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)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt
index 0e397f81..7ad15e4e 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt
@@ -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,
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
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt
index 666fbc24..f0542b77 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt
@@ -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() {
@@ -40,7 +42,7 @@ class HomeScrollAdapter : RecyclerView.Adapter() {
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)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt
index f471fefd..b0d4bdf8 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt
@@ -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)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt
index 8e9c8521..664946b1 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt
@@ -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)
@@ -131,6 +136,18 @@ class LibraryFragment : Fragment() {
super.onSaveInstanceState(outState)
}
+ private fun updateRandom() {
+ val position = libraryViewModel.currentPage.value ?: 0
+ val pages = (libraryViewModel.pages.value as? Resource.Success)?.value ?: return
+ if (toggleRandomButton) {
+ listLibraryItems.clear()
+ listLibraryItems.addAll(pages[position].items)
+ binding?.libraryRandom?.isVisible = listLibraryItems.isNotEmpty()
+ } else {
+ binding?.libraryRandom?.isGone = true
+ }
+ }
+
@SuppressLint("ResourceType", "CutPasteId")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -208,7 +225,7 @@ class LibraryFragment : Fragment() {
settingsManager.getBoolean(
getString(R.string.random_button_key),
false
- ) && !SettingsFragment.isTvSettings()
+ ) && isLayout(PHONE)
binding?.libraryRandom?.visibility = View.GONE
}
@@ -395,15 +412,7 @@ class LibraryFragment : Fragment() {
binding?.viewpager?.setCurrentItem(page, false)
}
- observe(libraryViewModel.currentPage){
- if (toggleRandomButton) {
- listLibraryItems.clear()
- listLibraryItems.addAll(pages[it].items)
- libraryRandom.isVisible = listLibraryItems.isNotEmpty()
- } else {
- libraryRandom.isGone = true
- }
- }
+ updateRandom()
// Only stop loading after 300ms to hide the fade effect the viewpager produces when updating
// Without this there would be a flashing effect:
@@ -481,6 +490,7 @@ class LibraryFragment : Fragment() {
}
observe(libraryViewModel.currentPage) { position ->
+ updateRandom()
val all = binding?.viewpager?.allViews?.toList()
?.filterIsInstance()
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt
index 6731eae2..c41ec681 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt
@@ -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(R.id.search_bar)
.apply {
if (diff <= 0)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt
index e8d74752..6735a350 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt
@@ -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()
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
index 01069f66..7ff56886 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
@@ -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]
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt
index 6414374b..fb600ef1 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt
@@ -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()
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt
index 5b300c06..26cf9918 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt
@@ -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()
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt
index 6b63e623..fad349c8 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt
@@ -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
@@ -246,12 +246,21 @@ class EpisodeAdapter(
episodeDescript.apply {
text = card.description.html()
isGone = text.isNullOrBlank()
+
+ var isExpanded = false
setOnClickListener {
- clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
+ if (isLayout(TV)) {
+ clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
+ } else {
+ isExpanded = !isExpanded
+ maxLines = if (isExpanded) {
+ Integer.MAX_VALUE
+ } else 4
+ }
}
}
- if (!isTrueTv) {
+ if (isLayout(EMULATOR or PHONE)) {
episodePoster.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
}
@@ -266,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
@@ -291,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 {
@@ -352,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
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ImageAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ImageAdapter.kt
index ca2934ef..7b7bae43 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ImageAdapter.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ImageAdapter.kt
@@ -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(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
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt
index 76066c2e..8d0ca37b 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt
@@ -2,9 +2,6 @@ package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint
import android.app.Dialog
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Rect
@@ -34,7 +31,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.CommonActivity
-import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
@@ -62,16 +58,15 @@ import com.lagradost.cloudstream3.ui.result.ResultFragment.updateUIEvent
import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
-import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
-import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogText
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.UIHelper
+import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
@@ -688,14 +683,15 @@ open class ResultFragmentPhone : FullScreenPlayer() {
resultNextAiringTime.setText(d.nextAiringDate)
resultPoster.setImage(d.posterImage)
resultPosterBackground.setImage(d.posterBackgroundImage)
- resultDescription.setTextHtml(d.plotText)
- resultDescription.setOnClickListener {
- activity?.let { activity ->
- activity.showBottomDialogText(
- d.titleText.asString(activity),
- d.plotText.asString(activity).html(),
- {}
- )
+
+ var isExpanded = false
+ resultDescription.apply {
+ setTextHtml(d.plotText)
+ setOnClickListener {
+ isExpanded = !isExpanded
+ maxLines = if (isExpanded) {
+ Integer.MAX_VALUE
+ } else 10
}
}
@@ -758,14 +754,8 @@ open class ResultFragmentPhone : FullScreenPlayer() {
resultReloadConnectionOpenInBrowser.isVisible = data is Resource.Failure
resultTitle.setOnLongClickListener {
- val titleToCopy = resultTitle.text
- val clipboardManager =
- activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?
- clipboardManager?.setPrimaryClip(ClipData.newPlainText("Title", titleToCopy))
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
- showToast(R.string.copyTitle, Toast.LENGTH_SHORT)
- }
- return@setOnLongClickListener true
+ clipboardHelper(txt(R.string.title), resultTitle.text)
+ true
}
}
}
@@ -901,14 +891,6 @@ open class ResultFragmentPhone : FullScreenPlayer() {
observe(viewModel.recommendations) { recommendations ->
setRecommendations(recommendations, null)
}
- observe(viewModel.episodeSynopsis) { description ->
- activity?.let { activity ->
- activity.showBottomDialogText(
- activity.getString(R.string.synopsis),
- description.html()
- ) { viewModel.releaseEpisodeSynopsis() }
- }
- }
context?.let { ctx ->
val arrayAdapter = ArrayAdapter(ctx, R.layout.sort_bottom_single_choice)
/*
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt
index 427e9cb3..3263ee93 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt
@@ -33,13 +33,16 @@ import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup
import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
+import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
import com.lagradost.cloudstream3.ui.result.ResultFragment.getStoredData
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.utils.AppUtils.getNameFull
+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
@@ -129,9 +132,9 @@ class ResultFragmentTv : Fragment() {
* Note that this will steal any focus if the episode loading is too slow (unlikely).
*/
private fun focusPlayButton() {
- binding?.resultPlayMovie?.requestFocus()
- binding?.resultPlaySeries?.requestFocus()
- binding?.resultResumeSeries?.requestFocus()
+ binding?.resultPlayMovieButton?.requestFocus()
+ binding?.resultPlaySeriesButton?.requestFocus()
+ binding?.resultResumeSeriesButton?.requestFocus()
}
private fun setRecommendations(rec: List?, validApiName: String?) {
@@ -246,37 +249,15 @@ class ResultFragmentTv : Fragment() {
storedData.start
)
// ===== ===== =====
+ var comingSoon = false
binding?.apply {
//episodesShadow.rotationX = 180.0f//if(episodesShadow.isRtl()) 180.0f else 0.0f
-
- val leftListener: View.OnFocusChangeListener =
- View.OnFocusChangeListener { _, hasFocus ->
- if (!hasFocus) return@OnFocusChangeListener
- toggleEpisodes(false)
- }
- val rightListener: View.OnFocusChangeListener =
- View.OnFocusChangeListener { _, hasFocus ->
- if (!hasFocus) return@OnFocusChangeListener
- toggleEpisodes(true)
- }
-
- resultPlayMovie.onFocusChangeListener = leftListener
- resultPlaySeries.onFocusChangeListener = leftListener
- resultResumeSeries.onFocusChangeListener = leftListener
- resultPlayTrailer.onFocusChangeListener = leftListener
- resultEpisodesShow.onFocusChangeListener = rightListener
- resultDescription.onFocusChangeListener = leftListener
- resultBookmarkButton.onFocusChangeListener = leftListener
- resultFavoriteButton.onFocusChangeListener = leftListener
- resultEpisodesShow.setOnClickListener {
- // toggle, to make it more touch accessable just in case someone thinks that a
- // tv layout is better but is using a touch device
- toggleEpisodes(!episodeHolderTv.isVisible)
- }
-
- // resultEpisodes.onFocusChangeListener = leftListener
+ // parallax on background
+ resultFinishLoading.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { view, _, scrollY, _, oldScrollY ->
+ backgroundPosterHolder.translationY = -scrollY.toFloat() * 0.8f
+ })
redirectToPlay.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) return@setOnFocusChangeListener
@@ -284,13 +265,14 @@ class ResultFragmentTv : Fragment() {
binding?.apply {
val views = listOf(
- resultPlayMovie,
- resultPlaySeries,
- resultResumeSeries,
- resultPlayTrailer,
+ resultPlayMovieButton,
+ resultPlaySeriesButton,
+ resultResumeSeriesButton,
+ resultPlayTrailerButton,
resultBookmarkButton,
resultFavoriteButton,
- resultSubscribeButton
+ resultSubscribeButton,
+ resultSearchButton
)
for (requestView in views) {
if (!requestView.isVisible) continue
@@ -299,11 +281,6 @@ class ResultFragmentTv : Fragment() {
}
}
- // parallax on background
- resultFinishLoading.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
- backgroundPosterHolder.translationY = -scrollY.toFloat() * 0.8f
- })
-
redirectToEpisodes.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) return@setOnFocusChangeListener
toggleEpisodes(true)
@@ -313,7 +290,7 @@ class ResultFragmentTv : Fragment() {
resultSeasonSelection,
resultRangeSelection,
resultEpisodes,
- resultPlayTrailer,
+ resultPlayTrailerButton,
)
for (requestView in views) {
if (!requestView.isShown) continue
@@ -322,6 +299,46 @@ class ResultFragmentTv : Fragment() {
}
}
+ mapOf(
+ resultPlayMovieButton to resultPlayMovieText,
+ resultPlaySeriesButton to resultPlaySeriesText,
+ resultResumeSeriesButton to resultResumeSeriesText,
+ resultPlayTrailerButton to resultPlayTrailerText,
+ resultBookmarkButton to resultBookmarkText,
+ resultFavoriteButton to resultFavoriteText,
+ resultSubscribeButton to resultSubscribeText,
+ resultSearchButton to resultSearchText,
+ resultEpisodesShowButton to resultEpisodesShowText
+ ).forEach { (button , text) ->
+
+ button.setOnFocusChangeListener { view, hasFocus ->
+ if (!hasFocus) {
+ text.isSelected = false
+ if (view.id == R.id.result_episodes_show_button) toggleEpisodes(false)
+ return@setOnFocusChangeListener
+ }
+
+ text.isSelected = true
+ if (button.tag == context?.getString(R.string.tv_no_focus_tag)){
+ resultFinishLoading.scrollTo(0,0)
+ }
+ when (button.id) {
+ R.id.result_episodes_show_button -> {
+ toggleEpisodes(true)
+ }
+ else -> {
+ toggleEpisodes(false)
+ }
+ }
+ }
+ }
+
+ resultEpisodesShowButton.setOnClickListener {
+ // toggle, to make it more touch accessible just in case someone thinks that a
+ // tv layout is better but is using a touch device
+ toggleEpisodes(!episodeHolderTv.isVisible)
+ }
+
resultEpisodes.setLinearListLayout(
isHorizontal = false,
nextUp = FOCUS_SELF,
@@ -363,10 +380,6 @@ class ResultFragmentTv : Fragment() {
resultMetaSite.isFocusable = false
- //resultReloadConnectionOpenInBrowser.setOnClickListener {view ->
- // view.context?.openBrowser(storedData?.url ?: return@setOnClickListener, fallbackWebview = true)
- //}
-
resultSeasonSelection.setAdapter()
resultRangeSelection.setAdapter()
resultDubSelection.setAdapter()
@@ -430,9 +443,9 @@ class ResultFragmentTv : Fragment() {
val aboveCast = listOf(
binding?.resultEpisodesShow,
- binding?.resultBookmarkButton,
- binding?.resultFavoriteButton,
- binding?.resultSubscribeButton,
+ binding?.resultBookmark,
+ binding?.resultFavorite,
+ binding?.resultSubscribe,
).firstOrNull {
it?.isVisible == true
}
@@ -443,8 +456,16 @@ class ResultFragmentTv : Fragment() {
observeNullable(viewModel.resumeWatching) { resume ->
binding?.apply {
+
+ if (resume == null) {
+ return@observeNullable
+ }
+ resultResumeSeries.isVisible = true
+ resultPlayMovie.isVisible = false
+ resultPlaySeries.isVisible = false
+
// show progress no matter if series or movie
- resume?.progress?.let { progress ->
+ resume.progress?.let { progress ->
resultResumeSeriesProgressText.setText(progress.progressLeft)
resultResumeSeriesProgress.apply {
isVisible = true
@@ -456,37 +477,20 @@ class ResultFragmentTv : Fragment() {
resultResumeProgressHolder.isVisible = false
}
- // if movie then hide both as movie button is
- // always visible on movies, this is done in movie observe
-
- if (resume?.isMovie == true) {
- resultPlaySeries.isVisible = false
- resultResumeSeries.isVisible = false
- return@observeNullable
- }
-
- // if series then
- // > resultPlaySeries is visible when null
- // > resultResumeSeries is visible when not null
- if (resume == null) {
- resultPlaySeries.isVisible = true
- resultResumeSeries.isVisible = false
- return@observeNullable
- }
-
- resultPlaySeries.isVisible = false
- resultResumeSeries.isVisible = true
-
focusPlayButton()
+ // Stops last button right focus if it is a movie
+ if (resume.isMovie)
+ resultSearchButton.nextFocusRightId = R.id.result_search_Button
- resultResumeSeries.text =
- if (resume.isMovie) context?.getString(R.string.play_movie_button) else context?.getNameFull(
- null, // resume.result.name, we don't want episode title
- resume.result.episode,
- resume.result.season
- )
+ resultResumeSeriesText.text =
+ when {
+ resume.isMovie -> context?.getString(R.string.resume)
+ resume.result.season != null ->
+ "${getString(R.string.season_short)}${resume.result.season}:${getString(R.string.episode_short)}${resume.result.episode}"
+ else -> "${getString(R.string.episode)} ${resume.result.episode}"
+ }
- resultResumeSeries.setOnClickListener {
+ resultResumeSeriesButton.setOnClickListener {
viewModel.handleAction(
EpisodeClickEvent(
storedData.playerAction, //?: ACTION_PLAY_EPISODE_IN_PLAYER,
@@ -495,7 +499,7 @@ class ResultFragmentTv : Fragment() {
)
}
- resultResumeSeries.setOnLongClickListener {
+ resultResumeSeriesButton.setOnLongClickListener {
viewModel.handleAction(
EpisodeClickEvent(ACTION_SHOW_OPTIONS, resume.result)
)
@@ -509,9 +513,9 @@ class ResultFragmentTv : Fragment() {
context?.updateHasTrailers()
if (!LoadResponse.isTrailersEnabled) return@observe
val trailers = trailersLinks.flatMap { it.mirros }
- binding?.resultPlayTrailer?.apply {
- isGone = trailers.isEmpty()
- setOnClickListener {
+ binding?.apply {
+ resultPlayTrailer.isGone = trailers.isEmpty()
+ resultPlayTrailerButton.setOnClickListener {
if (trailers.isEmpty()) return@setOnClickListener
activity.navigate(
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
@@ -526,24 +530,38 @@ class ResultFragmentTv : Fragment() {
}
observe(viewModel.watchStatus) { watchType ->
- binding?.resultBookmarkButton?.apply {
- setText(watchType.stringRes)
- setOnClickListener { view ->
- activity?.showBottomDialog(
- WatchType.values().map { view.context.getString(it.stringRes) }.toList(),
- watchType.ordinal,
- view.context.getString(R.string.action_add_to_bookmarks),
- showApply = false,
- {}) {
- viewModel.updateWatchStatus(WatchType.values()[it], context)
+ binding?.apply {
+ resultBookmarkText.setText(watchType.stringRes)
+
+ resultBookmarkButton.apply {
+
+ val drawable = if (watchType.stringRes == R.string.type_none) {
+ R.drawable.outline_bookmark_add_24
+ } else {
+ R.drawable.ic_baseline_bookmark_24
+ }
+ setIconResource(drawable)
+
+ setOnClickListener { view ->
+ activity?.showBottomDialog(
+ WatchType.entries.map { view.context.getString(it.stringRes) }.toList(),
+ watchType.ordinal,
+ view.context.getString(R.string.action_add_to_bookmarks),
+ showApply = false,
+ {}) {
+ viewModel.updateWatchStatus(WatchType.entries[it], context)
+ }
}
}
}
}
observeNullable(viewModel.favoriteStatus) { isFavorite ->
+
+ binding?.resultFavorite?.isVisible = isFavorite != null
+
binding?.resultFavoriteButton?.apply {
- isVisible = isFavorite != null
+
if (isFavorite == null) return@observeNullable
val drawable = if (isFavorite) {
@@ -552,14 +570,8 @@ class ResultFragmentTv : Fragment() {
R.drawable.ic_baseline_favorite_border_24
}
- val text = if (isFavorite) {
- R.string.action_remove_from_favorites
- } else {
- R.string.action_add_to_favorites
- }
-
setIconResource(drawable)
- setText(text)
+
setOnClickListener {
viewModel.toggleFavoriteStatus(context) { newStatus: Boolean? ->
if (newStatus == null) return@toggleFavoriteStatus
@@ -576,11 +588,21 @@ class ResultFragmentTv : Fragment() {
}
}
}
+
+ binding?.resultFavoriteText?.apply {
+ val text = if (isFavorite == true) {
+ R.string.unfavorite
+ } else {
+ R.string.favorite
+ }
+ setText(text)
+ }
}
observeNullable(viewModel.subscribeStatus) { isSubscribed ->
+ binding?.resultSubscribe?.isVisible = isSubscribed != null && isLayout(EMULATOR)
binding?.resultSubscribeButton?.apply {
- isVisible = isSubscribed != null && context.isEmulatorSettings()
+
if (isSubscribed == null) return@observeNullable
val drawable = if (isSubscribed) {
@@ -589,14 +611,8 @@ class ResultFragmentTv : Fragment() {
R.drawable.baseline_notifications_none_24
}
- val text = if (isSubscribed) {
- R.string.action_unsubscribe
- } else {
- R.string.action_subscribe
- }
-
setIconResource(drawable)
- setText(text)
+
setOnClickListener {
viewModel.toggleSubscriptionStatus(context) { newStatus: Boolean? ->
if (newStatus == null) return@toggleSubscriptionStatus
@@ -614,30 +630,47 @@ class ResultFragmentTv : Fragment() {
CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT)
}
}
+
+ binding?.resultSubscribeText?.apply {
+ val text = if (isSubscribed) {
+ R.string.action_unsubscribe
+ } else {
+ R.string.action_subscribe
+ }
+ setText(text)
+ }
}
}
observeNullable(viewModel.movie) { data ->
+ if (data == null ) {
+ return@observeNullable
+ }
+
binding?.apply {
- resultPlayMovie.isVisible = data is Resource.Success
- resultPlaySeries.isVisible = data == null
- seriesHolder.isVisible = data == null
- resultEpisodesShow.isVisible = data == null
(data as? Resource.Success)?.value?.let { (text, ep) ->
- resultPlayMovie.setText(text)
- resultPlayMovie.setOnClickListener {
+
+ resultPlayMovieButton.setOnClickListener {
viewModel.handleAction(
EpisodeClickEvent(ACTION_CLICK_DEFAULT, ep)
)
}
- resultPlayMovie.setOnLongClickListener {
+ resultPlayMovieButton.setOnLongClickListener {
viewModel.handleAction(
EpisodeClickEvent(ACTION_SHOW_OPTIONS, ep)
)
return@setOnLongClickListener true
}
- focusPlayButton()
+
+ resultPlayMovie.isVisible = !comingSoon && resultResumeSeries.isGone
+ if (comingSoon)
+ resultBookmarkButton.requestFocus()
+ else
+ resultPlayMovieButton.requestFocus()
+
+ // Stops last button right focus
+ resultSearchButton.nextFocusRightId = R.id.result_search_Button
}
}
}
@@ -720,35 +753,43 @@ class ResultFragmentTv : Fragment() {
observe(viewModel.recommendations) { recommendations ->
setRecommendations(recommendations, null)
}
- observe(viewModel.episodeSynopsis) { description ->
- view.context?.let { ctx ->
- val builder: AlertDialog.Builder =
- AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
- builder.setMessage(description.html())
- .setTitle(R.string.synopsis)
- .setOnDismissListener {
- viewModel.releaseEpisodeSynopsis()
- }
- .show()
+
+ if (isLayout(TV)) {
+ observe(viewModel.episodeSynopsis) { description ->
+ view.context?.let { ctx ->
+ val builder: AlertDialog.Builder =
+ AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
+ builder.setMessage(description.html())
+ .setTitle(R.string.synopsis)
+ .setOnDismissListener {
+ viewModel.releaseEpisodeSynopsis()
+ }
+ .show()
+ }
}
}
// Used to request focus the first time the episodes are loaded.
var hasLoadedEpisodesOnce = false
observeNullable(viewModel.episodes) { episodes ->
+ if (episodes == null) return@observeNullable
+
binding?.apply {
- resultEpisodes.isVisible = episodes is Resource.Success
+
+ if (comingSoon)
+ resultBookmarkButton.requestFocus()
+
// resultEpisodeLoading.isVisible = episodes is Resource.Loading
if (episodes is Resource.Success) {
val first = episodes.value.firstOrNull()
if (first != null) {
- resultPlaySeries.text = context?.getNameFull(
- null, // resume.result.name, we don't want episode title
- first.episode,
- first.season
- )
-
- resultPlaySeries.setOnClickListener {
+ resultPlaySeriesText.text =
+ when {
+ first.season != null ->
+ "${getString(R.string.season_short)}${first.season}:${getString(R.string.episode_short)}${first.episode}"
+ else -> "${getString(R.string.episode)} ${first.episode}"
+ }
+ resultPlaySeriesButton.setOnClickListener {
viewModel.handleAction(
EpisodeClickEvent(
ACTION_CLICK_DEFAULT,
@@ -756,7 +797,7 @@ class ResultFragmentTv : Fragment() {
)
)
}
- resultPlaySeries.setOnLongClickListener {
+ resultPlaySeriesButton.setOnLongClickListener {
viewModel.handleAction(
EpisodeClickEvent(ACTION_SHOW_OPTIONS, first)
)
@@ -764,7 +805,9 @@ class ResultFragmentTv : Fragment() {
}
if (!hasLoadedEpisodesOnce) {
hasLoadedEpisodesOnce = true
- focusPlayButton()
+ resultPlaySeries.isVisible = resultResumeSeries.isGone && !comingSoon
+ resultEpisodesShow.isVisible = true && !comingSoon
+ resultPlaySeriesButton.requestFocus()
}
}
@@ -826,19 +869,31 @@ class ResultFragmentTv : Fragment() {
resultMetaYear.setText(d.yearText)
resultMetaDuration.setText(d.durationText)
resultMetaRating.setText(d.ratingText)
+ resultMetaStatus.setText(d.onGoingText)
resultMetaContentRating.setText(d.contentRatingText)
resultCastText.setText(d.actorsText)
resultNextAiring.setText(d.nextAiringEpisode)
resultNextAiringTime.setText(d.nextAiringDate)
resultPoster.setImage(d.posterImage)
- resultDescription.setTextHtml(d.plotText)
- resultDescription.setOnClickListener { view ->
- view.context?.let { ctx ->
- val builder: AlertDialog.Builder =
- AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
- builder.setMessage(d.plotText.asString(ctx).html())
- .setTitle(d.plotHeaderText.asString(ctx))
- .show()
+
+ var isExpanded = false
+ resultDescription.apply {
+ setTextHtml(d.plotText)
+ setOnClickListener {
+ if (isLayout(EMULATOR)) {
+ isExpanded = !isExpanded
+ maxLines = if (isExpanded) {
+ Integer.MAX_VALUE
+ } else 10
+ } else {
+ view.context?.let { ctx ->
+ val builder: AlertDialog.Builder =
+ AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
+ builder.setMessage(d.plotText.asString(ctx).html())
+ .setTitle(d.plotHeaderText.asString(ctx))
+ .show()
+ }
+ }
}
}
@@ -859,8 +914,9 @@ class ResultFragmentTv : Fragment() {
radius = 0,
errorImageDrawable = error
)
- resultComingSoon.isVisible = d.comingSoon
- resultDataHolder.isGone = d.comingSoon
+ comingSoon = d.comingSoon
+ resultTvComingSoon.isVisible = d.comingSoon
+
UIHelper.populateChips(resultTag, d.tags)
resultCastItems.isGone = d.actors.isNullOrEmpty()
(resultCastItems.adapter as? ActorAdaptor)?.updateList(
@@ -871,6 +927,10 @@ class ResultFragmentTv : Fragment() {
// If there is no rating to display, we don't want an empty gap
resultMetaContentRating.width = 0
}
+
+ resultSearchButton.setOnClickListener {
+ QuickSearchFragment.pushSearch(activity, d.title)
+ }
}
is Resource.Loading -> {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt
index c24efe56..c90e01d0 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt
@@ -20,6 +20,7 @@ import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.APIHolder.unixTime
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
+import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.activity
import com.lagradost.cloudstream3.CommonActivity.getCastSession
@@ -31,6 +32,7 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.isMovie
import com.lagradost.cloudstream3.metaproviders.SyncRedirector
import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.syncproviders.AccountManager
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.secondsToReadable
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
import com.lagradost.cloudstream3.syncproviders.providers.SimklApi
@@ -79,12 +81,12 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState
import com.lagradost.cloudstream3.utils.DataStoreHelper.setSubscribedData
import com.lagradost.cloudstream3.utils.DataStoreHelper.setVideoWatchState
import com.lagradost.cloudstream3.utils.DataStoreHelper.updateSubscribedData
+import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import kotlinx.coroutines.*
import java.io.File
import java.util.concurrent.TimeUnit
-
/** This starts at 1 */
data class EpisodeRange(
// used to index data
@@ -261,8 +263,7 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
metaText =
if (repo.providerType == ProviderType.MetaProvider) txt(R.string.provider_info_meta) else null,
durationText = if (dur == null || dur <= 0) null else txt(
- R.string.duration_format,
- dur
+ secondsToReadable(dur * 60, "0 mins")
),
onGoingText = if (this is EpisodeResponse) {
txt(
@@ -927,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,
@@ -980,8 +986,8 @@ class ResultViewModel2 : ViewModel() {
)
_subscribeStatus.postValue(true)
-
statusChangedCallback?.invoke(true)
+ MainActivity.reloadLibraryEvent(true)
}
}
}
@@ -1692,14 +1698,8 @@ class ResultViewModel2 : ViewModel() {
LoadType.ExternalApp,
txt(R.string.episode_action_copy_link)
) { (result, index) ->
- val act = activity ?: return@acquireSingleLink
- val serviceClipboard =
- (act.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)
- ?: return@acquireSingleLink
val link = result.links[index]
- val clip = ClipData.newPlainText(link.name, link.url)
- serviceClipboard.setPrimaryClip(clip)
- showToast(R.string.copy_link_toast, Toast.LENGTH_SHORT)
+ clipboardHelper(txt(link.name), link.url)
}
}
@@ -2051,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)
}
}
@@ -2464,7 +2467,7 @@ class ResultViewModel2 : ViewModel() {
ResumeProgress(
progress = (viewPos.position / 1000).toInt(),
maxProgress = (viewPos.duration / 1000).toInt(),
- txt(R.string.resume_time_left, (viewPos.duration - viewPos.position) / (60_000))
+ txt(R.string.resume_remaining, secondsToReadable(((viewPos.duration - viewPos.position) / 1_000).toInt(), "0 mins"))
)
}
@@ -2590,6 +2593,7 @@ class ResultViewModel2 : ViewModel() {
override var posterHeaders: Map? = null,
override var backgroundPosterUrl: String? = null,
override var contentRating: String? = null,
+ val id : Int?,
) : LoadResponse
fun loadSmall(activity: Activity?, searchResponse : SearchResponse) = ioSafe {
@@ -2599,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
@@ -2611,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(
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SelectAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SelectAdaptor.kt
index 6fe45730..5a23bfc1 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SelectAdaptor.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SelectAdaptor.kt
@@ -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
@@ -72,8 +73,7 @@ class SelectAdaptor(val callback: (Any) -> Unit) : RecyclerView.Adapter Unit
) {
- val isTrueTv = isTrueTvSettings()
- if (isTrueTv) {
+ if (isLayout(TV)) {
item.isFocusable = true
item.isFocusableInTouchMode = true
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt
index 243d9f4e..4b4700ce 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt
@@ -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
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHelper.kt
index 3e33e01a..5b943105 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHelper.kt
@@ -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 {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt
index e1b72b30..d18c0197 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt
@@ -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
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/Globals.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/Globals.kt
new file mode 100644
index 00000000..aa513d87
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/Globals.kt
@@ -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
+ }
+}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt
index aa5a3182..298431ee 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt
@@ -11,8 +11,10 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
+import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.AccountManagmentBinding
@@ -27,12 +29,17 @@ 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
+import com.lagradost.cloudstream3.utils.AppUtils.html
+import com.lagradost.cloudstream3.utils.BackupUtils
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
+import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogText
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.setImage
@@ -71,7 +78,7 @@ class SettingsAccount : PreferenceFragmentCompat() {
showAccountSwitch(activity, api)
}
- if (isTvSettings()) {
+ if (isLayout(TV or EMULATOR)) {
binding.accountSwitchAccount.requestFocus()
}
}
@@ -135,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
@@ -256,6 +263,24 @@ class SettingsAccount : PreferenceFragmentCompat() {
hideKeyboard()
setPreferencesFromResource(R.xml.settings_account, rootKey)
+ getPref(R.string.biometric_key)?.setOnPreferenceClickListener {
+ val authEnabled = PreferenceManager.getDefaultSharedPreferences(
+ context ?: return@setOnPreferenceClickListener false
+ )
+ .getBoolean(getString(R.string.biometric_key), false)
+
+ if (authEnabled) {
+ BackupUtils.backup(activity)
+ val title = activity?.getString(R.string.biometric_setting)
+ val warning = activity?.getString(R.string.biometric_warning)
+ activity?.showBottomDialogText(
+ title as String,
+ warning.html()
+ ) { onDialogDismissedEvent }
+ }
+ true
+ }
+
val syncApis =
listOf(
R.string.mal_key to malApi,
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt
index 37c71134..caff5df6 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt
@@ -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,7 +12,6 @@ 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.R
@@ -24,7 +19,12 @@ 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.utils.UIHelper.fixPaddingStatusbar
+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.navigate
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.toPx
@@ -32,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
@@ -52,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(R.id.settings_toolbar)
settingsAppbar?.updateLayoutParams {
@@ -66,7 +62,7 @@ class SettingsFragment : Fragment() {
}
}
fun Fragment?.setToolBarScrollFlags() {
- if (isTvSettings()) {
+ if (isLayout(TV or EMULATOR)) {
val settingsAppbar = this?.view?.findViewById(R.id.settings_toolbar)
settingsAppbar?.updateLayoutParams {
@@ -85,7 +81,7 @@ class SettingsFragment : Fragment() {
activity?.onBackPressedDispatcher?.onBackPressed()
}
}
- fixPaddingStatusbar(settingsToolbar)
+ UIHelper.fixPaddingStatusbar(settingsToolbar)
}
fun Fragment?.setUpToolbar(@StringRes title: Int) {
@@ -100,7 +96,7 @@ class SettingsFragment : Fragment() {
activity?.onBackPressedDispatcher?.onBackPressed()
}
}
- fixPaddingStatusbar(settingsToolbar)
+ UIHelper.fixPaddingStatusbar(settingsToolbar)
}
fun getFolderSize(dir: File): Long {
@@ -116,55 +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
- }
-
- 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()
@@ -189,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
@@ -218,7 +164,7 @@ class SettingsFragment : Fragment() {
setOnClickListener {
navigate(navigationId)
}
- if (isTrueTv) {
+ if (isLayout(TV)) {
isFocusable = true
isFocusableInTouchMode = true
}
@@ -226,9 +172,17 @@ class SettingsFragment : Fragment() {
}
// Default focus on TV
- if (isTrueTv) {
+ if (isLayout(TV)) {
settingsGeneral.requestFocus()
}
}
+
+ val appVersion = getString(R.string.app_version)
+ val commitInfo = getString(R.string.commit_hash)
+
+ binding?.appVersionInfo?.setOnLongClickListener{
+ clipboardHelper(txt(R.string.extension_version), "$appVersion $commitInfo")
+ true
+ }
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt
index abd7fb9a..6cf00375 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt
@@ -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)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt
index 63053236..cc14e761 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt
@@ -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
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt
index 9f72c1d5..fb24c185 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt
@@ -1,10 +1,6 @@
package com.lagradost.cloudstream3.ui.settings
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
import android.os.Bundle
-import android.os.TransactionTooLargeException
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
@@ -20,6 +16,7 @@ import com.lagradost.cloudstream3.databinding.LogcatBinding
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.network.initClient
import com.lagradost.cloudstream3.services.BackupWorkManager
+import com.lagradost.cloudstream3.ui.result.txt
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
@@ -30,6 +27,7 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
+import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.VideoDownloadManager
@@ -117,22 +115,15 @@ class SettingsUpdates : PreferenceFragmentCompat() {
binding.text1.text = text
binding.copyBtt.setOnClickListener {
- // Can crash on too much text
- try {
- val serviceClipboard =
- (activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?)
- ?: return@setOnClickListener
- val clip = ClipData.newPlainText("logcat", text)
- serviceClipboard.setPrimaryClip(clip)
- dialog.dismissSafe(activity)
- } catch (e: TransactionTooLargeException) {
- showToast(R.string.clipboard_too_large)
- }
+ clipboardHelper(txt("Logcat"), text)
+ dialog.dismissSafe(activity)
}
+
binding.clearBtt.setOnClickListener {
Runtime.getRuntime().exec("logcat -c")
dialog.dismissSafe(activity)
}
+
binding.saveBtt.setOnClickListener {
var fileStream: OutputStream? = null
try {
@@ -153,9 +144,11 @@ class SettingsUpdates : PreferenceFragmentCompat() {
fileStream?.closeQuietly()
}
}
+
binding.closeBtt.setOnClickListener {
dialog.dismissSafe(activity)
}
+
return@setOnPreferenceClickListener true
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt
index f0b8a0bd..ebd3260f 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt
@@ -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
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt
index c3fb4fc2..04da30c7 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt
@@ -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 = 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(
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt
index c5256ffa..acfbc584 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt
@@ -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)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt
index 7ac7cbb2..faf6d38b 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt
@@ -1,22 +1,18 @@
package com.lagradost.cloudstream3.ui.settings.extensions
-import android.content.ClipData
-import android.content.ClipboardManager
-import android.content.Context
-import android.os.Build
import android.view.LayoutInflater
import android.view.ViewGroup
-import android.widget.Toast
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
-import com.lagradost.cloudstream3.CommonActivity.activity
-import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
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.settings.SettingsFragment.Companion.isTrueTvSettings
+import com.lagradost.cloudstream3.ui.result.txt
+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(
val isSetup: Boolean,
@@ -28,7 +24,7 @@ class RepoAdapter(
private val repositories: MutableList = 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
@@ -121,13 +117,9 @@ class RepoAdapter(
}
repositoryItemRoot.setOnLongClickListener {
- val clipboardManager =
- activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?
- clipboardManager?.setPrimaryClip(ClipData.newPlainText("RepoUrl", repositoryData.url))
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
- showToast(R.string.copyRepoUrl, Toast.LENGTH_SHORT)
- }
- return@setOnLongClickListener true
+ val shareableRepoData = "${repositoryData.name} : \n ${repositoryData.url}"
+ clipboardHelper(txt(R.string.repo_copy_label), shareableRepoData)
+ true
}
mainText.text = repositoryData.name
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt
index 3fbd1131..7878afaa 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt
@@ -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)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt
index 71fac2ed..bb9558b8 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt
@@ -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
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt
index 83521873..1466afed 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt
@@ -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
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt
index 1be966b6..ff27b192 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt
@@ -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()
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt
index e50131fe..87d17a2b 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt
@@ -32,7 +32,6 @@ import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getDefaultSharedPrefs
import com.lagradost.cloudstream3.utils.DataStore.getSharedPrefs
import com.lagradost.cloudstream3.utils.DataStore.mapper
-import com.lagradost.cloudstream3.utils.DataStore.setKeyRaw
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
import com.lagradost.cloudstream3.utils.VideoDownloadManager.setupStream
@@ -65,12 +64,16 @@ object BackupUtils {
PLUGINS_KEY_LOCAL,
OPEN_SUBTITLES_USER_KEY,
- "nginx_user", // Nginx user key
+
+ DOWNLOAD_EPISODE_CACHE,
+
+ "biometric_key", // can lock down users if backup is shared on a incompatible device
+ "nginx_user" // Nginx user key
)
- /** false if blacklisted key */
+ /** false if key should not be contained in backup */
private fun String.isTransferable(): Boolean {
- return !nonTransferableKeys.contains(this)
+ return !nonTransferableKeys.any { this.contains(it) }
}
private var restoreFileSelector: ActivityResultLauncher>? = null
@@ -252,8 +255,12 @@ object BackupUtils {
map: Map?,
isEditingAppSettings: Boolean = false
) {
- map?.filter { it.key.isTransferable() }?.forEach {
- setKeyRaw(it.key, it.value, isEditingAppSettings)
+ val editor = DataStore.editor(this, isEditingAppSettings)
+ map?.forEach {
+ if (it.key.isTransferable()) {
+ editor.setKeyRaw(it.key, it.value)
+ }
}
+ editor.apply()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt
new file mode 100644
index 00000000..de9b9963
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt
@@ -0,0 +1,177 @@
+package com.lagradost.cloudstream3.utils
+
+import android.app.Activity
+import android.app.KeyguardManager
+import android.content.Context
+import android.os.Build
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import androidx.biometric.BiometricManager
+import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
+import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
+import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
+import androidx.biometric.BiometricPrompt
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.FragmentActivity
+import com.lagradost.cloudstream3.CommonActivity.showToast
+import com.lagradost.cloudstream3.R
+
+object BiometricAuthenticator {
+
+ private const val MAX_FAILED_ATTEMPTS = 3
+ private var failedAttempts = 0
+ const val TAG = "cs3Auth"
+
+ private var biometricManager: BiometricManager? = null
+ var biometricPrompt: BiometricPrompt? = null
+ var promptInfo: BiometricPrompt.PromptInfo? = null
+
+ var authCallback: BiometricAuthCallback? = null // listen to authentication success
+
+ private fun initializeBiometrics(activity: Activity) {
+ val executor = ContextCompat.getMainExecutor(activity)
+
+ biometricManager = BiometricManager.from(activity)
+
+ biometricPrompt = BiometricPrompt(
+ activity as FragmentActivity,
+ executor,
+ object : BiometricPrompt.AuthenticationCallback() {
+
+ override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
+ super.onAuthenticationError(errorCode, errString)
+ showToast("$errString")
+ Log.e(TAG, "$errorCode")
+ failedAttempts++
+
+ if (failedAttempts >= MAX_FAILED_ATTEMPTS) {
+ failedAttempts = 0
+ activity.finish()
+ } else {
+ failedAttempts = 0
+ activity.finish()
+ }
+ }
+
+ override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
+ super.onAuthenticationSucceeded(result)
+ failedAttempts = 0
+ authCallback?.onAuthenticationSuccess()
+ }
+
+ override fun onAuthenticationFailed() {
+ super.onAuthenticationFailed()
+ failedAttempts++
+ if (failedAttempts >= MAX_FAILED_ATTEMPTS) {
+ failedAttempts = 0
+ activity.finish()
+ }
+ }
+ })
+ }
+
+ @Suppress("DEPRECATION")
+ // authentication dialog prompt builder
+ private fun authenticationDialog(
+ activity: Activity,
+ title: Int,
+ setDeviceCred: Boolean,
+ ) {
+ val description = activity.getString(R.string.biometric_prompt_description)
+
+ if (setDeviceCred) {
+ // For API level > 30, Newer API setAllowedAuthenticators is used
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+
+ val authFlag = DEVICE_CREDENTIAL or BIOMETRIC_WEAK or BIOMETRIC_STRONG
+ promptInfo = BiometricPrompt.PromptInfo.Builder()
+ .setTitle(activity.getString(title))
+ .setDescription(description)
+ .setAllowedAuthenticators(authFlag)
+ .build()
+
+ } else {
+ // for apis < 30
+ promptInfo = BiometricPrompt.PromptInfo.Builder()
+ .setTitle(activity.getString(title))
+ .setDescription(description)
+ .setDeviceCredentialAllowed(true)
+ .build()
+ }
+
+ } else {
+ // fallback for A12+ when both fingerprint & Face unlock is absent but PIN is set
+ promptInfo = BiometricPrompt.PromptInfo.Builder()
+ .setTitle(activity.getString(title))
+ .setDescription(description)
+ .setDeviceCredentialAllowed(true)
+ .build()
+ }
+ }
+
+ private fun isBiometricHardWareAvailable(): Boolean {
+ // authentication occurs only when this is true and device is truly capable
+ var result = false
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+
+ when (biometricManager?.canAuthenticate(
+ DEVICE_CREDENTIAL or BIOMETRIC_STRONG or BIOMETRIC_WEAK
+ )) {
+ BiometricManager.BIOMETRIC_SUCCESS -> result = true
+ BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false
+ BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false
+ BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false
+ BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true
+ BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true
+ BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false
+ }
+
+ } else {
+ @Suppress("DEPRECATION")
+ when (biometricManager?.canAuthenticate()) {
+ BiometricManager.BIOMETRIC_SUCCESS -> result = true
+ BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false
+ BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false
+ BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false
+ BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true
+ BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true
+ BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false
+ }
+ }
+
+ return result
+ }
+
+ // checks if device is secured i.e has at least some type of lock
+ fun deviceHasPasswordPinLock(context: Context?): Boolean {
+ val keyMgr =
+ context?.getSystemService(AppCompatActivity.KEYGUARD_SERVICE) as? KeyguardManager
+ return keyMgr?.isKeyguardSecure ?: false
+ }
+
+ // function to start authentication in any fragment or activity
+ fun startBiometricAuthentication(activity: Activity, title: Int, setDeviceCred: Boolean) {
+ initializeBiometrics(activity)
+
+ if (isBiometricHardWareAvailable()) {
+ authCallback = activity as? BiometricAuthCallback
+ authenticationDialog(activity, title, setDeviceCred)
+ promptInfo?.let { biometricPrompt?.authenticate(it) }
+
+ } else {
+ if (deviceHasPasswordPinLock(activity)) {
+ authCallback = activity as? BiometricAuthCallback
+ authenticationDialog(activity, R.string.password_pin_authentication_title, true)
+ promptInfo?.let { biometricPrompt?.authenticate(it) }
+
+ } else {
+ showToast(R.string.biometric_unsupported)
+ }
+ }
+ }
+
+ interface BiometricAuthCallback {
+ fun onAuthenticationSuccess()
+ }
+}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt
index 01a64d7d..19c817b9 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt
@@ -50,6 +50,28 @@ class PreferenceDelegate(
}
}
+/** When inserting many keys use this function, this is because apply for every key is very expensive on memory */
+data class Editor(
+ val editor : SharedPreferences.Editor
+) {
+ /** Always remember to call apply after */
+ fun setKeyRaw(path: String, value: T) {
+ when (value) {
+ is Boolean -> editor.putBoolean(path, value)
+ is Int -> editor.putInt(path, value)
+ is String -> editor.putString(path, value)
+ is Float -> editor.putFloat(path, value)
+ is Long -> editor.putLong(path, value)
+ (value as? Set != null) -> editor.putStringSet(path, value as Set)
+ }
+ }
+
+ fun apply() {
+ editor.apply()
+ System.gc()
+ }
+}
+
object DataStore {
val mapper: JsonMapper = JsonMapper.builder().addModule(kotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
@@ -66,22 +88,10 @@ object DataStore {
return "${folder}/${path}"
}
- fun Context.setKeyRaw(path: String, value: T, isEditingAppSettings: Boolean = false) {
- try {
- val editor: SharedPreferences.Editor =
- if (isEditingAppSettings) getDefaultSharedPrefs().edit() else getSharedPrefs().edit()
- when (value) {
- is Boolean -> editor.putBoolean(path, value)
- is Int -> editor.putInt(path, value)
- is String -> editor.putString(path, value)
- is Float -> editor.putFloat(path, value)
- is Long -> editor.putLong(path, value)
- (value as? Set != null) -> editor.putStringSet(path, value as Set)
- }
- editor.apply()
- } catch (e: Exception) {
- logError(e)
- }
+ fun editor(context : Context, isEditingAppSettings: Boolean = false) : Editor {
+ val editor: SharedPreferences.Editor =
+ if (isEditingAppSettings) context.getDefaultSharedPrefs().edit() else context.getSharedPrefs().edit()
+ return Editor(editor)
}
fun Context.getDefaultSharedPrefs(): SharedPreferences {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt
index f34e7238..70edf80c 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt
@@ -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)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt
index 76142f72..eedb626a 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt
@@ -5,6 +5,8 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.app.AppOpsManager
import android.app.Dialog
+import android.content.ClipData
+import android.content.ClipboardManager
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Configuration
@@ -14,12 +16,15 @@ import android.graphics.Color
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
+import android.os.TransactionTooLargeException
+import android.util.Log
import android.view.*
import android.view.ViewGroup.MarginLayoutParams
import android.view.inputmethod.InputMethodManager
import android.widget.ImageView
import android.widget.ListAdapter
import android.widget.ListView
+import android.widget.Toast.LENGTH_LONG
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
@@ -30,14 +35,12 @@ import androidx.appcompat.view.menu.MenuBuilder
import androidx.appcompat.widget.PopupMenu
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
+import androidx.core.content.getSystemService
import androidx.core.graphics.alpha
import androidx.core.graphics.blue
import androidx.core.graphics.drawable.toBitmapOrNull
import androidx.core.graphics.green
import androidx.core.graphics.red
-import androidx.core.view.WindowCompat
-import androidx.core.view.WindowInsetsCompat
-import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.marginBottom
import androidx.core.view.marginLeft
import androidx.core.view.marginRight
@@ -58,17 +61,20 @@ import com.bumptech.glide.request.target.Target
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable
import com.google.android.material.chip.ChipGroup
+import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.CommonActivity.activity
-import com.lagradost.cloudstream3.MainActivity
+import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.result.UiImage
-import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
-import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
+import com.lagradost.cloudstream3.ui.result.UiText
+import com.lagradost.cloudstream3.ui.result.txt
+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
-
object UIHelper {
val Int.toPx: Int get() = (this * Resources.getSystem().displayMetrics.density).toInt()
val Float.toPx: Float get() = (this * Resources.getSystem().displayMetrics.density)
@@ -123,6 +129,35 @@ object UIHelper {
)
}
+ fun clipboardHelper(label: UiText, text: CharSequence) {
+ val ctx = context ?: return
+ try {
+ ctx.let {
+ val clip = ClipData.newPlainText(label.asString(ctx), text)
+ val labelSuffix = txt(R.string.toast_copied).asString(ctx)
+ ctx.getSystemService()?.setPrimaryClip(clip)
+
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
+ showToast("${label.asString(ctx)} $labelSuffix")
+ }
+ }
+ } catch (t: Throwable) {
+ Log.e("ClipboardService", "$t")
+ when (t) {
+ is SecurityException -> {
+ showToast(R.string.clipboard_permission_error)
+ }
+
+ is TransactionTooLargeException -> {
+ showToast(R.string.clipboard_too_large)
+ }
+
+ else -> {
+ showToast(R.string.clipboard_unknown_error, LENGTH_LONG)
+ }
+ }
+ }
+ }
/**
* Sets ListView height dynamically based on the height of the items.
@@ -434,7 +469,7 @@ object UIHelper {
}
fun Context.getStatusBarHeight(): Int {
- if (isTvSettings()) {
+ if (isLayout(Globals.TV or EMULATOR)) {
return 0
}
@@ -532,12 +567,11 @@ object UIHelper {
WindowInsetsControllerCompat(window, View(this)).show(WindowInsetsCompat.Type.systemBars())
} else {*/ /** WINDOW COMPAT IS BUGGY DUE TO FU*KED UP PLAYER AND TRAILERS **/
- Suppress("DEPRECATION")
window.decorView.systemUiVisibility =
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
//}
- changeStatusBarState(isEmulatorSettings())
+ changeStatusBarState(isLayout(EMULATOR))
}
fun Context.shouldShowPIPMode(isInPlayer: Boolean): Boolean {
diff --git a/app/src/main/res/drawable/ic_baseline_film_roll_24.xml b/app/src/main/res/drawable/ic_baseline_film_roll_24.xml
new file mode 100644
index 00000000..941d936f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_film_roll_24.xml
@@ -0,0 +1,10 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_resume_arrow.xml b/app/src/main/res/drawable/ic_baseline_resume_arrow.xml
new file mode 100644
index 00000000..0326fbd4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_resume_arrow.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_resume_arrow2.xml b/app/src/main/res/drawable/ic_baseline_resume_arrow2.xml
new file mode 100644
index 00000000..fc533a0e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_resume_arrow2.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_fingerprint.xml b/app/src/main/res/drawable/ic_fingerprint.xml
new file mode 100644
index 00000000..5c96e5a5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_fingerprint.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/library_icon.xml b/app/src/main/res/drawable/library_icon.xml
new file mode 100644
index 00000000..f62dceac
--- /dev/null
+++ b/app/src/main/res/drawable/library_icon.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/outline_bookmark_add_24.xml b/app/src/main/res/drawable/outline_bookmark_add_24.xml
new file mode 100644
index 00000000..a4e18af3
--- /dev/null
+++ b/app/src/main/res/drawable/outline_bookmark_add_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/player_button_tv_attr.xml b/app/src/main/res/drawable/player_button_tv_attr.xml
index 4c90a64e..ed83887d 100644
--- a/app/src/main/res/drawable/player_button_tv_attr.xml
+++ b/app/src/main/res/drawable/player_button_tv_attr.xml
@@ -3,13 +3,13 @@
-
-
+
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/player_button_tv_attr_no_bg.xml b/app/src/main/res/drawable/player_button_tv_attr_no_bg.xml
index b9b927da..0dd8c256 100644
--- a/app/src/main/res/drawable/player_button_tv_attr_no_bg.xml
+++ b/app/src/main/res/drawable/player_button_tv_attr_no_bg.xml
@@ -3,7 +3,7 @@
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/bottom_resultview_preview.xml b/app/src/main/res/layout/bottom_resultview_preview.xml
index 4a64114e..3372fe7b 100644
--- a/app/src/main/res/layout/bottom_resultview_preview.xml
+++ b/app/src/main/res/layout/bottom_resultview_preview.xml
@@ -41,22 +41,34 @@
android:layout_marginStart="10dp"
android:orientation="vertical">
-
+ tools:text="The Perfect Run" />
-
+
-
+
+
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="7dp">
+ tools:visibility="visible" />
+ tools:visibility="visible" />
diff --git a/app/src/main/res/layout/cast_item.xml b/app/src/main/res/layout/cast_item.xml
index f164384b..99a9750b 100644
--- a/app/src/main/res/layout/cast_item.xml
+++ b/app/src/main/res/layout/cast_item.xml
@@ -17,7 +17,6 @@
android:layout_width="100dp"
android:layout_height="wrap_content"
android:orientation="vertical"
- android:focusable="true"
android:padding="5dp">
diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml
index 70461518..22d2e52f 100644
--- a/app/src/main/res/layout/fragment_result.xml
+++ b/app/src/main/res/layout/fragment_result.xml
@@ -409,10 +409,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
+ android:maxLines="10"
android:foreground="@drawable/outline_drawable"
- android:maxLength="1000"
android:nextFocusUp="@id/result_back"
- android:nextFocusDown="@id/result_bookmark_button"
+ android:nextFocusDown="@id/result_bookmark_Button"
android:paddingTop="5dp"
android:textColor="?attr/textColor"
android:textSize="15sp"
@@ -474,7 +474,7 @@
android:fadingEdge="horizontal"
android:focusable="false"
android:focusableInTouchMode="false"
- android:nextFocusUp="@id/result_bookmark_button"
+ android:nextFocusUp="@id/result_bookmark_Button"
android:nextFocusDown="@id/result_play_movie"
android:orientation="horizontal"
android:paddingTop="5dp"
@@ -580,7 +580,7 @@
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="10dp"
- android:nextFocusUp="@id/result_bookmark_button"
+ android:nextFocusUp="@id/result_bookmark_Button"
android:nextFocusDown="@id/result_download_movie"
android:text="@string/play_movie_button"
android:visibility="visible"
@@ -658,7 +658,7 @@
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="10dp"
- android:nextFocusUp="@id/result_bookmark_button"
+ android:nextFocusUp="@id/result_bookmark_Button"
android:nextFocusDown="@id/result_download_movie"
android:text="@string/resume"
android:visibility="visible"
@@ -674,7 +674,7 @@
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="10dp"
- android:nextFocusUp="@id/result_bookmark_button"
+ android:nextFocusUp="@id/result_bookmark_Button"
android:nextFocusDown="@id/result_download_movie"
android:text="@string/next_episode"
android:visibility="gone"
diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml
index a7ba4334..2ec2ae0a 100644
--- a/app/src/main/res/layout/fragment_result_tv.xml
+++ b/app/src/main/res/layout/fragment_result_tv.xml
@@ -78,6 +78,30 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+ android:layout_marginTop="225dp">
@@ -221,157 +220,293 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
android:textStyle="normal"
tools:text="5d 3h 30m" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_marginTop="10dp"
+ android:baselineAligned="false">
+ app:layout_constraintTop_toTopOf="parent"
+ tools:ignore="UselessParent">
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -513,10 +598,8 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
android:descendantFocusability="afterDescendants"
android:fadingEdge="horizontal"
- android:focusable="false"
- android:focusableInTouchMode="false"
- android:nextFocusUp="@id/result_episodes_show"
- android:nextFocusDown="@id/result_recommendations_filter_selection"
+ android:nextFocusUp="@id/result_description"
+ android:nextFocusDown="@id/result_recommendations_list"
android:orientation="horizontal"
android:paddingTop="5dp"
android:requiresFadingEdge="horizontal"
@@ -525,8 +608,23 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
tools:listitem="@layout/cast_item"
tools:visibility="visible" />
+
+
@@ -576,7 +674,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
android:layout_height="match_parent"
android:layout_gravity="end"
android:visibility="gone"
- tools:visibility="visible">
+ tools:visibility="invisible">
+
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/main_settings.xml b/app/src/main/res/layout/main_settings.xml
index 387f98fa..53db7e47 100644
--- a/app/src/main/res/layout/main_settings.xml
+++ b/app/src/main/res/layout/main_settings.xml
@@ -105,9 +105,10 @@
android:text="@string/extensions" />
- Banana
- Fiesta
- Dolor rosa
+ - Lavanda
- Material You
- Material You (Secondary)
@@ -235,6 +236,7 @@
- Banana
- Party
- Pink
+ - Lavender
- Monet
- Monet2
diff --git a/app/src/main/res/values-pl/array.xml b/app/src/main/res/values-pl/array.xml
index 8384187f..9f76f423 100644
--- a/app/src/main/res/values-pl/array.xml
+++ b/app/src/main/res/values-pl/array.xml
@@ -221,6 +221,7 @@
- Bananowy
- Łososiowy
- Świnko peppowy
+ - Lawenda
- Material You
- Material You (drugorzędny)
@@ -244,6 +245,7 @@
- Banana
- Party
- Pink
+ - Lavender
- Monet
- Monet2
diff --git a/app/src/main/res/values-tr/array.xml b/app/src/main/res/values-tr/array.xml
index d14a3e2a..5c723f72 100644
--- a/app/src/main/res/values-tr/array.xml
+++ b/app/src/main/res/values-tr/array.xml
@@ -247,6 +247,7 @@
- Muz
- Parti
- Pembe
+ - Lavanta
- Material You
- Material You (İkincil)
@@ -270,6 +271,7 @@
- Banana
- Party
- Pink
+ - Lavender
- Monet
- Monet2
diff --git a/app/src/main/res/values-vi/array.xml b/app/src/main/res/values-vi/array.xml
index d32f37ce..aac94100 100644
--- a/app/src/main/res/values-vi/array.xml
+++ b/app/src/main/res/values-vi/array.xml
@@ -213,6 +213,7 @@
- Vàng
- Hồng
- Hồng đậm
+ - Hoa oải hương
- Material You
- Material You (Secondary)
@@ -236,6 +237,7 @@
- Banana
- Party
- Pink
+ - Lavender
- Monet
- Monet2
diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml
index e38dd5c9..3be12510 100644
--- a/app/src/main/res/values/array.xml
+++ b/app/src/main/res/values/array.xml
@@ -284,6 +284,7 @@
- Banana
- Party
- Pink Pain
+ - Lavender
- Material You
- Material You (Secondary)
@@ -307,6 +308,7 @@
- Banana
- Party
- Pink
+ - Lavender
- Monet
- Monet2
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index c2c84d0d..7c9ccebe 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -84,6 +84,7 @@
#CE8500
#F5BB00
#408cac
+ #6F55AF
#48E484
#ea596e
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 14bb9552..b5dae57b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -69,6 +69,7 @@
enable_skip_op_from_database
rotate_video_key
auto_rotate_video_key
+ biometric_key
%d %s | %s
%s • %s
@@ -173,8 +174,8 @@
Close
Clear
Save
- Title copied!
- Repo URL copied!
+ Repository name and URL
+ copied!
New episode notification
Search in other extensions
Show recommendations
@@ -247,7 +248,7 @@
Error backing up %s
Search
Library
- Accounts
+ Accounts and Security
Updates and backup
Info
Advanced Search
@@ -306,6 +307,7 @@
+30
This will permanently delete %s\nAre you sure?
%dm\nremaining
+ %s\nremaining
Ongoing
Completed
Status
@@ -645,6 +647,8 @@
History
Show skip popups for opening/ending
Too much text. Unable to save to clipboard.
+ Error accessing Clipboard, Please try again.
+ Error copying, Please copy logcat and contact app support.
Mark as watched
Remove from watched
Are you sure you want to exit\?
@@ -745,4 +749,17 @@
Display a toggle button for screen orientation
Enable automatic switching of screen orientation based on video orientation
Auto rotate
+ Favorite
+ Unfavorite
+
+ Unlock CloudStream
+ Lock with Biometrics
+ Password/PIN Authentication
+ Biometric authentication is not supported on this device
+ Unlock the app with Fingerprint, Face ID, PIN, Pattern and Password.
+ This window will close after few failed attempts. You\'ll have to restart the App.
+ Your CloudStream data has been backed up now, although probability of this rare case is very low but all
+ devices behave differently, in case you get locked down from accessing the app in worst case scenario,
+ Clear the app data wholly and restore the backup. Any inconvenience if arrived is deeply regretted.
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 2fa4eb41..0a693769 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -383,6 +383,16 @@
- @color/colorPrimaryCoolBlue
+
+
+
+
+
+