mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Merge remote-tracking branch 'origin/master' into agp
This commit is contained in:
commit
ef6d270ac2
79 changed files with 1845 additions and 1204 deletions
|
@ -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
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" /> <!-- Used for Android TV watch next -->
|
||||
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" /> <!-- Used for updates without prompt -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- Used for update service -->
|
||||
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
|
||||
<!-- Required for getting arbitrary Aniyomi packages -->
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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<KeyEvent?, Boolean>) -> 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<View>(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
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<Boolean>()
|
||||
|
||||
/**
|
||||
* Used by DataStoreHelper to fully reload home when switching accounts
|
||||
*/
|
||||
val reloadHomeEvent = Event<Boolean>()
|
||||
|
||||
/**
|
||||
* Used by DataStoreHelper to fully reload library when switching accounts
|
||||
*/
|
||||
|
@ -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 -> {
|
||||
|
@ -783,6 +792,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
|
||||
lateinit var viewModel: ResultViewModel2
|
||||
lateinit var syncViewModel: SyncViewModel
|
||||
|
||||
/** kinda dirty, however it signals that we should use the watch status as sync or not*/
|
||||
var isLocalList: Boolean = false
|
||||
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
|
||||
|
@ -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<Boolean>(getString(R.string.jsdelivr_proxy_key)) == null && isNetworkAvailable()) {
|
||||
|
@ -1322,12 +1368,41 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
}
|
||||
}
|
||||
|
||||
observe(viewModel.watchStatus) { state ->
|
||||
setWatchStatus(state)
|
||||
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
|
||||
}
|
||||
observe(syncViewModel.userData) { status ->
|
||||
setUserData(status)
|
||||
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) {
|
||||
|
@ -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(),
|
||||
|
@ -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() {
|
||||
|
|
|
@ -49,11 +49,15 @@ inline fun debugWarning(assert: () -> Boolean, message: () -> String) {
|
|||
}
|
||||
}
|
||||
|
||||
/** NOTE: Only one observer at a time per value */
|
||||
fun <T> LifecycleOwner.observe(liveData: LiveData<T>, 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 <T> LifecycleOwner.observeNullable(liveData: LiveData<T>, action: (t: T) -> Unit) {
|
||||
liveData.removeObservers(this)
|
||||
liveData.observe(this) { action(it) }
|
||||
}
|
||||
|
||||
|
|
|
@ -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,7 +98,9 @@ class SubscriptionWorkManager(val context: Context, workerParams: WorkerParamete
|
|||
)
|
||||
}
|
||||
|
||||
@SuppressLint("UnspecifiedImmutableFlag")
|
||||
override suspend fun doWork(): Result {
|
||||
try {
|
||||
// println("Update subscriptions!")
|
||||
context.createNotificationChannel(
|
||||
SUBSCRIPTION_CHANNEL_ID,
|
||||
|
@ -215,10 +218,18 @@ class SubscriptionWorkManager(val context: Context, workerParams: WorkerParamete
|
|||
|
||||
// You can probably get some issues here since this is async but it does not matter much.
|
||||
updateProgress(max, ++progress, false)
|
||||
} catch (_: Throwable) {
|
||||
} 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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,7 +8,8 @@ import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
|||
import com.lagradost.cloudstream3.ui.WatchType
|
||||
import com.lagradost.cloudstream3.ui.library.ListSorting
|
||||
import com.lagradost.cloudstream3.ui.result.txt
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions
|
||||
|
@ -71,9 +72,9 @@ class LocalList : SyncAPI {
|
|||
}?.distinctBy { it.first } ?: return null
|
||||
|
||||
val list = ioWork {
|
||||
val isTrueTv = isTrueTvSettings()
|
||||
val isTrueTv = isLayout(TV)
|
||||
|
||||
val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate {
|
||||
val baseMap = WatchType.entries.filter { it != WatchType.NONE }.associate {
|
||||
// None is not something to display
|
||||
it.stringRes to emptyList<SyncAPI.LibraryItem>()
|
||||
} + mapOf(
|
||||
|
|
|
@ -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,
|
||||
|
@ -859,8 +870,10 @@ 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 {
|
||||
.status(
|
||||
status.status.internalId,
|
||||
(status as? SimklSyncStatus)?.oldStatus?.let { oldStatus ->
|
||||
SimklListStatusType.entries.firstOrNull {
|
||||
it.originalName == oldStatus
|
||||
}?.value
|
||||
})
|
||||
|
@ -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<SyncAPI.LibraryItem>()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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,14 +244,8 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
}
|
||||
}*/
|
||||
|
||||
/** 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 {
|
||||
@MainThread
|
||||
private fun setStatusInternal(status : DownloadStatusTell?) {
|
||||
val isPreActive = isZeroBytes && status == DownloadStatusTell.IsDownloading
|
||||
if (animateWaiting && (status == DownloadStatusTell.IsPending || isPreActive)) {
|
||||
val animation = AnimationUtils.loadAnimation(context, waitingAnimation)
|
||||
|
@ -276,6 +273,26 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
progressBarBackground.isGone = hide
|
||||
progressBar.isGone = hide
|
||||
}
|
||||
|
||||
/** Also sets currentStatus */
|
||||
override fun setStatus(status: DownloadStatusTell?) {
|
||||
currentStatus = status
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
progressBarBackground.post {
|
||||
setStatusInternal(status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun resetView() {
|
||||
|
|
|
@ -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(
|
||||
homeMasterAdapter = HomeParentItemAdapterPreview(
|
||||
mutableListOf(),
|
||||
homeViewModel
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.lagradost.cloudstream3.ui.home
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -7,16 +9,20 @@ import android.widget.TextView
|
|||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListUpdateCallback
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import com.lagradost.cloudstream3.HomePageList
|
||||
import com.lagradost.cloudstream3.LoadResponse
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.databinding.HomepageParentBinding
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
||||
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable
|
||||
|
||||
class LoadClickCallback(
|
||||
|
@ -32,18 +38,90 @@ open class ParentItemAdapter(
|
|||
private val clickCallback: (SearchClickCallback) -> Unit,
|
||||
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
|
||||
private val expandCallback: ((String) -> Unit)? = null,
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
) : RecyclerView.Adapter<ViewHolder>() {
|
||||
// Ok, this is fucked, but there is a reason for this as we want to resume 1. when scrolling up and down
|
||||
// and 2. when doing into a thing and coming back. 1 is always active, but 2 requires doing it in the fragment
|
||||
// as OnCreateView is called and this adapter is recreated losing the internal state to the GC
|
||||
//
|
||||
// 1. This works by having the adapter having a internal state "scrollStates" that keeps track of the states
|
||||
// when a view recycles, it looks up this internal state
|
||||
// 2. To solve the the coming back shit we have to save "scrollStates" to a Bundle inside the
|
||||
// fragment via onSaveInstanceState, because this cant be easy for some reason as the adapter does
|
||||
// not have a state but the layout-manager for no reason, then it is resumed via onRestoreInstanceState
|
||||
//
|
||||
// Even when looking at a real example they do this :skull:
|
||||
// https://github.com/vivchar/RendererRecyclerViewAdapter/blob/185251ee9d94fb6eb3e063b00d646b745186c365/example/src/main/java/com/github/vivchar/example/pages/github/GithubFragment.kt#L32
|
||||
private val scrollStates = mutableMapOf<Int, Parcelable?>()
|
||||
|
||||
companion object {
|
||||
private const val SCROLL_KEY: String = "ParentItemAdapter::scrollStates.keys"
|
||||
private const val SCROLL_VALUE: String = "ParentItemAdapter::scrollStates.values"
|
||||
}
|
||||
|
||||
open fun onRestoreInstanceState(savedInstanceState: Bundle?) {
|
||||
try {
|
||||
val keys = savedInstanceState?.getIntArray(SCROLL_KEY) ?: intArrayOf()
|
||||
val values = savedInstanceState?.getParcelableArray(SCROLL_VALUE) ?: arrayOf()
|
||||
for ((k, v) in keys.zip(values)) {
|
||||
this.scrollStates[k] = v
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
logError(t)
|
||||
}
|
||||
}
|
||||
|
||||
open fun onSaveInstanceState(outState: Bundle, recyclerView: RecyclerView? = null) {
|
||||
if (recyclerView != null) {
|
||||
for (position in items.indices) {
|
||||
val holder = recyclerView.findViewHolderForAdapterPosition(position) ?: continue
|
||||
saveHolder(holder)
|
||||
}
|
||||
}
|
||||
|
||||
outState.putIntArray(SCROLL_KEY, scrollStates.keys.toIntArray())
|
||||
outState.putParcelableArray(SCROLL_VALUE, scrollStates.values.toTypedArray())
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
when (holder) {
|
||||
is ParentViewHolder -> {
|
||||
holder.bind(items[position])
|
||||
scrollStates[holder.absoluteAdapterPosition]?.let {
|
||||
holder.binding.homeChildRecyclerview.layoutManager?.onRestoreInstanceState(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveHolder(holder : ViewHolder) {
|
||||
when (holder) {
|
||||
is ParentViewHolder -> {
|
||||
scrollStates[holder.absoluteAdapterPosition] =
|
||||
holder.binding.homeChildRecyclerview.layoutManager?.onSaveInstanceState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewRecycled(holder: ViewHolder) {
|
||||
saveHolder(holder)
|
||||
super.onViewRecycled(holder)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val layoutResId = when {
|
||||
isTrueTvSettings() -> R.layout.homepage_parent_tv
|
||||
parent.context.isEmulatorSettings() -> R.layout.homepage_parent_emulator
|
||||
isLayout(TV) -> R.layout.homepage_parent_tv
|
||||
isLayout(EMULATOR) -> R.layout.homepage_parent_emulator
|
||||
else -> R.layout.homepage_parent
|
||||
}
|
||||
|
||||
val root = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false)
|
||||
|
||||
val binding = HomepageParentBinding.bind(root)
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val binding = try {
|
||||
HomepageParentBinding.bind(inflater.inflate(layoutResId, parent, false))
|
||||
} catch (t : Throwable) {
|
||||
logError(t)
|
||||
// just in case someone forgot we don't want to crash
|
||||
HomepageParentBinding.inflate(inflater)
|
||||
}
|
||||
|
||||
return ParentViewHolder(
|
||||
binding,
|
||||
|
@ -53,14 +131,6 @@ open class ParentItemAdapter(
|
|||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
when (holder) {
|
||||
is ParentViewHolder -> {
|
||||
holder.bind(items[position])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
|
@ -116,7 +186,6 @@ open class ParentItemAdapter(
|
|||
}
|
||||
|
||||
override fun onChanged(_position: Int, count: Int, payload: Any?) {
|
||||
|
||||
val position = _position + delta
|
||||
|
||||
// I know kinda messy, what this does is using the update or bind instead of onCreateViewHolder -> bind
|
||||
|
@ -155,15 +224,15 @@ open class ParentItemAdapter(
|
|||
//diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
class ParentViewHolder
|
||||
constructor(
|
||||
|
||||
class ParentViewHolder(
|
||||
val binding: HomepageParentBinding,
|
||||
// val viewModel: HomeViewModel,
|
||||
private val clickCallback: (SearchClickCallback) -> Unit,
|
||||
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
|
||||
private val expandCallback: ((String) -> Unit)? = null,
|
||||
) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
ViewHolder(binding.root) {
|
||||
val title: TextView = binding.homeChildMoreInfo
|
||||
private val recyclerView: RecyclerView = binding.homeChildRecyclerview
|
||||
private val startFocus = R.id.nav_rail_view
|
||||
|
@ -237,7 +306,7 @@ open class ParentItemAdapter(
|
|||
})
|
||||
|
||||
//(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
|
||||
if (!isTrueTvSettings()) {
|
||||
if (isLayout(PHONE)) {
|
||||
title.setOnClickListener {
|
||||
moreInfoClickCallback.invoke(expand)
|
||||
}
|
||||
|
|
|
@ -36,8 +36,9 @@ import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
|||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA
|
||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes
|
||||
|
@ -48,7 +49,8 @@ import com.lagradost.cloudstream3.utils.UIHelper.populateChips
|
|||
class HomeParentItemAdapterPreview(
|
||||
items: MutableList<HomeViewModel.ExpandableHomepageList>,
|
||||
private val viewModel: HomeViewModel,
|
||||
) : ParentItemAdapter(items, clickCallback = {
|
||||
) : ParentItemAdapter(items,
|
||||
clickCallback = {
|
||||
viewModel.click(it)
|
||||
}, moreInfoClickCallback = {
|
||||
viewModel.popup(it)
|
||||
|
@ -78,13 +80,13 @@ class HomeParentItemAdapterPreview(
|
|||
return when (viewType) {
|
||||
VIEW_TYPE_HEADER -> {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val binding = if (isTvSettings()) FragmentHomeHeadTvBinding.inflate(
|
||||
val binding = if (isLayout(TV or EMULATOR)) FragmentHomeHeadTvBinding.inflate(
|
||||
inflater,
|
||||
parent,
|
||||
false
|
||||
) else FragmentHomeHeadBinding.inflate(inflater, parent, false)
|
||||
|
||||
if (binding is FragmentHomeHeadTvBinding && parent.context.isEmulatorSettings()) {
|
||||
if (binding is FragmentHomeHeadTvBinding && isLayout(EMULATOR)) {
|
||||
binding.homeBookmarkParentItemMoreInfo.isVisible = true
|
||||
|
||||
val marginInDp = 50
|
||||
|
@ -598,7 +600,7 @@ class HomeParentItemAdapterPreview(
|
|||
if (
|
||||
binding is FragmentHomeHeadBinding ||
|
||||
binding is FragmentHomeHeadTvBinding &&
|
||||
binding.root.context.isEmulatorSettings()
|
||||
isLayout(EMULATOR)
|
||||
) {
|
||||
val title = (binding as? FragmentHomeHeadBinding)?.homeWatchParentItemTitle
|
||||
?: (binding as? FragmentHomeHeadTvBinding)?.homeWatchParentItemTitle
|
||||
|
@ -628,7 +630,7 @@ class HomeParentItemAdapterPreview(
|
|||
if (
|
||||
binding is FragmentHomeHeadBinding ||
|
||||
binding is FragmentHomeHeadTvBinding &&
|
||||
binding.root.context.isEmulatorSettings()
|
||||
isLayout(EMULATOR)
|
||||
) {
|
||||
val title = (binding as? FragmentHomeHeadBinding)?.homeBookmarkParentItemTitle
|
||||
?: (binding as? FragmentHomeHeadTvBinding)?.homeBookmarkParentItemTitle
|
||||
|
|
|
@ -10,7 +10,9 @@ import androidx.viewbinding.ViewBinding
|
|||
import com.lagradost.cloudstream3.LoadResponse
|
||||
import com.lagradost.cloudstream3.databinding.HomeScrollViewBinding
|
||||
import com.lagradost.cloudstream3.databinding.HomeScrollViewTvBinding
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
|
||||
class HomeScrollAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
@ -40,7 +42,7 @@ class HomeScrollAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
val binding = if (isTvSettings()) {
|
||||
val binding = if (isLayout(TV or EMULATOR)) {
|
||||
HomeScrollViewTvBinding.inflate(inflater, parent, false)
|
||||
} else {
|
||||
HomeScrollViewBinding.inflate(inflater, parent, false)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<AutofitRecyclerView>()
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.lagradost.cloudstream3.ui.library
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.doOnAttach
|
||||
|
@ -12,7 +11,9 @@ import com.lagradost.cloudstream3.R
|
|||
import com.lagradost.cloudstream3.databinding.LibraryViewpagerPageBinding
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||
|
||||
class ViewpagerAdapter(
|
||||
|
@ -73,7 +74,7 @@ class ViewpagerAdapter(
|
|||
val diff = scrollY - oldScrollY
|
||||
|
||||
//Expand the top Appbar based on scroll direction up/down, simulate phone behavior
|
||||
if (SettingsFragment.isTvSettings()) {
|
||||
if (isLayout(TV or EMULATOR)) {
|
||||
binding.root.rootView.findViewById<AppBarLayout>(R.id.search_bar)
|
||||
.apply {
|
||||
if (diff <= 0)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
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
|
||||
|
|
|
@ -5,7 +5,8 @@ import android.view.ViewGroup
|
|||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.lagradost.cloudstream3.databinding.ResultMiniImageBinding
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
|
||||
/*
|
||||
class ImageAdapter(context: Context, val resource: Int) : ArrayAdapter<Int>(context, resource) {
|
||||
|
@ -83,7 +84,7 @@ class ImageAdapter(
|
|||
this.nextFocusUpId = nextFocusUp
|
||||
}
|
||||
if (clickCallback != null) {
|
||||
if (isTrueTvSettings()) {
|
||||
if (isLayout(TV)) {
|
||||
isClickable = true
|
||||
isLongClickable = true
|
||||
isFocusable = true
|
||||
|
|
|
@ -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<String>(ctx, R.layout.sort_bottom_single_choice)
|
||||
/*
|
||||
|
|
|
@ -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<SearchResponse>?, 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)
|
||||
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.values().map { view.context.getString(it.stringRes) }.toList(),
|
||||
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.values()[it], context)
|
||||
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,6 +753,8 @@ class ResultFragmentTv : Fragment() {
|
|||
observe(viewModel.recommendations) { recommendations ->
|
||||
setRecommendations(recommendations, null)
|
||||
}
|
||||
|
||||
if (isLayout(TV)) {
|
||||
observe(viewModel.episodeSynopsis) { description ->
|
||||
view.context?.let { ctx ->
|
||||
val builder: AlertDialog.Builder =
|
||||
|
@ -732,23 +767,29 @@ class ResultFragmentTv : Fragment() {
|
|||
.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,13 +869,23 @@ 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 ->
|
||||
|
||||
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)
|
||||
|
@ -841,6 +894,8 @@ class ResultFragmentTv : Fragment() {
|
|||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val error = listOf(
|
||||
R.drawable.profile_bg_dark_blue,
|
||||
|
@ -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 -> {
|
||||
|
|
|
@ -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) {
|
||||
if (loadResponse.isEpisodeBased()) {
|
||||
val id = loadResponse.getId()
|
||||
val data = getSubscribedData(id)
|
||||
if (loadResponse.isEpisodeBased()) {
|
||||
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<String, String>? = 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(
|
||||
|
|
|
@ -6,7 +6,8 @@ import androidx.recyclerview.widget.DiffUtil
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.lagradost.cloudstream3.databinding.ResultSelectionBinding
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
|
||||
typealias SelectData = Pair<UiText?, Any>
|
||||
|
||||
|
@ -72,8 +73,7 @@ class SelectAdaptor(val callback: (Any) -> Unit) : RecyclerView.Adapter<Recycler
|
|||
fun bind(
|
||||
data: SelectData, isSelected: Boolean, callback: (Any) -> Unit
|
||||
) {
|
||||
val isTrueTv = isTrueTvSettings()
|
||||
if (isTrueTv) {
|
||||
if (isLayout(TV)) {
|
||||
item.isFocusable = true
|
||||
item.isFocusableInTouchMode = true
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
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)
|
||||
// TODO TRYCATCH
|
||||
binding = FragmentSearchBinding.bind(root)
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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<MaterialToolbar>(R.id.settings_toolbar)
|
||||
|
||||
settingsAppbar?.updateLayoutParams<AppBarLayout.LayoutParams> {
|
||||
|
@ -66,7 +62,7 @@ class SettingsFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
fun Fragment?.setToolBarScrollFlags() {
|
||||
if (isTvSettings()) {
|
||||
if (isLayout(TV or EMULATOR)) {
|
||||
val settingsAppbar = this?.view?.findViewById<MaterialToolbar>(R.id.settings_toolbar)
|
||||
|
||||
settingsAppbar?.updateLayoutParams<AppBarLayout.LayoutParams> {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
clipboardHelper(txt("Logcat"), text)
|
||||
dialog.dismissSafe(activity)
|
||||
} catch (e: TransactionTooLargeException) {
|
||||
showToast(R.string.clipboard_too_large)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -17,7 +17,8 @@ import com.lagradost.cloudstream3.plugins.PluginManager
|
|||
import com.lagradost.cloudstream3.plugins.VotingApi.getVotes
|
||||
import com.lagradost.cloudstream3.ui.result.setText
|
||||
import com.lagradost.cloudstream3.ui.result.txt
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
|
@ -44,7 +45,7 @@ class PluginAdapter(
|
|||
private val plugins: MutableList<PluginViewData> = mutableListOf()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val layout = if(isTrueTvSettings()) R.layout.repository_item_tv else R.layout.repository_item
|
||||
val layout = if(isLayout(TV)) R.layout.repository_item_tv else R.layout.repository_item
|
||||
val inflated = LayoutInflater.from(parent.context).inflate(layout, parent, false)
|
||||
|
||||
return PluginViewHolder(
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<RepositoryData> = mutableListOf()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
val layout = if (isTrueTvSettings()) RepositoryItemTvBinding.inflate(
|
||||
val layout = if (isLayout(TV)) RepositoryItemTvBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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<Array<String>>? = null
|
||||
|
@ -252,8 +255,12 @@ object BackupUtils {
|
|||
map: Map<String, T>?,
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -50,6 +50,28 @@ class PreferenceDelegate<T : Any>(
|
|||
}
|
||||
}
|
||||
|
||||
/** 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<T> 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<String> != null) -> editor.putStringSet(path, value as Set<String>)
|
||||
}
|
||||
}
|
||||
|
||||
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 <T> Context.setKeyRaw(path: String, value: T, isEditingAppSettings: Boolean = false) {
|
||||
try {
|
||||
fun editor(context : Context, isEditingAppSettings: Boolean = false) : Editor {
|
||||
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<String> != null) -> editor.putStringSet(path, value as Set<String>)
|
||||
}
|
||||
editor.apply()
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
if (isEditingAppSettings) context.getDefaultSharedPrefs().edit() else context.getSharedPrefs().edit()
|
||||
return Editor(editor)
|
||||
}
|
||||
|
||||
fun Context.getDefaultSharedPrefs(): SharedPreferences {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<ClipboardManager>()?.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 {
|
||||
|
|
10
app/src/main/res/drawable/ic_baseline_film_roll_24.xml
Normal file
10
app/src/main/res/drawable/ic_baseline_film_roll_24.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M240,760L240,800Q240,817 228.5,828.5Q217,840 200,840Q183,840 171.5,828.5Q160,817 160,800L160,160Q160,143 171.5,131.5Q183,120 200,120Q217,120 228.5,131.5Q240,143 240,160L240,200L320,200L320,160Q320,143 331.5,131.5Q343,120 360,120L600,120Q617,120 628.5,131.5Q640,143 640,160L640,200L720,200L720,160Q720,143 731.5,131.5Q743,120 760,120Q777,120 788.5,131.5Q800,143 800,160L800,800Q800,817 788.5,828.5Q777,840 760,840Q743,840 731.5,828.5Q720,817 720,800L720,760L640,760L640,800Q640,817 628.5,828.5Q617,840 600,840L360,840Q343,840 331.5,828.5Q320,817 320,800L320,760L240,760ZM240,680L320,680L320,600L240,600L240,680ZM240,520L320,520L320,440L240,440L240,520ZM240,360L320,360L320,280L240,280L240,360ZM640,680L720,680L720,600L640,600L640,680ZM640,520L720,520L720,440L640,440L640,520ZM640,360L720,360L720,280L640,280L640,360Z"/>
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_baseline_resume_arrow.xml
Normal file
11
app/src/main/res/drawable/ic_baseline_resume_arrow.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="m8.46,5l0,14l11,-7l-11,-7z"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="0.006"
|
||||
android:fillColor="#000000"/>
|
||||
</vector>
|
12
app/src/main/res/drawable/ic_baseline_resume_arrow2.xml
Normal file
12
app/src/main/res/drawable/ic_baseline_resume_arrow2.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="m8.46,5l0,14l11,-7l-11,-7z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M4.92,5.04h2.31v13.98h-2.31z"/>
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_fingerprint.xml
Normal file
11
app/src/main/res/drawable/ic_fingerprint.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/white"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M17.81,4.47c-0.08,0 -0.16,-0.02 -0.23,-0.06C15.66,3.42 14,3 12.01,3c-1.98,0 -3.86,0.47 -5.57,1.41 -0.24,0.13 -0.54,0.04 -0.68,-0.2 -0.13,-0.24 -0.04,-0.55 0.2,-0.68C7.82,2.52 9.86,2 12.01,2c2.13,0 3.99,0.47 6.03,1.52 0.25,0.13 0.34,0.43 0.21,0.67 -0.09,0.18 -0.26,0.28 -0.44,0.28zM3.5,9.72c-0.1,0 -0.2,-0.03 -0.29,-0.09 -0.23,-0.16 -0.28,-0.47 -0.12,-0.7 0.99,-1.4 2.25,-2.5 3.75,-3.27C9.98,4.04 14,4.03 17.15,5.65c1.5,0.77 2.76,1.86 3.75,3.25 0.16,0.22 0.11,0.54 -0.12,0.7 -0.23,0.16 -0.54,0.11 -0.7,-0.12 -0.9,-1.26 -2.04,-2.25 -3.39,-2.94 -2.87,-1.47 -6.54,-1.47 -9.4,0.01 -1.36,0.7 -2.5,1.7 -3.4,2.96 -0.08,0.14 -0.23,0.21 -0.39,0.21zM9.75,21.79c-0.13,0 -0.26,-0.05 -0.35,-0.15 -0.87,-0.87 -1.34,-1.43 -2.01,-2.64 -0.69,-1.23 -1.05,-2.73 -1.05,-4.34 0,-2.97 2.54,-5.39 5.66,-5.39s5.66,2.42 5.66,5.39c0,0.28 -0.22,0.5 -0.5,0.5s-0.5,-0.22 -0.5,-0.5c0,-2.42 -2.09,-4.39 -4.66,-4.39s-4.66,1.97 -4.66,4.39c0,1.44 0.32,2.77 0.93,3.85 0.64,1.15 1.08,1.64 1.85,2.42 0.19,0.2 0.19,0.51 0,0.71 -0.11,0.1 -0.24,0.15 -0.37,0.15zM16.92,19.94c-1.19,0 -2.24,-0.3 -3.1,-0.89 -1.49,-1.01 -2.38,-2.65 -2.38,-4.39 0,-0.28 0.22,-0.5 0.5,-0.5s0.5,0.22 0.5,0.5c0,1.41 0.72,2.74 1.94,3.56 0.71,0.48 1.54,0.71 2.54,0.71 0.24,0 0.64,-0.03 1.04,-0.1 0.27,-0.05 0.53,0.13 0.58,0.41 0.05,0.27 -0.13,0.53 -0.41,0.58 -0.57,0.11 -1.07,0.12 -1.21,0.12zM14.91,22c-0.04,0 -0.09,-0.01 -0.13,-0.02 -1.59,-0.44 -2.63,-1.03 -3.72,-2.1 -1.4,-1.39 -2.17,-3.24 -2.17,-5.22 0,-1.62 1.38,-2.94 3.08,-2.94s3.08,1.32 3.08,2.94c0,1.07 0.93,1.94 2.08,1.94s2.08,-0.87 2.08,-1.94c0,-3.77 -3.25,-6.83 -7.25,-6.83 -2.84,0 -5.44,1.58 -6.61,4.03 -0.39,0.81 -0.59,1.76 -0.59,2.8 0,0.78 0.07,2.01 0.67,3.61 0.1,0.26 -0.03,0.55 -0.29,0.64 -0.26,0.1 -0.55,-0.04 -0.64,-0.29 -0.49,-1.31 -0.73,-2.61 -0.73,-3.96 0,-1.2 0.23,-2.29 0.68,-3.24 1.33,-2.79 4.28,-4.6 7.51,-4.6 4.55,0 8.25,3.51 8.25,7.83 0,1.62 -1.38,2.94 -3.08,2.94s-3.08,-1.32 -3.08,-2.94c0,-1.07 -0.93,-1.94 -2.08,-1.94s-2.08,0.87 -2.08,1.94c0,1.71 0.66,3.31 1.87,4.51 0.95,0.94 1.86,1.46 3.27,1.85 0.27,0.07 0.42,0.35 0.35,0.61 -0.05,0.23 -0.26,0.38 -0.47,0.38z"/>
|
||||
</vector>
|
10
app/src/main/res/drawable/library_icon.xml
Normal file
10
app/src/main/res/drawable/library_icon.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/white">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L400,160L480,240L800,240Q833,240 856.5,263.5Q880,287 880,320L447,320L367,240L160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720L256,400L940,400L837,743Q829,769 807.5,784.5Q786,800 760,800L160,800ZM244,720L760,720L832,480L316,480L244,720ZM244,720L316,480L316,480L244,720L244,720ZM160,320L160,240Q160,240 160,240Q160,240 160,240L160,240L160,320L160,320Z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/outline_bookmark_add_24.xml
Normal file
5
app/src/main/res/drawable/outline_bookmark_add_24.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M17,11v6.97l-5,-2.14l-5,2.14V5h6V3H7C5.9,3 5,3.9 5,5v16l7,-3l7,3V11H17zM21,7h-2v2h-2V7h-2V5h2V3h2v2h2V7z"/>
|
||||
</vector>
|
|
@ -3,13 +3,13 @@
|
|||
<item android:state_focused="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?attr/white" />
|
||||
<corners android:radius="3dp"/>
|
||||
<corners android:radius="@dimen/rounded_image_radius"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/white_attr_20" />
|
||||
<corners android:radius="3dp"/>
|
||||
<corners android:radius="@dimen/rounded_image_radius"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -3,7 +3,7 @@
|
|||
<item android:state_focused="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="?attr/white" />
|
||||
<corners android:radius="3dp"/>
|
||||
<corners android:radius="@dimen/rounded_image_radius"/>
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -41,22 +41,34 @@
|
|||
android:layout_marginStart="10dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/resultview_preview_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="16sp"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginEnd="25dp"
|
||||
tools:text="The Perfect Run">
|
||||
tools:text="The Perfect Run" />
|
||||
|
||||
</TextView>
|
||||
<ImageView
|
||||
android:id="@+id/resultview_preview_subscribe"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
|
||||
android:layout_margin="5dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/subscribe_tooltip"
|
||||
android:elevation="10dp"
|
||||
android:nextFocusDown="@id/resultview_preview_bookmark"
|
||||
android:src="@drawable/baseline_notifications_none_24"
|
||||
app:tint="?attr/textColor" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/resultview_preview_favorite"
|
||||
|
@ -66,11 +78,13 @@
|
|||
|
||||
android:layout_margin="5dp"
|
||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/favorite"
|
||||
android:elevation="10dp"
|
||||
android:nextFocusDown="@id/resultview_preview_bookmark"
|
||||
android:src="@drawable/ic_baseline_favorite_border_24"
|
||||
app:tint="?attr/textColor" />
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.lagradost.cloudstream3.widget.FlowLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -134,38 +148,38 @@
|
|||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:padding="7dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="7dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/resultview_preview_bookmark"
|
||||
style="@style/BlackButton"
|
||||
android:layout_width="50dp"
|
||||
android:layout_weight="1"
|
||||
android:nextFocusUp="@id/resultview_preview_favorite"
|
||||
|
||||
android:nextFocusRight="@id/resultview_preview_more_info"
|
||||
|
||||
tools:visibility="visible"
|
||||
|
||||
android:nextFocusUp="@id/resultview_preview_favorite"
|
||||
app:icon="@drawable/ic_baseline_bookmark_24"
|
||||
tools:text="Bookmark"
|
||||
style="@style/BlackButton"
|
||||
|
||||
android:layout_width="50dp" />
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/resultview_preview_more_info"
|
||||
style="@style/WhiteButton"
|
||||
android:layout_width="50dp"
|
||||
android:layout_weight="1"
|
||||
android:nextFocusUp="@id/resultview_preview_favorite"
|
||||
|
||||
android:nextFocusLeft="@id/resultview_preview_bookmark"
|
||||
|
||||
tools:visibility="visible"
|
||||
|
||||
app:icon="@drawable/ic_baseline_open_in_new_24"
|
||||
android:nextFocusUp="@id/resultview_preview_favorite"
|
||||
android:text="@string/home_more_info"
|
||||
style="@style/WhiteButton"
|
||||
app:icon="@drawable/ic_baseline_open_in_new_24"
|
||||
|
||||
android:layout_width="50dp" />
|
||||
tools:visibility="visible" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
android:layout_width="100dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:focusable="true"
|
||||
android:padding="5dp">
|
||||
<!--app:cardCornerRadius="@dimen/roundedImageRadius"-->
|
||||
<FrameLayout
|
||||
|
|
|
@ -157,6 +157,7 @@
|
|||
android:id="@+id/home_preview_hidden_prev_focus"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="1dp"
|
||||
android:tag="@string/tv_no_focus_tag"
|
||||
android:focusable="false" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
|
@ -189,6 +190,7 @@
|
|||
android:id="@+id/home_preview_hidden_next_focus"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="1dp"
|
||||
android:tag="@string/tv_no_focus_tag"
|
||||
android:focusable="false" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -78,6 +78,30 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
|||
</LinearLayout>
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/background_poster_holder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="275dp"
|
||||
android:visibility="visible">
|
||||
|
||||
<com.lagradost.cloudstream3.utils.PercentageCropImageView
|
||||
android:id="@+id/background_poster"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="275dp"
|
||||
android:layout_gravity="center"
|
||||
android:alpha="0.8"
|
||||
android:scaleType="matrix"
|
||||
tools:src="@drawable/profile_bg_dark_blue" >
|
||||
</com.lagradost.cloudstream3.utils.PercentageCropImageView>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:src="@drawable/background_shadow">
|
||||
</ImageView>
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_loading_error"
|
||||
|
||||
|
@ -124,31 +148,6 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
|||
android:textColor="?attr/textColor" />
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/background_poster_holder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="250dp"
|
||||
android:visibility="visible">
|
||||
|
||||
<com.lagradost.cloudstream3.utils.PercentageCropImageView
|
||||
android:id="@+id/background_poster"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="275dp"
|
||||
android:layout_gravity="center"
|
||||
android:alpha="0.8"
|
||||
android:scaleType="matrix"
|
||||
tools:src="@drawable/profile_bg_dark_blue" >
|
||||
</com.lagradost.cloudstream3.utils.PercentageCropImageView>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:src="@drawable/background_shadow">
|
||||
</ImageView>
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/result_finish_loading"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -165,7 +164,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="175dp">
|
||||
android:layout_marginTop="225dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_title"
|
||||
|
@ -175,7 +174,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
|||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="20sp"
|
||||
android:textSize="25sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="The Perfect Run The Perfect Run" />
|
||||
|
||||
|
@ -221,157 +220,293 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
|||
android:textStyle="normal"
|
||||
tools:text="5d 3h 30m" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_resume_progress_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/result_resume_series_progress"
|
||||
app:trackCornerRadius="50dp"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:indeterminate="false"
|
||||
android:max="100"
|
||||
android:paddingEnd="10dp"
|
||||
android:progress="0"
|
||||
android:progressBackgroundTint="?attr/colorPrimary"
|
||||
tools:progress="50"
|
||||
tools:visibility="visible"
|
||||
tools:ignore="RtlSymmetry" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_resume_series_progress_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
tools:ignore="RtlSymmetry"
|
||||
tools:text="69m remaining" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_play_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:id="@+id/result_play_movie"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_play_movie_button"
|
||||
android:focusable="true"
|
||||
style="@style/ResultSmallButtonTV"
|
||||
app:iconPadding="0dp"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||
android:nextFocusUp="@id/result_play_movie_button"
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:tag="@string/tv_no_focus_tag">
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_play_movie_text"
|
||||
style="@style/ResultMarqueeButtonText"
|
||||
android:text="@string/movies_singular" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:id="@+id/result_play_series"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_play_series_button"
|
||||
android:focusable="true"
|
||||
style="@style/ResultSmallButtonTV"
|
||||
app:iconPadding="0dp"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||
android:nextFocusUp="@id/result_play_series_button"
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:tag="@string/tv_no_focus_tag">
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_play_series_text"
|
||||
style="@style/ResultMarqueeButtonText"
|
||||
android:text="@string/episode" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_resume_series"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_resume_series_button"
|
||||
android:focusable="true"
|
||||
style="@style/ResultSmallButtonTV"
|
||||
app:iconPadding="0dp"
|
||||
app:icon="@drawable/ic_baseline_resume_arrow2"
|
||||
android:nextFocusUp="@id/result_resume_series_button"
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:tag="@string/tv_no_focus_tag">
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_resume_series_text"
|
||||
style="@style/ResultMarqueeButtonText"
|
||||
android:text="@string/resume" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_play_trailer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_play_trailer_button"
|
||||
android:focusable="true"
|
||||
style="@style/ResultSmallButtonTV"
|
||||
app:iconPadding="0dp"
|
||||
app:icon="@drawable/ic_baseline_film_roll_24"
|
||||
android:nextFocusUp="@id/result_play_trailer_button"
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:tag="@string/tv_no_focus_tag">
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_play_trailer_text"
|
||||
style="@style/ResultMarqueeButtonText"
|
||||
android:text="@string/play_trailer_button" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_bookmark"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_bookmark_Button"
|
||||
android:focusable="true"
|
||||
style="@style/ResultSmallButtonTV"
|
||||
app:iconPadding="0dp"
|
||||
app:icon="@drawable/outline_bookmark_add_24"
|
||||
android:nextFocusUp="@id/result_bookmark_Button"
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:tag="@string/tv_no_focus_tag">
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_bookmark_text"
|
||||
style="@style/ResultMarqueeButtonText"
|
||||
android:text="@string/type_none" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_favorite"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_favorite_Button"
|
||||
android:focusable="true"
|
||||
style="@style/ResultSmallButtonTV"
|
||||
app:iconPadding="0dp"
|
||||
app:icon="@drawable/ic_baseline_favorite_border_24"
|
||||
android:nextFocusUp="@id/result_favorite_Button"
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:tag="@string/tv_no_focus_tag">
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_favorite_Text"
|
||||
style="@style/ResultMarqueeButtonText"
|
||||
android:text="@string/favorite" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:id="@+id/result_subscribe"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_subscribe_Button"
|
||||
android:focusable="true"
|
||||
style="@style/ResultSmallButtonTV"
|
||||
app:iconPadding="0dp"
|
||||
app:icon="@drawable/baseline_notifications_none_24"
|
||||
android:nextFocusUp="@id/result_subscribe_Button"
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:tag="@string/tv_no_focus_tag">
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_subscribe_text"
|
||||
style="@style/ResultMarqueeButtonText"
|
||||
android:text="@string/action_subscribe" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:id="@+id/result_search"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_search_Button"
|
||||
android:focusable="true"
|
||||
style="@style/ResultSmallButtonTV"
|
||||
app:iconPadding="0dp"
|
||||
app:icon="@drawable/search_icon"
|
||||
android:nextFocusUp="@id/result_search_Button"
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:tag="@string/tv_no_focus_tag">
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_search_text"
|
||||
style="@style/ResultMarqueeButtonText"
|
||||
android:text="@string/title_search" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:id="@+id/result_episodes_show"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_episodes_show_button"
|
||||
android:focusable="true"
|
||||
style="@style/ResultSmallButtonTV"
|
||||
app:iconPadding="0dp"
|
||||
app:icon="@drawable/ic_baseline_sort_24"
|
||||
android:nextFocusUp="@id/result_episodes_show_button"
|
||||
android:nextFocusRight="@id/redirect_to_episodes"
|
||||
android:nextFocusDown="@id/result_description"
|
||||
android:tag="@string/tv_no_focus_tag">
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_episodes_show_text"
|
||||
style="@style/ResultMarqueeButtonText"
|
||||
android:text="@string/episodes" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="10dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_weight="0"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_movie_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_play_movie"
|
||||
style="@style/ResultButtonTV"
|
||||
|
||||
android:nextFocusRight="@id/result_description"
|
||||
android:nextFocusUp="@id/result_play_movie"
|
||||
android:nextFocusDown="@id/result_play_series"
|
||||
android:text="@string/play_movie_button"
|
||||
android:visibility="visible"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/series_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_play_series"
|
||||
style="@style/ResultButtonTV"
|
||||
|
||||
android:nextFocusRight="@id/result_description"
|
||||
android:nextFocusUp="@id/result_play_movie"
|
||||
android:nextFocusDown="@id/result_resume_series"
|
||||
android:text="@string/play_episode"
|
||||
android:visibility="visible"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_resume_series"
|
||||
style="@style/ResultButtonTV"
|
||||
|
||||
android:nextFocusRight="@id/result_description"
|
||||
android:nextFocusUp="@id/result_play_series"
|
||||
android:nextFocusDown="@id/result_play_trailer"
|
||||
android:text="@string/resume"
|
||||
android:visibility="visible"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_play_trailer"
|
||||
style="@style/ResultButtonTV"
|
||||
|
||||
android:nextFocusRight="@id/result_description"
|
||||
android:nextFocusUp="@id/result_resume_series"
|
||||
android:nextFocusDown="@id/result_bookmark_button"
|
||||
android:text="@string/play_trailer_button"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
<!-- <com.lagradost.cloudstream3.ui.download.button.DownloadButton
|
||||
android:visibility="gone"
|
||||
android:id="@+id/download_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="250dp"
|
||||
app:download_layout="@layout/download_button_layout" />
|
||||
-->
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_bookmark_button"
|
||||
style="@style/ResultButtonTV"
|
||||
android:nextFocusRight="@id/result_description"
|
||||
android:nextFocusUp="@id/result_play_trailer"
|
||||
android:nextFocusDown="@id/result_favorite_button"
|
||||
|
||||
android:text="@string/type_none"
|
||||
android:visibility="visible"
|
||||
app:icon="@drawable/ic_baseline_bookmark_24" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_favorite_button"
|
||||
style="@style/ResultButtonTV"
|
||||
android:nextFocusRight="@id/result_description"
|
||||
android:nextFocusUp="@id/result_bookmark_button"
|
||||
android:nextFocusDown="@id/result_subscribe_button"
|
||||
|
||||
android:text="@string/action_add_to_favorites"
|
||||
android:visibility="visible"
|
||||
app:icon="@drawable/ic_baseline_favorite_border_24" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_subscribe_button"
|
||||
style="@style/ResultButtonTV"
|
||||
android:nextFocusRight="@id/result_description"
|
||||
android:nextFocusUp="@id/result_favorite_button"
|
||||
android:nextFocusDown="@id/result_episodes_show"
|
||||
|
||||
android:text="@string/action_subscribe"
|
||||
android:visibility="visible"
|
||||
app:icon="@drawable/ic_baseline_favorite_border_24" />
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_episodes_show"
|
||||
style="@style/ResultButtonTV"
|
||||
|
||||
android:nextFocusRight="@id/redirect_to_episodes"
|
||||
android:nextFocusUp="@id/result_subscribe_button"
|
||||
android:nextFocusDown="@id/result_cast_items"
|
||||
|
||||
android:text="@string/episodes"
|
||||
android:visibility="visible"
|
||||
app:icon="@drawable/ic_baseline_sort_24"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<View
|
||||
android:id="@+id/redirect_to_episodes"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="1dp"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true" />
|
||||
|
||||
|
||||
<View
|
||||
android:id="@+id/redirect_to_play"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="1dp"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
android:layout_marginTop="10dp"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/right_layout"
|
||||
|
@ -382,7 +517,8 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/linearLayout2"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<com.lagradost.cloudstream3.widget.FlowLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -439,8 +575,8 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
|||
android:foreground="@drawable/outline_drawable"
|
||||
android:maxLines="7"
|
||||
android:focusable="true"
|
||||
android:nextFocusUp="@id/result_back"
|
||||
android:nextFocusDown="@id/result_bookmark_button"
|
||||
android:nextFocusUp="@id/result_play_parent"
|
||||
android:nextFocusDown="@id/result_cast_items"
|
||||
android:padding="5dp"
|
||||
android:requiresFadingEdge="vertical"
|
||||
android:textColor="?attr/textColor"
|
||||
|
@ -450,59 +586,8 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
|||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/result_tag"
|
||||
style="@style/ChipParent"
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_resume_progress_holder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:id="@+id/result_resume_series_progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:indeterminate="false"
|
||||
android:max="100"
|
||||
|
||||
android:paddingEnd="10dp"
|
||||
android:progress="0"
|
||||
android:progressBackgroundTint="?attr/colorPrimary"
|
||||
android:visibility="gone"
|
||||
tools:progress="50"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_resume_series_progress_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="0"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:paddingEnd="5dp"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:visibility="gone"
|
||||
tools:ignore="RtlSymmetry"
|
||||
tools:text="69m remaining" />
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
@ -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" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_tv_coming_soon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:paddingTop="50dp"
|
||||
android:text="@string/coming_soon"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
tools:visibility="visible"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_recommendations_holder"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
|
@ -540,7 +638,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
|||
android:layout_marginEnd="10dp"
|
||||
android:nextFocusUp="@id/result_cast_items"
|
||||
android:nextFocusDown="@id/result_recommendations_list"
|
||||
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="2"
|
||||
|
@ -563,7 +661,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
|||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:nextFocusUp="@id/result_recommendations_filter_selection"
|
||||
android:nextFocusUp="@id/result_cast_items"
|
||||
android:orientation="vertical"
|
||||
app:spanCount="8"
|
||||
tools:listitem="@layout/search_result_grid" />
|
||||
|
@ -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">
|
||||
|
||||
|
||||
<!--
|
||||
|
@ -765,152 +863,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<!--
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_movie_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical"
|
||||
tools:visibility="visible">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_play_movie"
|
||||
style="@style/RegularButtonTV"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="250dp"
|
||||
android:nextFocusUp="@id/result_back"
|
||||
android:nextFocusDown="@id/result_play_series"
|
||||
android:text="@string/play_movie_button"
|
||||
android:visibility="visible"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24">
|
||||
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/series_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_play_series"
|
||||
style="@style/RegularButtonTV"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="250dp"
|
||||
android:nextFocusUp="@id/result_play_movie"
|
||||
android:nextFocusDown="@id/result_resume_series"
|
||||
android:text="@string/play_episode"
|
||||
android:visibility="visible"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_resume_series"
|
||||
style="@style/RegularButtonTV"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="250dp"
|
||||
android:nextFocusUp="@id/result_play_series"
|
||||
android:nextFocusDown="@id/result_play_trailer"
|
||||
android:text="@string/resume"
|
||||
android:visibility="visible"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||
tools:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_play_trailer"
|
||||
style="@style/RegularButtonTV"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="250dp"
|
||||
android:nextFocusUp="@id/result_resume_series"
|
||||
android:nextFocusDown="@id/result_bookmark_button"
|
||||
android:text="@string/play_trailer_button"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_baseline_play_arrow_24"
|
||||
tools:visibility="visible">
|
||||
|
||||
</com.google.android.material.button.MaterialButton>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_bookmark_button"
|
||||
style="@style/RegularButtonTV"
|
||||
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_weight="1"
|
||||
android:minWidth="250dp"
|
||||
android:nextFocusUp="@id/result_play_trailer"
|
||||
android:nextFocusDown="@id/result_resume_series_button"
|
||||
android:text="@string/type_none"
|
||||
android:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_resume_progress_holder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<androidx.core.widget.ContentLoadingProgressBar
|
||||
android:id="@+id/result_resume_series_progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:indeterminate="false"
|
||||
android:max="100"
|
||||
android:paddingEnd="10dp"
|
||||
android:progress="0"
|
||||
android:progressBackgroundTint="?attr/colorPrimary"
|
||||
android:visibility="visible"
|
||||
tools:progress="50"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_resume_series_progress_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="0"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:paddingEnd="5dp"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
tools:ignore="RtlSymmetry"
|
||||
tools:text="69m remaining" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
-->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/result_poster_holder"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -1067,70 +1020,47 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
|||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_baseline_add_24" />
|
||||
|
||||
|
||||
<!--<LinearLayout
|
||||
|
||||
android:id="@+id/result_resume_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
tools:visibility="gone">
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/result_next_series_button"
|
||||
style="@style/WhiteButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="0dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:nextFocusUp="@id/result_bookmark_button"
|
||||
android:nextFocusDown="@id/result_download_movie"
|
||||
android:text="@string/next_episode"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/cast_ic_mini_controller_skip_next" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:layout_weight="0"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_movie_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:layout_gravity="start"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/result_resume_series_button"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
|
||||
android:layout_gravity="center"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/download"
|
||||
|
||||
android:nextFocusUp="@id/result_play_movie"
|
||||
android:nextFocusDown="@id/result_season_selection"
|
||||
android:src="@drawable/ic_baseline_play_arrow_24"
|
||||
android:visibility="visible"
|
||||
app:tint="?attr/white" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/result_resume_series_title"
|
||||
<LinearLayout
|
||||
android:id="@+id/series_holder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="17sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="S1E1 Episode 1" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<View
|
||||
android:id="@+id/redirect_to_episodes"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="1dp"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true" />
|
||||
|
||||
<View
|
||||
android:id="@+id/redirect_to_play"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="1dp"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>-->
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</FrameLayout>
|
|
@ -105,9 +105,10 @@
|
|||
android:text="@string/extensions" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/app_version_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
|
|
|
@ -533,18 +533,14 @@
|
|||
android:id="@id/exo_position"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="30dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="20dp"
|
||||
android:gravity="end|center_vertical"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:minWidth="50dp"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="normal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/player_pause_play"
|
||||
app:layout_constraintStart_toEndOf="@id/player_pause_play"
|
||||
tools:text="15:30" />
|
||||
|
||||
<FrameLayout
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
android:title="@string/title_search" />
|
||||
<item
|
||||
android:id="@+id/navigation_library"
|
||||
android:icon="@drawable/ic_outline_account_circle_24"
|
||||
android:icon="@drawable/library_icon"
|
||||
android:title="@string/library" />
|
||||
<item
|
||||
android:id="@+id/navigation_downloads"
|
||||
|
|
|
@ -212,6 +212,7 @@
|
|||
<item>Banana</item>
|
||||
<item>Fiesta</item>
|
||||
<item>Dolor rosa</item>
|
||||
<item>Lavanda</item>
|
||||
<item>Material You</item>
|
||||
<item>Material You (Secondary)</item>
|
||||
</string-array>
|
||||
|
@ -235,6 +236,7 @@
|
|||
<item>Banana</item>
|
||||
<item>Party</item>
|
||||
<item>Pink</item>
|
||||
<item>Lavender</item>
|
||||
<item>Monet</item>
|
||||
<item>Monet2</item>
|
||||
</string-array>
|
||||
|
|
|
@ -221,6 +221,7 @@
|
|||
<item>Bananowy</item>
|
||||
<item>Łososiowy</item>
|
||||
<item>Świnko peppowy</item>
|
||||
<item>Lawenda</item>
|
||||
<item>Material You</item>
|
||||
<item>Material You (drugorzędny)</item>
|
||||
</string-array>
|
||||
|
@ -244,6 +245,7 @@
|
|||
<item>Banana</item>
|
||||
<item>Party</item>
|
||||
<item>Pink</item>
|
||||
<item>Lavender</item>
|
||||
<item>Monet</item>
|
||||
<item>Monet2</item>
|
||||
</string-array>
|
||||
|
|
|
@ -247,6 +247,7 @@
|
|||
<item>Muz</item>
|
||||
<item>Parti</item>
|
||||
<item>Pembe</item>
|
||||
<item>Lavanta</item>
|
||||
<item>Material You</item>
|
||||
<item>Material You (İkincil)</item>
|
||||
</string-array>
|
||||
|
@ -270,6 +271,7 @@
|
|||
<item>Banana</item>
|
||||
<item>Party</item>
|
||||
<item>Pink</item>
|
||||
<item>Lavender</item>
|
||||
<item>Monet</item>
|
||||
<item>Monet2</item>
|
||||
</string-array>
|
||||
|
|
|
@ -213,6 +213,7 @@
|
|||
<item>Vàng</item>
|
||||
<item>Hồng</item>
|
||||
<item>Hồng đậm</item>
|
||||
<item>Hoa oải hương</item>
|
||||
<item>Material You</item>
|
||||
<item>Material You (Secondary)</item>
|
||||
</string-array>
|
||||
|
@ -236,6 +237,7 @@
|
|||
<item>Banana</item>
|
||||
<item>Party</item>
|
||||
<item>Pink</item>
|
||||
<item>Lavender</item>
|
||||
<item>Monet</item>
|
||||
<item>Monet2</item>
|
||||
</string-array>
|
||||
|
|
|
@ -284,6 +284,7 @@
|
|||
<item>Banana</item>
|
||||
<item>Party</item>
|
||||
<item>Pink Pain</item>
|
||||
<item>Lavender</item>
|
||||
<item>Material You</item>
|
||||
<item>Material You (Secondary)</item>
|
||||
</string-array>
|
||||
|
@ -307,6 +308,7 @@
|
|||
<item>Banana</item>
|
||||
<item>Party</item>
|
||||
<item>Pink</item>
|
||||
<item>Lavender</item>
|
||||
<item>Monet</item>
|
||||
<item>Monet2</item>
|
||||
</string-array>
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
<color name="colorPrimaryOrange">#CE8500</color>
|
||||
<color name="colorPrimaryDandelionYellow">#F5BB00</color>
|
||||
<color name="colorPrimaryCoolBlue">#408cac</color>
|
||||
<color name="colorPrimaryLavender">#6F55AF</color>
|
||||
|
||||
<color name="colorTestPass">#48E484</color>
|
||||
<color name="colorTestFail">#ea596e</color>
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
<string name="enable_skip_op_from_database" translatable="false">enable_skip_op_from_database</string>
|
||||
<string name="rotate_video_key" translatable="false">rotate_video_key</string>
|
||||
<string name="auto_rotate_video_key" translatable="false">auto_rotate_video_key</string>
|
||||
<string name="biometric_key" translatable="false">biometric_key</string>
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="extra_info_format" formatted="true" translatable="false">%d %s | %s</string>
|
||||
<string name="storage_size_format" formatted="true" translatable="false">%s • %s</string>
|
||||
|
@ -173,8 +174,8 @@
|
|||
<string name="sort_close">Close</string>
|
||||
<string name="sort_clear">Clear</string>
|
||||
<string name="sort_save">Save</string>
|
||||
<string name="copyTitle">Title copied!</string>
|
||||
<string name="copyRepoUrl">Repo URL copied!</string>
|
||||
<string name="repo_copy_label">Repository name and URL</string>
|
||||
<string name="toast_copied">copied!</string>
|
||||
<string name="subscribe_tooltip">New episode notification</string>
|
||||
<string name="result_search_tooltip">Search in other extensions</string>
|
||||
<string name="recommendations_tooltip">Show recommendations</string>
|
||||
|
@ -247,7 +248,7 @@
|
|||
<string name="backup_failed_error_format">Error backing up %s</string>
|
||||
<string name="search">Search</string>
|
||||
<string name="library">Library</string>
|
||||
<string name="category_account">Accounts</string>
|
||||
<string name="category_account">Accounts and Security</string>
|
||||
<string name="category_updates">Updates and backup</string>
|
||||
<string name="settings_info">Info</string>
|
||||
<string name="advanced_search">Advanced Search</string>
|
||||
|
@ -306,6 +307,7 @@
|
|||
<string name="go_forward_30">+30</string>
|
||||
<string name="delete_message" formatted="true">This will permanently delete %s\nAre you sure?</string>
|
||||
<string name="resume_time_left" formatted="true">%dm\nremaining</string>
|
||||
<string name="resume_remaining" formatted="true">%s\nremaining</string>
|
||||
<string name="status_ongoing">Ongoing</string>
|
||||
<string name="status_completed">Completed</string>
|
||||
<string name="status">Status</string>
|
||||
|
@ -645,6 +647,8 @@
|
|||
<string name="history">History</string>
|
||||
<string name="enable_skip_op_from_database_des">Show skip popups for opening/ending</string>
|
||||
<string name="clipboard_too_large">Too much text. Unable to save to clipboard.</string>
|
||||
<string name="clipboard_permission_error">Error accessing Clipboard, Please try again.</string>
|
||||
<string name="clipboard_unknown_error">Error copying, Please copy logcat and contact app support.</string>
|
||||
<string name="action_mark_as_watched">Mark as watched</string>
|
||||
<string name="action_remove_from_watched">Remove from watched</string>
|
||||
<string name="confirm_exit_dialog">Are you sure you want to exit\?</string>
|
||||
|
@ -745,4 +749,17 @@
|
|||
<string name="rotate_video_desc">Display a toggle button for screen orientation</string>
|
||||
<string name="auto_rotate_video_desc">Enable automatic switching of screen orientation based on video orientation</string>
|
||||
<string name="auto_rotate_video">Auto rotate</string>
|
||||
<string name="favorite">Favorite</string>
|
||||
<string name="unfavorite">Unfavorite</string>
|
||||
<!-- For Biometrics -->
|
||||
<string name="biometric_authentication_title">Unlock CloudStream</string>
|
||||
<string name="biometric_setting">Lock with Biometrics</string>
|
||||
<string name="password_pin_authentication_title">Password/PIN Authentication</string>
|
||||
<string name="biometric_unsupported">Biometric authentication is not supported on this device</string>
|
||||
<string name="biometric_setting_summary">Unlock the app with Fingerprint, Face ID, PIN, Pattern and Password.</string>
|
||||
<string name="biometric_prompt_description">This window will close after few failed attempts. You\'ll have to restart the App.</string>
|
||||
<string name="biometric_warning">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.</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -383,6 +383,16 @@
|
|||
<item name="android:colorAccent">@color/colorPrimaryCoolBlue</item>
|
||||
</style>
|
||||
|
||||
<style name="OverlayPrimaryColorLavender">
|
||||
<item name="colorPrimary">@color/colorPrimaryLavender</item>
|
||||
<item name="android:colorPrimary">@color/colorPrimaryLavender</item>
|
||||
<item name="colorPrimaryDark">#6B51AB</item>
|
||||
<item name="colorAccent">#7961B4</item>
|
||||
<item name="colorOnPrimary">@color/whiteText</item>
|
||||
<!-- Needed for leanback fuckery -->
|
||||
<item name="android:colorAccent">@color/colorPrimaryLavender</item>
|
||||
</style>
|
||||
|
||||
<style name="customRatingBar" parent="@style/Widget.AppCompat.RatingBar">
|
||||
|
||||
<item name="android:progressDrawable">@drawable/abc_ratingbar_indicator_material</item>
|
||||
|
@ -804,6 +814,35 @@
|
|||
<item name="android:insetTop">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="ResultSmallButtonTV">
|
||||
<item name="android:tag">@string/tv_no_focus_tag</item>
|
||||
<item name="android:stateListAnimator">@null</item>
|
||||
<item name="strokeColor">@color/transparent</item>
|
||||
<item name="backgroundTint">@null</item>
|
||||
<item name="android:background">@drawable/player_button_tv_attr</item>
|
||||
<item name="rippleColor">@color/white</item>
|
||||
<item name="android:shadowColor">@color/transparent</item>
|
||||
<item name="iconTint">@color/player_on_button_tv_attr</item>
|
||||
<item name="iconGravity">textStart</item>
|
||||
<item name="android:layout_width">60dp</item>
|
||||
<item name="android:layout_height">40dp</item>
|
||||
<item name="android:gravity">center</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
<item name="android:layout_marginStart">4dp</item>
|
||||
<item name="android:layout_marginEnd">4dp</item>
|
||||
<item name="android:layout_marginBottom">4dp</item>
|
||||
</style>
|
||||
|
||||
<style name="ResultMarqueeButtonText">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:gravity">top|center_horizontal</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
<item name="android:scrollHorizontally">true</item>
|
||||
<item name="android:marqueeRepeatLimit">marquee_forever</item>
|
||||
<item name="android:ellipsize">marquee</item>
|
||||
</style>
|
||||
|
||||
<style name="VideoButtonTV">
|
||||
<item name="android:tag">@string/tv_no_focus_tag</item>
|
||||
<item name="android:stateListAnimator">@null</item>
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:icon="@drawable/ic_outline_account_circle_24"
|
||||
android:key="@string/skip_startup_account_select_key"
|
||||
android:title="@string/skip_startup_account_select_pref" />
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/mal_logo"
|
||||
|
@ -22,17 +16,18 @@
|
|||
<Preference
|
||||
android:icon="@drawable/open_subtitles_icon"
|
||||
android:key="@string/opensubtitles_key" />
|
||||
<!-- <Preference-->
|
||||
<!-- android:key="@string/nginx_key"-->
|
||||
<!-- android:icon="@drawable/nginx" />-->
|
||||
|
||||
<!-- <Preference-->
|
||||
<!-- android:title="@string/nginx_info_title"-->
|
||||
<!-- android:icon="@drawable/nginx_question"-->
|
||||
<!-- android:summary="@string/nginx_info_summary">-->
|
||||
<!-- <intent-->
|
||||
<!-- android:action="android.intent.action.VIEW"-->
|
||||
<!-- android:data="https://www.sarlays.com/use-nginx-with-cloudstream/" />-->
|
||||
<!-- </Preference>-->
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:icon="@drawable/ic_outline_account_circle_24"
|
||||
android:key="@string/skip_startup_account_select_key"
|
||||
android:title="@string/skip_startup_account_select_pref" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:key="@string/biometric_key"
|
||||
android:defaultValue="false"
|
||||
android:summary="@string/biometric_setting_summary"
|
||||
android:icon="@drawable/ic_fingerprint"
|
||||
android:title="@string/biometric_setting" />
|
||||
|
||||
</PreferenceScreen>
|
|
@ -4,6 +4,7 @@ buildscript {
|
|||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:8.3.0")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue