Merge branch 'recloudstream:master' into master
This commit is contained in:
commit
96fa3de474
|
@ -60,7 +60,7 @@ android {
|
||||||
targetSdk = 33 /* Android 14 is Fu*ked
|
targetSdk = 33 /* Android 14 is Fu*ked
|
||||||
^ https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading*/
|
^ https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading*/
|
||||||
versionCode = 63
|
versionCode = 63
|
||||||
versionName = "4.3.1"
|
versionName = "4.3.2"
|
||||||
|
|
||||||
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
|
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
|
||||||
resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "")
|
resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "")
|
||||||
|
@ -163,15 +163,15 @@ dependencies {
|
||||||
// Android Core & Lifecycle
|
// Android Core & Lifecycle
|
||||||
implementation("androidx.core:core-ktx:1.12.0")
|
implementation("androidx.core:core-ktx:1.12.0")
|
||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
|
implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
|
||||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
|
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
|
||||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
|
implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")
|
||||||
|
|
||||||
// Design & UI
|
// Design & UI
|
||||||
implementation("jp.wasabeef:glide-transformations:4.3.0")
|
implementation("jp.wasabeef:glide-transformations:4.3.0")
|
||||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
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.constraintlayout:constraintlayout:2.1.4")
|
||||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,9 @@ import androidx.fragment.app.FragmentActivity
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
|
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||||
|
@ -31,7 +33,6 @@ import org.acra.sender.ReportSenderFactory
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.PrintStream
|
import java.io.PrintStream
|
||||||
import java.lang.Exception
|
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
@ -211,7 +212,7 @@ class AcraApplication : Application() {
|
||||||
fun openBrowser(url: String, activity: FragmentActivity?) {
|
fun openBrowser(url: String, activity: FragmentActivity?) {
|
||||||
openBrowser(
|
openBrowser(
|
||||||
url,
|
url,
|
||||||
isTvSettings(),
|
isLayout(TV or EMULATOR),
|
||||||
activity?.supportFragmentManager?.fragments?.lastOrNull()
|
activity?.supportFragmentManager?.fragments?.lastOrNull()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,9 @@ import android.util.DisplayMetrics
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.NO_ID
|
import android.view.View.NO_ID
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
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.google.android.material.navigationrail.NavigationRailView
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||||
|
import com.lagradost.cloudstream3.databinding.ToastBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.ui.player.PlayerEventType
|
import com.lagradost.cloudstream3.ui.player.PlayerEventType
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
||||||
import com.lagradost.cloudstream3.ui.result.UiText
|
import com.lagradost.cloudstream3.ui.result.UiText
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
|
import com.lagradost.cloudstream3.ui.settings.Globals.updateTv
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isRtl
|
import com.lagradost.cloudstream3.utils.AppUtils.isRtl
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
import com.lagradost.cloudstream3.utils.Event
|
import com.lagradost.cloudstream3.utils.Event
|
||||||
|
@ -99,8 +98,7 @@ object CommonActivity {
|
||||||
var playerEventListener: ((PlayerEventType) -> Unit)? = null
|
var playerEventListener: ((PlayerEventType) -> Unit)? = null
|
||||||
var keyEventListener: ((Pair<KeyEvent?, Boolean>) -> Boolean)? = null
|
var keyEventListener: ((Pair<KeyEvent?, Boolean>) -> Boolean)? = null
|
||||||
|
|
||||||
|
private var currentToast: Toast? = null
|
||||||
var currentToast: Toast? = null
|
|
||||||
|
|
||||||
fun showToast(@StringRes message: Int, duration: Int? = null) {
|
fun showToast(@StringRes message: Int, duration: Int? = null) {
|
||||||
val act = activity ?: return
|
val act = activity ?: return
|
||||||
|
@ -156,25 +154,19 @@ object CommonActivity {
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val inflater =
|
val binding = ToastBinding.inflate(act.layoutInflater)
|
||||||
act.getSystemService(AppCompatActivity.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
binding.text.text = message.trim()
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
// custom toasts are deprecated and won't appear when cs3 sets minSDK to api30 (A11)
|
||||||
val toast = Toast(act)
|
val toast = Toast(act)
|
||||||
toast.setGravity(Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM, 0, 5.toPx)
|
|
||||||
toast.duration = duration ?: Toast.LENGTH_SHORT
|
toast.duration = duration ?: Toast.LENGTH_SHORT
|
||||||
toast.view = layout
|
toast.setGravity(Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM, 0, 5.toPx)
|
||||||
//https://github.com/PureWriter/ToastCompat
|
toast.view = binding.root
|
||||||
toast.show()
|
|
||||||
currentToast = toast
|
currentToast = toast
|
||||||
|
toast.show()
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.simklAp
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
||||||
import com.lagradost.cloudstream3.syncproviders.providers.SimklApi
|
import com.lagradost.cloudstream3.syncproviders.providers.SimklApi
|
||||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||||
|
import com.lagradost.cloudstream3.ui.result.ResultViewModel2
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.*
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.mainWork
|
import com.lagradost.cloudstream3.utils.Coroutines.mainWork
|
||||||
|
@ -119,7 +120,8 @@ object APIHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LoadResponse.getId(): Int {
|
fun LoadResponse.getId(): Int {
|
||||||
return getLoadResponseIdFromUrl(url, apiName)
|
// this fixes an issue with outdated api as getLoadResponseIdFromUrl might be fucked
|
||||||
|
return (if (this is ResultViewModel2.LoadResponseFromSearch) this.id else null) ?: getLoadResponseIdFromUrl(url, apiName)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -86,6 +86,7 @@ import com.lagradost.cloudstream3.plugins.PluginManager
|
||||||
import com.lagradost.cloudstream3.plugins.PluginManager.loadAllOnlinePlugins
|
import com.lagradost.cloudstream3.plugins.PluginManager.loadAllOnlinePlugins
|
||||||
import com.lagradost.cloudstream3.plugins.PluginManager.loadSinglePlugin
|
import com.lagradost.cloudstream3.plugins.PluginManager.loadSinglePlugin
|
||||||
import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
|
import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
|
||||||
|
import com.lagradost.cloudstream3.services.SubscriptionWorkManager
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString
|
||||||
|
@ -112,11 +113,11 @@ import com.lagradost.cloudstream3.ui.result.setText
|
||||||
import com.lagradost.cloudstream3.ui.result.txt
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchFragment
|
import com.lagradost.cloudstream3.ui.search.SearchFragment
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTruePhone
|
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
|
import com.lagradost.cloudstream3.ui.settings.Globals.updateTv
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsGeneral
|
import com.lagradost.cloudstream3.ui.settings.SettingsGeneral
|
||||||
import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY
|
import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY
|
||||||
import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
|
import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
|
||||||
|
@ -290,7 +291,8 @@ var app = Requests(responseParser = object : ResponseParser {
|
||||||
defaultHeaders = mapOf("user-agent" to USER_AGENT)
|
defaultHeaders = mapOf("user-agent" to USER_AGENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAuthenticator.BiometricAuthCallback {
|
class MainActivity : AppCompatActivity(), ColorPickerDialogListener,
|
||||||
|
BiometricAuthenticator.BiometricAuthCallback {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "MAINACT"
|
const val TAG = "MAINACT"
|
||||||
const val ANIMATED_OUTLINE: Boolean = false
|
const val ANIMATED_OUTLINE: Boolean = false
|
||||||
|
@ -336,10 +338,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
|
||||||
|
|
||||||
// kinda shitty solution, but cant com main->home otherwise for popups
|
// kinda shitty solution, but cant com main->home otherwise for popups
|
||||||
val bookmarksUpdatedEvent = Event<Boolean>()
|
val bookmarksUpdatedEvent = Event<Boolean>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by DataStoreHelper to fully reload home when switching accounts
|
* Used by DataStoreHelper to fully reload home when switching accounts
|
||||||
*/
|
*/
|
||||||
val reloadHomeEvent = Event<Boolean>()
|
val reloadHomeEvent = Event<Boolean>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by DataStoreHelper to fully reload library when switching accounts
|
* Used by DataStoreHelper to fully reload library when switching accounts
|
||||||
*/
|
*/
|
||||||
|
@ -554,7 +558,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
|
||||||
binding?.navHostFragment?.apply {
|
binding?.navHostFragment?.apply {
|
||||||
val params = layoutParams as ConstraintLayout.LayoutParams
|
val params = layoutParams as ConstraintLayout.LayoutParams
|
||||||
val push =
|
val push =
|
||||||
if (!dontPush && isTvSettings()) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0
|
if (!dontPush && isLayout(TV or EMULATOR)) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0
|
||||||
|
|
||||||
if (!this.isLtr()) {
|
if (!this.isLtr()) {
|
||||||
params.setMargins(
|
params.setMargins(
|
||||||
|
@ -581,7 +585,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
|
||||||
}
|
}
|
||||||
|
|
||||||
Configuration.ORIENTATION_PORTRAIT -> {
|
Configuration.ORIENTATION_PORTRAIT -> {
|
||||||
isTvSettings()
|
isLayout(TV or EMULATOR)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -788,6 +792,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
|
||||||
|
|
||||||
lateinit var viewModel: ResultViewModel2
|
lateinit var viewModel: ResultViewModel2
|
||||||
lateinit var syncViewModel: SyncViewModel
|
lateinit var syncViewModel: SyncViewModel
|
||||||
|
|
||||||
/** kinda dirty, however it signals that we should use the watch status as sync or not*/
|
/** kinda dirty, however it signals that we should use the watch status as sync or not*/
|
||||||
var isLocalList: Boolean = false
|
var isLocalList: Boolean = false
|
||||||
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
|
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
|
||||||
|
@ -1172,11 +1177,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
|
||||||
|
|
||||||
// just in case, MAIN SHOULD *NEVER* BOOT LOOP CRASH
|
// just in case, MAIN SHOULD *NEVER* BOOT LOOP CRASH
|
||||||
binding = try {
|
binding = try {
|
||||||
if (isTvSettings()) {
|
if (isLayout(TV or EMULATOR)) {
|
||||||
val newLocalBinding = ActivityMainTvBinding.inflate(layoutInflater, null, false)
|
val newLocalBinding = ActivityMainTvBinding.inflate(layoutInflater, null, false)
|
||||||
setContentView(newLocalBinding.root)
|
setContentView(newLocalBinding.root)
|
||||||
|
|
||||||
if (isTrueTvSettings() && ANIMATED_OUTLINE) {
|
if (isLayout(TV) && ANIMATED_OUTLINE) {
|
||||||
TvFocus.focusOutline = WeakReference(newLocalBinding.focusOutline)
|
TvFocus.focusOutline = WeakReference(newLocalBinding.focusOutline)
|
||||||
newLocalBinding.root.viewTreeObserver.addOnScrollChangedListener {
|
newLocalBinding.root.viewTreeObserver.addOnScrollChangedListener {
|
||||||
TvFocus.updateFocusView(TvFocus.lastFocus.get(), same = true)
|
TvFocus.updateFocusView(TvFocus.lastFocus.get(), same = true)
|
||||||
|
@ -1188,7 +1193,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
|
||||||
newLocalBinding.focusOutline.isVisible = false
|
newLocalBinding.focusOutline.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isTrueTvSettings()) {
|
if (isLayout(TV)) {
|
||||||
// Put here any button you don't want focusing it to center the view
|
// Put here any button you don't want focusing it to center the view
|
||||||
val exceptionButtons = listOf(
|
val exceptionButtons = listOf(
|
||||||
R.id.home_preview_play_btt,
|
R.id.home_preview_play_btt,
|
||||||
|
@ -1223,18 +1228,21 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
changeStatusBarState(isEmulatorSettings())
|
changeStatusBarState(isLayout(EMULATOR))
|
||||||
|
|
||||||
/** Biometric stuff for users without accounts **/
|
/** Biometric stuff for users without accounts **/
|
||||||
val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_key), false)
|
val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_key), false)
|
||||||
val noAccounts = settingsManager.getBoolean(getString(R.string.skip_startup_account_select_key), false) || accounts.count() <= 1
|
val noAccounts = settingsManager.getBoolean(
|
||||||
|
getString(R.string.skip_startup_account_select_key),
|
||||||
|
false
|
||||||
|
) || accounts.count() <= 1
|
||||||
|
|
||||||
if (isTruePhone() && authEnabled && noAccounts) {
|
if (isLayout(PHONE) && authEnabled && noAccounts) {
|
||||||
if (deviceHasPasswordPinLock(this)) {
|
if (deviceHasPasswordPinLock(this)) {
|
||||||
startBiometricAuthentication(this, R.string.biometric_authentication_title, false)
|
startBiometricAuthentication(this, R.string.biometric_authentication_title, false)
|
||||||
|
|
||||||
BiometricAuthenticator.promptInfo?.let {
|
BiometricAuthenticator.promptInfo?.let { promt ->
|
||||||
BiometricAuthenticator.biometricPrompt?.authenticate(it)
|
BiometricAuthenticator.biometricPrompt?.authenticate(promt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hide background while authenticating, Sorry moms & dads 🙏
|
// hide background while authenticating, Sorry moms & dads 🙏
|
||||||
|
@ -1360,12 +1368,41 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(viewModel.watchStatus) { state ->
|
fun setSubscribeStatus(state: Boolean?) {
|
||||||
setWatchStatus(state)
|
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 ->
|
setImageResource(drawable)
|
||||||
setUserData(status)
|
|
||||||
}
|
}
|
||||||
|
isVisible = state != null
|
||||||
|
|
||||||
|
setOnClickListener {
|
||||||
|
viewModel.toggleSubscriptionStatus(context) { newStatus: Boolean? ->
|
||||||
|
if (newStatus == null) return@toggleSubscriptionStatus
|
||||||
|
|
||||||
|
val message = if (newStatus) {
|
||||||
|
// Kinda icky to have this here, but it works.
|
||||||
|
SubscriptionWorkManager.enqueuePeriodicWork(context)
|
||||||
|
R.string.subscription_new
|
||||||
|
} else {
|
||||||
|
R.string.subscription_deleted
|
||||||
|
}
|
||||||
|
|
||||||
|
val name = (viewModel.page.value as? Resource.Success)?.value?.title
|
||||||
|
?: txt(R.string.no_data).asStringNull(context) ?: ""
|
||||||
|
showToast(txt(message, name), Toast.LENGTH_SHORT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
observe(viewModel.watchStatus,::setWatchStatus)
|
||||||
|
observe(syncViewModel.userData, ::setUserData)
|
||||||
|
observeNullable(viewModel.subscribeStatus, ::setSubscribeStatus)
|
||||||
|
|
||||||
observeNullable(viewModel.page) { resource ->
|
observeNullable(viewModel.page) { resource ->
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
|
@ -1408,6 +1445,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
|
||||||
|
|
||||||
setUserData(syncViewModel.userData.value)
|
setUserData(syncViewModel.userData.value)
|
||||||
setWatchStatus(viewModel.watchStatus.value)
|
setWatchStatus(viewModel.watchStatus.value)
|
||||||
|
setSubscribeStatus(viewModel.subscribeStatus.value)
|
||||||
|
|
||||||
resultviewPreviewBookmark.setOnClickListener {
|
resultviewPreviewBookmark.setOnClickListener {
|
||||||
//viewModel.updateWatchStatus(WatchType.PLANTOWATCH)
|
//viewModel.updateWatchStatus(WatchType.PLANTOWATCH)
|
||||||
|
@ -1426,7 +1464,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val value = (syncViewModel.userData.value as? Resource.Success)?.value?.status ?: SyncWatchType.NONE
|
val value =
|
||||||
|
(syncViewModel.userData.value as? Resource.Success)?.value?.status
|
||||||
|
?: SyncWatchType.NONE
|
||||||
|
|
||||||
this@MainActivity.showBottomDialog(
|
this@MainActivity.showBottomDialog(
|
||||||
SyncWatchType.values().map { getString(it.stringRes) }.toList(),
|
SyncWatchType.values().map { getString(it.stringRes) }.toList(),
|
||||||
|
@ -1469,7 +1509,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isTvSettings()) // dont want this clickable on tv layout
|
if (isLayout(PHONE)) // dont want this clickable on tv layout
|
||||||
resultviewPreviewDescription.setOnClickListener { view ->
|
resultviewPreviewDescription.setOnClickListener { view ->
|
||||||
view.context?.let { ctx ->
|
view.context?.let { ctx ->
|
||||||
val builder: AlertDialog.Builder =
|
val builder: AlertDialog.Builder =
|
||||||
|
@ -1544,7 +1584,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTvSettings()) {
|
if (isLayout(TV or EMULATOR)) {
|
||||||
if (navDestination.matchDestination(R.id.navigation_home)) {
|
if (navDestination.matchDestination(R.id.navigation_home)) {
|
||||||
attachBackPressedCallback()
|
attachBackPressedCallback()
|
||||||
} else detachBackPressedCallback()
|
} else detachBackPressedCallback()
|
||||||
|
@ -1580,7 +1620,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
|
||||||
itemRippleColor = rippleColor
|
itemRippleColor = rippleColor
|
||||||
itemActiveIndicatorColor = rippleColor
|
itemActiveIndicatorColor = rippleColor
|
||||||
setupWithNavController(navController)
|
setupWithNavController(navController)
|
||||||
if (isTvSettings()) {
|
if (isLayout(TV or EMULATOR)) {
|
||||||
background?.alpha = 200
|
background?.alpha = 200
|
||||||
} else {
|
} else {
|
||||||
background?.alpha = 255
|
background?.alpha = 255
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.lagradost.cloudstream3.services
|
package com.lagradost.cloudstream3.services
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -12,7 +13,7 @@ import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
|
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||||
import com.lagradost.cloudstream3.ui.result.txt
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel
|
import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel
|
||||||
|
@ -97,7 +98,9 @@ class SubscriptionWorkManager(val context: Context, workerParams: WorkerParamete
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnspecifiedImmutableFlag")
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
|
try {
|
||||||
// println("Update subscriptions!")
|
// println("Update subscriptions!")
|
||||||
context.createNotificationChannel(
|
context.createNotificationChannel(
|
||||||
SUBSCRIPTION_CHANNEL_ID,
|
SUBSCRIPTION_CHANNEL_ID,
|
||||||
|
@ -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.
|
// You can probably get some issues here since this is async but it does not matter much.
|
||||||
updateProgress(max, ++progress, false)
|
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()
|
return Result.success()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
|
@ -8,7 +8,8 @@ import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
||||||
import com.lagradost.cloudstream3.ui.WatchType
|
import com.lagradost.cloudstream3.ui.WatchType
|
||||||
import com.lagradost.cloudstream3.ui.library.ListSorting
|
import com.lagradost.cloudstream3.ui.library.ListSorting
|
||||||
import com.lagradost.cloudstream3.ui.result.txt
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
|
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions
|
||||||
|
@ -71,9 +72,9 @@ class LocalList : SyncAPI {
|
||||||
}?.distinctBy { it.first } ?: return null
|
}?.distinctBy { it.first } ?: return null
|
||||||
|
|
||||||
val list = ioWork {
|
val list = ioWork {
|
||||||
val isTrueTv = isTrueTvSettings()
|
val isTrueTv = isLayout(TV)
|
||||||
|
|
||||||
val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate {
|
val baseMap = WatchType.entries.filter { it != WatchType.NONE }.associate {
|
||||||
// None is not something to display
|
// None is not something to display
|
||||||
it.stringRes to emptyList<SyncAPI.LibraryItem>()
|
it.stringRes to emptyList<SyncAPI.LibraryItem>()
|
||||||
} + mapOf(
|
} + mapOf(
|
||||||
|
|
|
@ -0,0 +1,250 @@
|
||||||
|
package com.lagradost.cloudstream3.ui
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.children
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||||
|
import androidx.recyclerview.widget.AsyncListDiffer
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
||||||
|
open class ViewHolderState<T>(val view: ViewBinding) : ViewHolder(view.root) {
|
||||||
|
open fun save(): T? = null
|
||||||
|
open fun restore(state: T) = Unit
|
||||||
|
open fun onViewAttachedToWindow() = Unit
|
||||||
|
open fun onViewDetachedFromWindow() = Unit
|
||||||
|
open fun onViewRecycled() = Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Based of the concept https://github.com/brahmkshatriya/echo/blob/main/app%2Fsrc%2Fmain%2Fjava%2Fdev%2Fbrahmkshatriya%2Fecho%2Fui%2Fadapters%2FMediaItemsContainerAdapter.kt#L108-L154
|
||||||
|
class StateViewModel : ViewModel() {
|
||||||
|
val layoutManagerStates = hashMapOf<Int, HashMap<Int, Any?>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class NoStateAdapter<T : Any>(fragment: Fragment) : BaseAdapter<T, Any>(fragment, 0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BaseAdapter is a persistent state stored adapter that supports headers and footers.
|
||||||
|
* This should be used for restoring eg scroll or focus related to a view when it is recreated.
|
||||||
|
*
|
||||||
|
* Id is a per fragment based unique id used to store the underlying data done in an internal ViewModel.
|
||||||
|
*
|
||||||
|
* diffCallback is how the view should be handled when updating, override onUpdateContent for updates
|
||||||
|
*
|
||||||
|
* NOTE:
|
||||||
|
*
|
||||||
|
* By default it should save automatically, but you can also call save(recycle)
|
||||||
|
*
|
||||||
|
* By default no state is stored, but doing an id != 0 will store
|
||||||
|
*
|
||||||
|
* By default no headers or footers exist, override footers and headers count
|
||||||
|
*/
|
||||||
|
abstract class BaseAdapter<
|
||||||
|
T : Any,
|
||||||
|
S : Any>(
|
||||||
|
fragment: Fragment,
|
||||||
|
val id: Int = 0,
|
||||||
|
diffCallback: DiffUtil.ItemCallback<T> = BaseDiffCallback()
|
||||||
|
) : RecyclerView.Adapter<ViewHolderState<S>>() {
|
||||||
|
open val footers: Int = 0
|
||||||
|
open val headers: Int = 0
|
||||||
|
|
||||||
|
fun getItem(position: Int): T {
|
||||||
|
return mDiffer.currentList[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getItemOrNull(position: Int): T? {
|
||||||
|
return mDiffer.currentList.getOrNull(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val mDiffer: AsyncListDiffer<T> = AsyncListDiffer(
|
||||||
|
object : NonFinalAdapterListUpdateCallback(this) {
|
||||||
|
override fun onMoved(fromPosition: Int, toPosition: Int) {
|
||||||
|
super.onMoved(fromPosition + headers, toPosition + headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRemoved(position: Int, count: Int) {
|
||||||
|
super.onRemoved(position + headers, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
||||||
|
super.onChanged(position + headers, count, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInserted(position: Int, count: Int) {
|
||||||
|
super.onInserted(position + headers, count)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AsyncDifferConfig.Builder(diffCallback).build()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun submitList(list: List<T>?) {
|
||||||
|
// deep copy at least the top list, because otherwise adapter can go crazy
|
||||||
|
mDiffer.submitList(list?.let { CopyOnWriteArrayList(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return mDiffer.currentList.size + footers + headers
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onUpdateContent(holder: ViewHolderState<S>, item: T, position: Int) =
|
||||||
|
onBindContent(holder, item, position)
|
||||||
|
|
||||||
|
open fun onBindContent(holder: ViewHolderState<S>, item: T, position: Int) = Unit
|
||||||
|
open fun onBindFooter(holder: ViewHolderState<S>) = Unit
|
||||||
|
open fun onBindHeader(holder: ViewHolderState<S>) = Unit
|
||||||
|
open fun onCreateContent(parent: ViewGroup): ViewHolderState<S> = throw NotImplementedError()
|
||||||
|
open fun onCreateFooter(parent: ViewGroup): ViewHolderState<S> = throw NotImplementedError()
|
||||||
|
open fun onCreateHeader(parent: ViewGroup): ViewHolderState<S> = throw NotImplementedError()
|
||||||
|
|
||||||
|
override fun onViewAttachedToWindow(holder: ViewHolderState<S>) {
|
||||||
|
holder.onViewAttachedToWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewDetachedFromWindow(holder: ViewHolderState<S>) {
|
||||||
|
holder.onViewDetachedFromWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save(recyclerView: RecyclerView) {
|
||||||
|
for (child in recyclerView.children) {
|
||||||
|
val holder =
|
||||||
|
recyclerView.findContainingViewHolder(child) as? ViewHolderState<S> ?: continue
|
||||||
|
setState(holder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
stateViewModel.layoutManagerStates[id]?.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getState(holder: ViewHolderState<S>): S? =
|
||||||
|
stateViewModel.layoutManagerStates[id]?.get(holder.absoluteAdapterPosition) as? S
|
||||||
|
|
||||||
|
private fun setState(holder: ViewHolderState<S>) {
|
||||||
|
if(id == 0) return
|
||||||
|
|
||||||
|
if (!stateViewModel.layoutManagerStates.contains(id)) {
|
||||||
|
stateViewModel.layoutManagerStates[id] = HashMap()
|
||||||
|
}
|
||||||
|
stateViewModel.layoutManagerStates[id]?.let { map ->
|
||||||
|
map[holder.absoluteAdapterPosition] = holder.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val attachListener = object : View.OnAttachStateChangeListener {
|
||||||
|
override fun onViewAttachedToWindow(v: View) = Unit
|
||||||
|
override fun onViewDetachedFromWindow(v: View) {
|
||||||
|
if (v !is RecyclerView) return
|
||||||
|
save(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
recyclerView.addOnAttachStateChangeListener(attachListener)
|
||||||
|
super.onAttachedToRecyclerView(recyclerView)
|
||||||
|
}
|
||||||
|
|
||||||
|
final override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
||||||
|
recyclerView.removeOnAttachStateChangeListener(attachListener)
|
||||||
|
super.onDetachedFromRecyclerView(recyclerView)
|
||||||
|
}
|
||||||
|
|
||||||
|
final override fun getItemViewType(position: Int): Int {
|
||||||
|
if (position < headers) {
|
||||||
|
return HEADER
|
||||||
|
}
|
||||||
|
if (position - headers >= mDiffer.currentList.size) {
|
||||||
|
return FOOTER
|
||||||
|
}
|
||||||
|
|
||||||
|
return CONTENT
|
||||||
|
}
|
||||||
|
|
||||||
|
private val stateViewModel: StateViewModel by fragment.viewModels()
|
||||||
|
|
||||||
|
final override fun onViewRecycled(holder: ViewHolderState<S>) {
|
||||||
|
setState(holder)
|
||||||
|
holder.onViewRecycled()
|
||||||
|
super.onViewRecycled(holder)
|
||||||
|
}
|
||||||
|
|
||||||
|
final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderState<S> {
|
||||||
|
return when (viewType) {
|
||||||
|
CONTENT -> onCreateContent(parent)
|
||||||
|
HEADER -> onCreateHeader(parent)
|
||||||
|
FOOTER -> onCreateFooter(parent)
|
||||||
|
else -> throw NotImplementedError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://medium.com/@domen.lanisnik/efficiently-updating-recyclerview-items-using-payloads-1305f65f3068
|
||||||
|
override fun onBindViewHolder(
|
||||||
|
holder: ViewHolderState<S>,
|
||||||
|
position: Int,
|
||||||
|
payloads: MutableList<Any>
|
||||||
|
) {
|
||||||
|
if (payloads.isEmpty()) {
|
||||||
|
super.onBindViewHolder(holder, position, payloads)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
when (getItemViewType(position)) {
|
||||||
|
CONTENT -> {
|
||||||
|
val realPosition = position - headers
|
||||||
|
val item = getItem(realPosition)
|
||||||
|
onUpdateContent(holder, item, realPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
FOOTER -> {
|
||||||
|
onBindFooter(holder)
|
||||||
|
}
|
||||||
|
|
||||||
|
HEADER -> {
|
||||||
|
onBindHeader(holder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final override fun onBindViewHolder(holder: ViewHolderState<S>, position: Int) {
|
||||||
|
when (getItemViewType(position)) {
|
||||||
|
CONTENT -> {
|
||||||
|
val realPosition = position - headers
|
||||||
|
val item = getItem(realPosition)
|
||||||
|
onBindContent(holder, item, realPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
FOOTER -> {
|
||||||
|
onBindFooter(holder)
|
||||||
|
}
|
||||||
|
|
||||||
|
HEADER -> {
|
||||||
|
onBindHeader(holder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getState(holder)?.let { state ->
|
||||||
|
holder.restore(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val HEADER: Int = 1
|
||||||
|
private const val FOOTER: Int = 2
|
||||||
|
private const val CONTENT: Int = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BaseDiffCallback<T : Any>(
|
||||||
|
val itemSame: (T, T) -> Boolean = { a, b -> a.hashCode() == b.hashCode() },
|
||||||
|
val contentSame: (T, T) -> Boolean = { a, b -> a.hashCode() == b.hashCode() }
|
||||||
|
) : DiffUtil.ItemCallback<T>() {
|
||||||
|
override fun areItemsTheSame(oldItem: T, newItem: T): Boolean = itemSame(oldItem, newItem)
|
||||||
|
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean = contentSame(oldItem, newItem)
|
||||||
|
override fun getChangePayload(oldItem: T, newItem: T): Any = Any()
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.lagradost.cloudstream3.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import androidx.recyclerview.widget.ListUpdateCallback
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ListUpdateCallback that dispatches update events to the given adapter.
|
||||||
|
*
|
||||||
|
* @see DiffUtil.DiffResult.dispatchUpdatesTo
|
||||||
|
*/
|
||||||
|
open class NonFinalAdapterListUpdateCallback
|
||||||
|
/**
|
||||||
|
* Creates an AdapterListUpdateCallback that will dispatch update events to the given adapter.
|
||||||
|
*
|
||||||
|
* @param adapter The Adapter to send updates to.
|
||||||
|
*/(private var mAdapter: RecyclerView.Adapter<*>) :
|
||||||
|
ListUpdateCallback {
|
||||||
|
|
||||||
|
override fun onInserted(position: Int, count: Int) {
|
||||||
|
mAdapter.notifyItemRangeInserted(position, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRemoved(position: Int, count: Int) {
|
||||||
|
mAdapter.notifyItemRangeRemoved(position, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMoved(fromPosition: Int, toPosition: Int) {
|
||||||
|
mAdapter.notifyItemMoved(fromPosition, toPosition)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnknownNullness") // b/240775049: Cannot annotate properly
|
||||||
|
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
||||||
|
mAdapter.notifyItemRangeChanged(position, count, payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -12,7 +12,9 @@ import com.lagradost.cloudstream3.databinding.AccountListItemBinding
|
||||||
import com.lagradost.cloudstream3.databinding.AccountListItemEditBinding
|
import com.lagradost.cloudstream3.databinding.AccountListItemEditBinding
|
||||||
import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountEditDialog
|
import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountEditDialog
|
||||||
import com.lagradost.cloudstream3.ui.result.setImage
|
import com.lagradost.cloudstream3.ui.result.setImage
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
|
|
||||||
|
@ -38,7 +40,7 @@ class AccountAdapter(
|
||||||
is AccountListItemBinding -> binding.apply {
|
is AccountListItemBinding -> binding.apply {
|
||||||
if (account == null) return@apply
|
if (account == null) return@apply
|
||||||
|
|
||||||
val isTv = isTvSettings() || !root.isInTouchMode
|
val isTv = isLayout(TV or EMULATOR) || !root.isInTouchMode
|
||||||
|
|
||||||
val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex
|
val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex
|
||||||
|
|
||||||
|
@ -80,7 +82,7 @@ class AccountAdapter(
|
||||||
is AccountListItemEditBinding -> binding.apply {
|
is AccountListItemEditBinding -> binding.apply {
|
||||||
if (account == null) return@apply
|
if (account == null) return@apply
|
||||||
|
|
||||||
val isTv = isTvSettings() || !root.isInTouchMode
|
val isTv = isLayout(TV or EMULATOR) || !root.isInTouchMode
|
||||||
|
|
||||||
val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex
|
val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,10 @@ import com.lagradost.cloudstream3.mvvm.observe
|
||||||
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
||||||
import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_EDIT_ACCOUNT
|
import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_EDIT_ACCOUNT
|
||||||
import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_SELECT_ACCOUNT
|
import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_SELECT_ACCOUNT
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTruePhone
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.BiometricAuthenticator
|
import com.lagradost.cloudstream3.utils.BiometricAuthenticator
|
||||||
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
|
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
|
||||||
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
|
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
|
||||||
|
@ -54,7 +56,7 @@ class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.Biomet
|
||||||
|
|
||||||
fun askBiometricAuth() {
|
fun askBiometricAuth() {
|
||||||
|
|
||||||
if (isTruePhone() && authEnabled) {
|
if (isLayout(PHONE) && authEnabled) {
|
||||||
if (deviceHasPasswordPinLock(this)) {
|
if (deviceHasPasswordPinLock(this)) {
|
||||||
startBiometricAuthentication(
|
startBiometricAuthentication(
|
||||||
this,
|
this,
|
||||||
|
@ -62,8 +64,8 @@ class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.Biomet
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
BiometricAuthenticator.promptInfo?.let {
|
BiometricAuthenticator.promptInfo?.let { promt ->
|
||||||
BiometricAuthenticator.biometricPrompt?.authenticate(it)
|
BiometricAuthenticator.biometricPrompt?.authenticate(promt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,7 +129,7 @@ class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.Biomet
|
||||||
|
|
||||||
recyclerView.adapter = adapter
|
recyclerView.adapter = adapter
|
||||||
|
|
||||||
if (isTvSettings()) {
|
if (isLayout(TV or EMULATOR)) {
|
||||||
binding.editAccountButton.setBackgroundResource(
|
binding.editAccountButton.setBackgroundResource(
|
||||||
R.drawable.player_button_tv_attr_no_bg
|
R.drawable.player_button_tv_attr_no_bg
|
||||||
)
|
)
|
||||||
|
@ -168,7 +170,7 @@ class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.Biomet
|
||||||
viewModel.toggleIsEditing()
|
viewModel.toggleIsEditing()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTvSettings()) {
|
if (isLayout(TV or EMULATOR)) {
|
||||||
recyclerView.spanCount = if (liveAccounts.count() + 1 <= 6) {
|
recyclerView.spanCount = if (liveAccounts.count() + 1 <= 6) {
|
||||||
liveAccounts.count() + 1
|
liveAccounts.count() + 1
|
||||||
} else 6
|
} else 6
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.format.Formatter.formatShortFileSize
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -13,17 +14,25 @@ import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.StreamInputBinding
|
||||||
import com.lagradost.cloudstream3.isMovieType
|
import com.lagradost.cloudstream3.isMovieType
|
||||||
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
||||||
|
import com.lagradost.cloudstream3.ui.player.BasicLink
|
||||||
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
|
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
|
||||||
import com.lagradost.cloudstream3.ui.player.LinkGenerator
|
import com.lagradost.cloudstream3.ui.player.LinkGenerator
|
||||||
|
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
||||||
|
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE
|
import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE
|
||||||
|
@ -34,15 +43,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
import android.text.format.Formatter.formatShortFileSize
|
|
||||||
import androidx.core.widget.doOnTextChanged
|
|
||||||
import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding
|
|
||||||
import com.lagradost.cloudstream3.databinding.StreamInputBinding
|
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
|
||||||
import com.lagradost.cloudstream3.ui.player.BasicLink
|
|
||||||
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
|
||||||
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ class DownloadFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should be visible in emulator layout
|
// Should be visible in emulator layout
|
||||||
binding?.downloadStreamButton?.isGone = isTrueTvSettings()
|
binding?.downloadStreamButton?.isGone = isLayout(TV)
|
||||||
binding?.downloadStreamButton?.setOnClickListener {
|
binding?.downloadStreamButton?.setOnClickListener {
|
||||||
val dialog =
|
val dialog =
|
||||||
Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom)
|
Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom)
|
||||||
|
|
|
@ -2,16 +2,19 @@ package com.lagradost.cloudstream3.ui.download.button
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Looper
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.animation.AnimationUtils
|
import android.view.animation.AnimationUtils
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.MainThread
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DELETE_FILE
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DELETE_FILE
|
||||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
|
||||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK
|
||||||
|
@ -241,14 +244,8 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
/** Also sets currentStatus */
|
@MainThread
|
||||||
override fun setStatus(status: DownloadStatusTell?) {
|
private fun setStatusInternal(status : DownloadStatusTell?) {
|
||||||
currentStatus = status
|
|
||||||
|
|
||||||
//progressBar.isVisible =
|
|
||||||
// status != null && status != DownloadStatusTell.Complete && status != DownloadStatusTell.Error
|
|
||||||
//progressBarBackground.isVisible = status != null && status != DownloadStatusTell.Complete
|
|
||||||
progressBarBackground.post {
|
|
||||||
val isPreActive = isZeroBytes && status == DownloadStatusTell.IsDownloading
|
val isPreActive = isZeroBytes && status == DownloadStatusTell.IsDownloading
|
||||||
if (animateWaiting && (status == DownloadStatusTell.IsPending || isPreActive)) {
|
if (animateWaiting && (status == DownloadStatusTell.IsPending || isPreActive)) {
|
||||||
val animation = AnimationUtils.loadAnimation(context, waitingAnimation)
|
val animation = AnimationUtils.loadAnimation(context, waitingAnimation)
|
||||||
|
@ -276,6 +273,26 @@ open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
progressBarBackground.isGone = hide
|
progressBarBackground.isGone = hide
|
||||||
progressBar.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() {
|
override fun resetView() {
|
||||||
|
|
|
@ -2,31 +2,58 @@ package com.lagradost.cloudstream3.ui.home
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.SearchResponse
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
import com.lagradost.cloudstream3.databinding.HomeResultGridBinding
|
import com.lagradost.cloudstream3.databinding.HomeResultGridBinding
|
||||||
import com.lagradost.cloudstream3.databinding.HomeResultGridExpandedBinding
|
import com.lagradost.cloudstream3.databinding.HomeResultGridExpandedBinding
|
||||||
|
import com.lagradost.cloudstream3.ui.BaseAdapter
|
||||||
|
import com.lagradost.cloudstream3.ui.ViewHolderState
|
||||||
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isRtl
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.IsBottomLayout
|
import com.lagradost.cloudstream3.utils.UIHelper.IsBottomLayout
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
|
|
||||||
class HomeChildItemAdapter(
|
class HomeScrollViewHolderState(view: ViewBinding) : ViewHolderState<Boolean>(view) {
|
||||||
val cardList: MutableList<SearchResponse>,
|
/*private fun recursive(view : View) : Boolean {
|
||||||
|
if (view.isFocused) {
|
||||||
|
println("VIEW: $view | id=${view.id}")
|
||||||
|
}
|
||||||
|
return (view as? ViewGroup)?.children?.any { recursive(it) } ?: false
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// very shitty that we cant store the state when the view clears,
|
||||||
|
// but this is because the focus clears before the view is removed
|
||||||
|
// so we have to manually store it
|
||||||
|
var wasFocused: Boolean = false
|
||||||
|
override fun save(): Boolean = wasFocused
|
||||||
|
override fun restore(state: Boolean) {
|
||||||
|
if (state) {
|
||||||
|
wasFocused = false
|
||||||
|
// only refocus if tv
|
||||||
|
if(isLayout(TV)) {
|
||||||
|
itemView.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HomeChildItemAdapter(
|
||||||
|
fragment: Fragment,
|
||||||
|
id: Int,
|
||||||
private val nextFocusUp: Int? = null,
|
private val nextFocusUp: Int? = null,
|
||||||
private val nextFocusDown: Int? = null,
|
private val nextFocusDown: Int? = null,
|
||||||
private val clickCallback: (SearchClickCallback) -> Unit,
|
private val clickCallback: (SearchClickCallback) -> Unit,
|
||||||
) :
|
) :
|
||||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
BaseAdapter<SearchResponse, Boolean>(fragment, id) {
|
||||||
var isHorizontal: Boolean = false
|
var isHorizontal: Boolean = false
|
||||||
var hasNext: Boolean = false
|
var hasNext: Boolean = false
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateContent(parent: ViewGroup): ViewHolderState<Boolean> {
|
||||||
val expanded = parent.context.IsBottomLayout()
|
val expanded = parent.context.IsBottomLayout()
|
||||||
/* val layout = if (bottom) R.layout.home_result_grid_expanded else R.layout.home_result_grid
|
/* val layout = if (bottom) R.layout.home_result_grid_expanded else R.layout.home_result_grid
|
||||||
|
|
||||||
|
@ -39,84 +66,15 @@ class HomeChildItemAdapter(
|
||||||
parent,
|
parent,
|
||||||
false
|
false
|
||||||
) else HomeResultGridBinding.inflate(inflater, parent, false)
|
) else HomeResultGridBinding.inflate(inflater, parent, false)
|
||||||
|
return HomeScrollViewHolderState(binding)
|
||||||
|
|
||||||
return CardViewHolder(
|
|
||||||
binding,
|
|
||||||
clickCallback,
|
|
||||||
itemCount,
|
|
||||||
nextFocusUp,
|
|
||||||
nextFocusDown,
|
|
||||||
isHorizontal,
|
|
||||||
parent.isRtl()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindContent(
|
||||||
when (holder) {
|
holder: ViewHolderState<Boolean>,
|
||||||
is CardViewHolder -> {
|
item: SearchResponse,
|
||||||
holder.itemCount = itemCount // i know ugly af
|
position: Int
|
||||||
holder.bind(cardList[position], position)
|
) {
|
||||||
}
|
when (val binding = holder.view) {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return cardList.size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemId(position: Int): Long {
|
|
||||||
return (cardList[position].id ?: position).toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateList(newList: List<SearchResponse>) {
|
|
||||||
val diffResult = DiffUtil.calculateDiff(
|
|
||||||
HomeChildDiffCallback(this.cardList, newList)
|
|
||||||
)
|
|
||||||
|
|
||||||
cardList.clear()
|
|
||||||
cardList.addAll(newList)
|
|
||||||
|
|
||||||
diffResult.dispatchUpdatesTo(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
class CardViewHolder
|
|
||||||
constructor(
|
|
||||||
val binding: ViewBinding,
|
|
||||||
private val clickCallback: (SearchClickCallback) -> Unit,
|
|
||||||
var itemCount: Int,
|
|
||||||
private val nextFocusUp: Int? = null,
|
|
||||||
private val nextFocusDown: Int? = null,
|
|
||||||
private val isHorizontal: Boolean = false,
|
|
||||||
private val isRtl: Boolean
|
|
||||||
) :
|
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
|
||||||
|
|
||||||
fun bind(card: SearchResponse, position: Int) {
|
|
||||||
|
|
||||||
// TV focus fixing
|
|
||||||
/*val nextFocusBehavior = when (position) {
|
|
||||||
0 -> true
|
|
||||||
itemCount - 1 -> false
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (position == 0) { // to fix tv
|
|
||||||
if (isRtl) {
|
|
||||||
itemView.nextFocusRightId = R.id.nav_rail_view
|
|
||||||
itemView.nextFocusLeftId = -1
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
itemView.nextFocusLeftId = R.id.nav_rail_view
|
|
||||||
itemView.nextFocusRightId = -1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
itemView.nextFocusRightId = -1
|
|
||||||
itemView.nextFocusLeftId = -1
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
when (binding) {
|
|
||||||
is HomeResultGridBinding -> {
|
is HomeResultGridBinding -> {
|
||||||
binding.backgroundCard.apply {
|
binding.backgroundCard.apply {
|
||||||
val min = 114.toPx
|
val min = 114.toPx
|
||||||
|
@ -136,8 +94,6 @@ class HomeChildItemAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is HomeResultGridExpandedBinding -> {
|
is HomeResultGridExpandedBinding -> {
|
||||||
|
@ -167,36 +123,21 @@ class HomeChildItemAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchResultBuilder.bind(
|
SearchResultBuilder.bind(
|
||||||
clickCallback,
|
clickCallback = { click ->
|
||||||
card,
|
// ok, so here we hijack the callback to fix the focus
|
||||||
|
when (click.action) {
|
||||||
|
SEARCH_ACTION_LOAD -> (holder as? HomeScrollViewHolderState)?.wasFocused = true
|
||||||
|
}
|
||||||
|
clickCallback(click)
|
||||||
|
},
|
||||||
|
item,
|
||||||
position,
|
position,
|
||||||
itemView,
|
holder.itemView,
|
||||||
null, // nextFocusBehavior,
|
null, // nextFocusBehavior,
|
||||||
nextFocusUp,
|
nextFocusUp,
|
||||||
nextFocusDown
|
nextFocusDown
|
||||||
)
|
)
|
||||||
itemView.tag = position
|
|
||||||
|
|
||||||
//val ani = ScaleAnimation(0.9f, 1.0f, 0.9f, 1f)
|
holder.itemView.tag = position
|
||||||
//ani.fillAfter = true
|
|
||||||
//ani.duration = 200
|
|
||||||
//itemView.startAnimation(ani)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class HomeChildDiffCallback(
|
|
||||||
private val oldList: List<SearchResponse>,
|
|
||||||
private val newList: List<SearchResponse>
|
|
||||||
) :
|
|
||||||
DiffUtil.Callback() {
|
|
||||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
|
||||||
oldList[oldItemPosition].name == newList[newItemPosition].name
|
|
||||||
|
|
||||||
override fun getOldListSize() = oldList.size
|
|
||||||
|
|
||||||
override fun getNewListSize() = newList.size
|
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
|
||||||
oldList[oldItemPosition] == newList[newItemPosition] && oldItemPosition < oldList.size - 1 // always update the last item
|
|
||||||
}
|
|
|
@ -42,8 +42,10 @@ import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountSelectLine
|
||||||
import com.lagradost.cloudstream3.ui.result.txt
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
import com.lagradost.cloudstream3.ui.search.*
|
import com.lagradost.cloudstream3.ui.search.*
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable
|
import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.ownHide
|
import com.lagradost.cloudstream3.utils.AppUtils.ownHide
|
||||||
|
@ -311,7 +313,7 @@ class HomeFragment : Fragment() {
|
||||||
button?.isVisible = isValid
|
button?.isVisible = isValid
|
||||||
button?.isChecked = isValid && selectedTypes.any { types.contains(it) }
|
button?.isChecked = isValid && selectedTypes.any { types.contains(it) }
|
||||||
button?.isFocusable = true
|
button?.isFocusable = true
|
||||||
if (isTrueTvSettings()) {
|
if (isLayout(TV)) {
|
||||||
button?.isFocusableInTouchMode = true
|
button?.isFocusableInTouchMode = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,7 +437,7 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
bottomSheetDialog?.ownShow()
|
bottomSheetDialog?.ownShow()
|
||||||
val layout =
|
val layout =
|
||||||
if (isTvSettings()) R.layout.fragment_home_tv else R.layout.fragment_home
|
if (isLayout(TV or EMULATOR)) R.layout.fragment_home_tv else R.layout.fragment_home
|
||||||
val root = inflater.inflate(layout, container, false)
|
val root = inflater.inflate(layout, container, false)
|
||||||
binding = try {
|
binding = try {
|
||||||
FragmentHomeBinding.bind(root)
|
FragmentHomeBinding.bind(root)
|
||||||
|
@ -449,6 +451,7 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
|
||||||
bottomSheetDialog?.ownHide()
|
bottomSheetDialog?.ownHide()
|
||||||
binding = null
|
binding = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
@ -485,6 +488,10 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
private var bottomSheetDialog: BottomSheetDialog? = null
|
private var bottomSheetDialog: BottomSheetDialog? = null
|
||||||
|
|
||||||
|
// https://github.com/vivchar/RendererRecyclerViewAdapter/blob/185251ee9d94fb6eb3e063b00d646b745186c365/example/src/main/java/com/github/vivchar/example/pages/github/GithubFragment.kt#L32
|
||||||
|
// cry about it, but this is android we are talking about, we cant do the most simple shit without making a global variable
|
||||||
|
private var instanceState: Bundle = Bundle()
|
||||||
|
private var homeMasterAdapter: HomeParentItemAdapterPreview? = null
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
@ -505,15 +512,14 @@ class HomeFragment : Fragment() {
|
||||||
activity.loadSearchResult(listHomepageItems.random())
|
activity.loadSearchResult(listHomepageItems.random())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
homeMasterAdapter = HomeParentItemAdapterPreview(
|
||||||
homeMasterRecycler.adapter =
|
fragment = this@HomeFragment,
|
||||||
HomeParentItemAdapterPreview(
|
homeViewModel,
|
||||||
mutableListOf(),
|
|
||||||
homeViewModel
|
|
||||||
)
|
)
|
||||||
|
homeMasterRecycler.adapter = homeMasterAdapter
|
||||||
//fixPaddingStatusbar(homeLoadingStatusbar)
|
//fixPaddingStatusbar(homeLoadingStatusbar)
|
||||||
|
|
||||||
homeApiFab.isVisible = !isTvSettings()
|
homeApiFab.isVisible = isLayout(PHONE)
|
||||||
|
|
||||||
homeMasterRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
homeMasterRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
@ -521,7 +527,7 @@ class HomeFragment : Fragment() {
|
||||||
homeApiFab.shrink() // hide
|
homeApiFab.shrink() // hide
|
||||||
homeRandom.shrink()
|
homeRandom.shrink()
|
||||||
} else if (dy < -5) {
|
} else if (dy < -5) {
|
||||||
if (!isTvSettings()) {
|
if (isLayout(PHONE)) {
|
||||||
homeApiFab.extend() // show
|
homeApiFab.extend() // show
|
||||||
homeRandom.extend()
|
homeRandom.extend()
|
||||||
}
|
}
|
||||||
|
@ -540,7 +546,7 @@ class HomeFragment : Fragment() {
|
||||||
settingsManager.getBoolean(
|
settingsManager.getBoolean(
|
||||||
getString(R.string.random_button_key),
|
getString(R.string.random_button_key),
|
||||||
false
|
false
|
||||||
) && !isTvSettings()
|
) && isLayout(PHONE)
|
||||||
binding?.homeRandom?.visibility = View.GONE
|
binding?.homeRandom?.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -560,10 +566,11 @@ class HomeFragment : Fragment() {
|
||||||
val mutableListOfResponse = mutableListOf<SearchResponse>()
|
val mutableListOfResponse = mutableListOf<SearchResponse>()
|
||||||
listHomepageItems.clear()
|
listHomepageItems.clear()
|
||||||
|
|
||||||
(homeMasterRecycler.adapter as? ParentItemAdapter)?.updateList(
|
(homeMasterRecycler.adapter as? ParentItemAdapter)?.submitList(d.values.map {
|
||||||
d.values.toMutableList(),
|
it.copy(
|
||||||
homeMasterRecycler
|
list = it.list.copy(list = it.list.list.toMutableList())
|
||||||
)
|
)
|
||||||
|
}.toMutableList())
|
||||||
|
|
||||||
homeLoading.isVisible = false
|
homeLoading.isVisible = false
|
||||||
homeLoadingError.isVisible = false
|
homeLoadingError.isVisible = false
|
||||||
|
@ -612,7 +619,7 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Loading -> {
|
is Resource.Loading -> {
|
||||||
(homeMasterRecycler.adapter as? ParentItemAdapter)?.updateList(listOf())
|
(homeMasterRecycler.adapter as? ParentItemAdapter)?.submitList(listOf())
|
||||||
homeLoadingShimmer.startShimmer()
|
homeLoadingShimmer.startShimmer()
|
||||||
homeLoading.isVisible = true
|
homeLoading.isVisible = true
|
||||||
homeLoadingError.isVisible = false
|
homeLoadingError.isVisible = false
|
||||||
|
|
|
@ -1,22 +1,27 @@
|
||||||
package com.lagradost.cloudstream3.ui.home
|
package com.lagradost.cloudstream3.ui.home
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
|
||||||
import androidx.recyclerview.widget.ListUpdateCallback
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.lagradost.cloudstream3.HomePageList
|
import com.lagradost.cloudstream3.HomePageList
|
||||||
import com.lagradost.cloudstream3.LoadResponse
|
import com.lagradost.cloudstream3.LoadResponse
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.databinding.HomepageParentBinding
|
import com.lagradost.cloudstream3.databinding.HomepageParentBinding
|
||||||
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.ui.BaseAdapter
|
||||||
|
import com.lagradost.cloudstream3.ui.BaseDiffCallback
|
||||||
|
import com.lagradost.cloudstream3.ui.ViewHolderState
|
||||||
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
||||||
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable
|
import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable
|
||||||
|
|
||||||
class LoadClickCallback(
|
class LoadClickCallback(
|
||||||
|
@ -27,193 +32,85 @@ class LoadClickCallback(
|
||||||
)
|
)
|
||||||
|
|
||||||
open class ParentItemAdapter(
|
open class ParentItemAdapter(
|
||||||
private var items: MutableList<HomeViewModel.ExpandableHomepageList>,
|
open val fragment: Fragment,
|
||||||
//private val viewModel: HomeViewModel,
|
id: Int,
|
||||||
private val clickCallback: (SearchClickCallback) -> Unit,
|
private val clickCallback: (SearchClickCallback) -> Unit,
|
||||||
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
|
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
|
||||||
private val expandCallback: ((String) -> Unit)? = null,
|
private val expandCallback: ((String) -> Unit)? = null,
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : BaseAdapter<HomeViewModel.ExpandableHomepageList, Bundle>(
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
fragment,
|
||||||
|
id,
|
||||||
val layoutResId = when {
|
diffCallback = BaseDiffCallback(
|
||||||
isTrueTvSettings() -> R.layout.homepage_parent_tv
|
itemSame = { a, b -> a.list.name == b.list.name },
|
||||||
parent.context.isEmulatorSettings() -> R.layout.homepage_parent_emulator
|
contentSame = { a, b ->
|
||||||
else -> R.layout.homepage_parent
|
a.list.list == b.list.list
|
||||||
}
|
|
||||||
|
|
||||||
val root = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false)
|
|
||||||
|
|
||||||
val binding = HomepageParentBinding.bind(root)
|
|
||||||
|
|
||||||
return ParentViewHolder(
|
|
||||||
binding,
|
|
||||||
clickCallback,
|
|
||||||
moreInfoClickCallback,
|
|
||||||
expandCallback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
||||||
when (holder) {
|
|
||||||
is ParentViewHolder -> {
|
|
||||||
holder.bind(items[position])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return items.size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemId(position: Int): Long {
|
|
||||||
return items[position].list.name.hashCode().toLong()
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmName("updateListHomePageList")
|
|
||||||
fun updateList(newList: List<HomePageList>) {
|
|
||||||
updateList(newList.map { HomeViewModel.ExpandableHomepageList(it, 1, false) }
|
|
||||||
.toMutableList())
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmName("updateListExpandableHomepageList")
|
|
||||||
fun updateList(
|
|
||||||
newList: MutableList<HomeViewModel.ExpandableHomepageList>,
|
|
||||||
recyclerView: RecyclerView? = null
|
|
||||||
) {
|
|
||||||
// this
|
|
||||||
// 1. prevents deep copy that makes this.items == newList
|
|
||||||
// 2. filters out undesirable results
|
|
||||||
// 3. moves empty results to the bottom (sortedBy is a stable sort)
|
|
||||||
val new =
|
|
||||||
newList.map { it.copy(list = it.list.copy(list = it.list.list.filterSearchResponse())) }
|
|
||||||
.sortedBy { it.list.list.isEmpty() }
|
|
||||||
|
|
||||||
val diffResult = DiffUtil.calculateDiff(
|
|
||||||
SearchDiffCallback(items, new)
|
|
||||||
)
|
|
||||||
items.clear()
|
|
||||||
items.addAll(new)
|
|
||||||
|
|
||||||
//val mAdapter = this
|
|
||||||
val delta = if (this@ParentItemAdapter is HomeParentItemAdapterPreview) {
|
|
||||||
headItems
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
diffResult.dispatchUpdatesTo(object : ListUpdateCallback {
|
|
||||||
override fun onInserted(position: Int, count: Int) {
|
|
||||||
//notifyItemRangeChanged(position + delta, count)
|
|
||||||
notifyItemRangeInserted(position + delta, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRemoved(position: Int, count: Int) {
|
|
||||||
notifyItemRangeRemoved(position + delta, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMoved(fromPosition: Int, toPosition: Int) {
|
|
||||||
notifyItemMoved(fromPosition + delta, toPosition + delta)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
recyclerView?.apply {
|
|
||||||
// this loops every viewHolder in the recycle view and checks the position to see if it is within the update range
|
|
||||||
val missingUpdates = (position until (position + count)).toMutableSet()
|
|
||||||
for (i in 0 until itemCount) {
|
|
||||||
val child = getChildAt(i) ?: continue
|
|
||||||
val viewHolder = getChildViewHolder(child) ?: continue
|
|
||||||
if (viewHolder !is ParentViewHolder) continue
|
|
||||||
|
|
||||||
val absolutePosition = viewHolder.bindingAdapterPosition
|
|
||||||
if (absolutePosition >= position && absolutePosition < position + count) {
|
|
||||||
val expand = items.getOrNull(absolutePosition - delta) ?: continue
|
|
||||||
missingUpdates -= absolutePosition
|
|
||||||
//println("Updating ${viewHolder.title.text} ($absolutePosition $position) -> ${expand.list.name}")
|
|
||||||
if (viewHolder.title.text == expand.list.name) {
|
|
||||||
viewHolder.update(expand)
|
|
||||||
} else {
|
|
||||||
viewHolder.bind(expand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// just in case some item did not get updated
|
|
||||||
for (i in missingUpdates) {
|
|
||||||
notifyItemChanged(i, payload)
|
|
||||||
}
|
|
||||||
} ?: run {
|
|
||||||
// in case we don't have a nice
|
|
||||||
notifyItemRangeChanged(position, count, payload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
) {
|
||||||
//diffResult.dispatchUpdatesTo(this)
|
data class ParentItemHolder(val binding: ViewBinding) : ViewHolderState<Bundle>(binding) {
|
||||||
|
override fun save(): Bundle = Bundle().apply {
|
||||||
|
val recyclerView = (binding as? HomepageParentBinding)?.homeChildRecyclerview
|
||||||
|
putParcelable(
|
||||||
|
"value",
|
||||||
|
recyclerView?.layoutManager?.onSaveInstanceState()
|
||||||
|
)
|
||||||
|
(recyclerView?.adapter as? BaseAdapter<*,*>)?.save(recyclerView)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ParentViewHolder
|
override fun restore(state: Bundle) {
|
||||||
constructor(
|
(binding as? HomepageParentBinding)?.homeChildRecyclerview?.layoutManager?.onRestoreInstanceState(
|
||||||
val binding: HomepageParentBinding,
|
state.getParcelable("value")
|
||||||
// val viewModel: HomeViewModel,
|
|
||||||
private val clickCallback: (SearchClickCallback) -> Unit,
|
|
||||||
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
|
|
||||||
private val expandCallback: ((String) -> Unit)? = null,
|
|
||||||
) :
|
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
|
||||||
val title: TextView = binding.homeChildMoreInfo
|
|
||||||
private val recyclerView: RecyclerView = binding.homeChildRecyclerview
|
|
||||||
private val startFocus = R.id.nav_rail_view
|
|
||||||
private val endFocus = FOCUS_SELF
|
|
||||||
fun update(expand: HomeViewModel.ExpandableHomepageList) {
|
|
||||||
val info = expand.list
|
|
||||||
(recyclerView.adapter as? HomeChildItemAdapter?)?.apply {
|
|
||||||
updateList(info.list.toMutableList())
|
|
||||||
hasNext = expand.hasNext
|
|
||||||
} ?: run {
|
|
||||||
recyclerView.adapter = HomeChildItemAdapter(
|
|
||||||
info.list.toMutableList(),
|
|
||||||
clickCallback = clickCallback,
|
|
||||||
nextFocusUp = recyclerView.nextFocusUpId,
|
|
||||||
nextFocusDown = recyclerView.nextFocusDownId,
|
|
||||||
).apply {
|
|
||||||
isHorizontal = info.isHorizontalImages
|
|
||||||
hasNext = expand.hasNext
|
|
||||||
}
|
|
||||||
recyclerView.setLinearListLayout(
|
|
||||||
isHorizontal = true,
|
|
||||||
nextLeft = startFocus,
|
|
||||||
nextRight = endFocus,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bind(expand: HomeViewModel.ExpandableHomepageList) {
|
override fun onUpdateContent(
|
||||||
val info = expand.list
|
holder: ViewHolderState<Bundle>,
|
||||||
recyclerView.adapter = HomeChildItemAdapter(
|
item: HomeViewModel.ExpandableHomepageList,
|
||||||
info.list.toMutableList(),
|
position: Int
|
||||||
|
) {
|
||||||
|
val binding = holder.view
|
||||||
|
if (binding !is HomepageParentBinding) return
|
||||||
|
(binding.homeChildRecyclerview.adapter as? HomeChildItemAdapter)?.submitList(item.list.list)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindContent(
|
||||||
|
holder: ViewHolderState<Bundle>,
|
||||||
|
item: HomeViewModel.ExpandableHomepageList,
|
||||||
|
position: Int
|
||||||
|
) {
|
||||||
|
val startFocus = R.id.nav_rail_view
|
||||||
|
val endFocus = FOCUS_SELF
|
||||||
|
val binding = holder.view
|
||||||
|
if (binding !is HomepageParentBinding) return
|
||||||
|
val info = item.list
|
||||||
|
binding.apply {
|
||||||
|
homeChildRecyclerview.adapter = HomeChildItemAdapter(
|
||||||
|
fragment = fragment,
|
||||||
|
id = id + position + 100,
|
||||||
clickCallback = clickCallback,
|
clickCallback = clickCallback,
|
||||||
nextFocusUp = recyclerView.nextFocusUpId,
|
nextFocusUp = homeChildRecyclerview.nextFocusUpId,
|
||||||
nextFocusDown = recyclerView.nextFocusDownId,
|
nextFocusDown = homeChildRecyclerview.nextFocusDownId,
|
||||||
).apply {
|
).apply {
|
||||||
isHorizontal = info.isHorizontalImages
|
isHorizontal = info.isHorizontalImages
|
||||||
hasNext = expand.hasNext
|
hasNext = item.hasNext
|
||||||
|
submitList(item.list.list)
|
||||||
}
|
}
|
||||||
recyclerView.setLinearListLayout(
|
homeChildRecyclerview.setLinearListLayout(
|
||||||
isHorizontal = true,
|
isHorizontal = true,
|
||||||
nextLeft = startFocus,
|
nextLeft = startFocus,
|
||||||
nextRight = endFocus,
|
nextRight = endFocus,
|
||||||
)
|
)
|
||||||
title.text = info.name
|
homeChildMoreInfo.text = info.name
|
||||||
|
|
||||||
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
homeChildRecyclerview.addOnScrollListener(object :
|
||||||
|
RecyclerView.OnScrollListener() {
|
||||||
var expandCount = 0
|
var expandCount = 0
|
||||||
val name = expand.list.name
|
val name = item.list.name
|
||||||
|
|
||||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
override fun onScrollStateChanged(
|
||||||
|
recyclerView: RecyclerView,
|
||||||
|
newState: Int
|
||||||
|
) {
|
||||||
super.onScrollStateChanged(recyclerView, newState)
|
super.onScrollStateChanged(recyclerView, newState)
|
||||||
|
|
||||||
val adapter = recyclerView.adapter
|
val adapter = recyclerView.adapter
|
||||||
|
@ -237,27 +134,35 @@ open class ParentItemAdapter(
|
||||||
})
|
})
|
||||||
|
|
||||||
//(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
|
//(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
|
||||||
if (!isTrueTvSettings()) {
|
if (isLayout(PHONE)) {
|
||||||
title.setOnClickListener {
|
homeChildMoreInfo.setOnClickListener {
|
||||||
moreInfoClickCallback.invoke(expand)
|
moreInfoClickCallback.invoke(item)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SearchDiffCallback(
|
override fun onCreateContent(parent: ViewGroup): ParentItemHolder {
|
||||||
private val oldList: List<HomeViewModel.ExpandableHomepageList>,
|
val layoutResId = when {
|
||||||
private val newList: List<HomeViewModel.ExpandableHomepageList>
|
isLayout(TV) -> R.layout.homepage_parent_tv
|
||||||
) :
|
isLayout(EMULATOR) -> R.layout.homepage_parent_emulator
|
||||||
DiffUtil.Callback() {
|
else -> R.layout.homepage_parent
|
||||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
}
|
||||||
oldList[oldItemPosition].list.name == newList[newItemPosition].list.name
|
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
override fun getOldListSize() = oldList.size
|
val binding = try {
|
||||||
|
HomepageParentBinding.bind(inflater.inflate(layoutResId, parent, false))
|
||||||
override fun getNewListSize() = newList.size
|
} catch (t: Throwable) {
|
||||||
|
logError(t)
|
||||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
// just in case someone forgot we don't want to crash
|
||||||
oldList[oldItemPosition] == newList[newItemPosition]
|
HomepageParentBinding.inflate(inflater)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParentItemHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateList(newList: List<HomePageList>) {
|
||||||
|
submitList(newList.map { HomeViewModel.ExpandableHomepageList(it, 1, false) }
|
||||||
|
.toMutableList())
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package com.lagradost.cloudstream3.ui.home
|
package com.lagradost.cloudstream3.ui.home
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -7,6 +9,7 @@ import androidx.appcompat.widget.SearchView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewbinding.ViewBinding
|
import androidx.viewbinding.ViewBinding
|
||||||
|
@ -26,6 +29,7 @@ import com.lagradost.cloudstream3.databinding.FragmentHomeHeadTvBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.debugException
|
import com.lagradost.cloudstream3.mvvm.debugException
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
|
import com.lagradost.cloudstream3.ui.ViewHolderState
|
||||||
import com.lagradost.cloudstream3.ui.WatchType
|
import com.lagradost.cloudstream3.ui.WatchType
|
||||||
import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountSelectLinear
|
import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountSelectLinear
|
||||||
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.selectHomepage
|
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.selectHomepage
|
||||||
|
@ -36,8 +40,9 @@ import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes
|
||||||
|
@ -46,45 +51,26 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.populateChips
|
import com.lagradost.cloudstream3.utils.UIHelper.populateChips
|
||||||
|
|
||||||
class HomeParentItemAdapterPreview(
|
class HomeParentItemAdapterPreview(
|
||||||
items: MutableList<HomeViewModel.ExpandableHomepageList>,
|
override val fragment: Fragment,
|
||||||
private val viewModel: HomeViewModel,
|
private val viewModel: HomeViewModel,
|
||||||
) : ParentItemAdapter(items, clickCallback = {
|
) : ParentItemAdapter(fragment, id = "HomeParentItemAdapterPreview".hashCode(),
|
||||||
|
clickCallback = {
|
||||||
viewModel.click(it)
|
viewModel.click(it)
|
||||||
}, moreInfoClickCallback = {
|
}, moreInfoClickCallback = {
|
||||||
viewModel.popup(it)
|
viewModel.popup(it)
|
||||||
}, expandCallback = {
|
}, expandCallback = {
|
||||||
viewModel.expand(it)
|
viewModel.expand(it)
|
||||||
}) {
|
}) {
|
||||||
val headItems = 1
|
override val headers = 1
|
||||||
|
override fun onCreateHeader(parent: ViewGroup): ViewHolderState<Bundle> {
|
||||||
companion object {
|
|
||||||
private const val VIEW_TYPE_HEADER = 2
|
|
||||||
private const val VIEW_TYPE_ITEM = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int) = when (position) {
|
|
||||||
0 -> VIEW_TYPE_HEADER
|
|
||||||
else -> VIEW_TYPE_ITEM
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
||||||
when (holder) {
|
|
||||||
is HeaderViewHolder -> {}
|
|
||||||
else -> super.onBindViewHolder(holder, position - headItems)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
||||||
return when (viewType) {
|
|
||||||
VIEW_TYPE_HEADER -> {
|
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
val binding = if (isTvSettings()) FragmentHomeHeadTvBinding.inflate(
|
val binding = if (isLayout(TV or EMULATOR)) FragmentHomeHeadTvBinding.inflate(
|
||||||
inflater,
|
inflater,
|
||||||
parent,
|
parent,
|
||||||
false
|
false
|
||||||
) else FragmentHomeHeadBinding.inflate(inflater, parent, false)
|
) else FragmentHomeHeadBinding.inflate(inflater, parent, false)
|
||||||
|
|
||||||
if (binding is FragmentHomeHeadTvBinding && parent.context.isEmulatorSettings()) {
|
if (binding is FragmentHomeHeadTvBinding && isLayout(EMULATOR)) {
|
||||||
binding.homeBookmarkParentItemMoreInfo.isVisible = true
|
binding.homeBookmarkParentItemMoreInfo.isVisible = true
|
||||||
|
|
||||||
val marginInDp = 50
|
val marginInDp = 50
|
||||||
|
@ -105,54 +91,47 @@ class HomeParentItemAdapterPreview(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
HeaderViewHolder(
|
return HeaderViewHolder(binding, viewModel, fragment = fragment)
|
||||||
binding,
|
}
|
||||||
viewModel,
|
|
||||||
|
override fun onBindHeader(holder: ViewHolderState<Bundle>) {
|
||||||
|
(holder as? HeaderViewHolder)?.bind()
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HeaderViewHolder(
|
||||||
|
val binding: ViewBinding, val viewModel: HomeViewModel, fragment: Fragment,
|
||||||
|
) :
|
||||||
|
ViewHolderState<Bundle>(binding) {
|
||||||
|
|
||||||
|
override fun save(): Bundle =
|
||||||
|
Bundle().apply {
|
||||||
|
putParcelable(
|
||||||
|
"resumeRecyclerView",
|
||||||
|
resumeRecyclerView.layoutManager?.onSaveInstanceState()
|
||||||
)
|
)
|
||||||
|
putParcelable(
|
||||||
|
"bookmarkRecyclerView",
|
||||||
|
bookmarkRecyclerView.layoutManager?.onSaveInstanceState()
|
||||||
|
)
|
||||||
|
//putInt("previewViewpager", previewViewpager.currentItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
VIEW_TYPE_ITEM -> super.onCreateViewHolder(parent, viewType)
|
override fun restore(state: Bundle) {
|
||||||
else -> error("Unhandled viewType=$viewType")
|
state.getParcelable<Parcelable>("resumeRecyclerView")?.let { recycle ->
|
||||||
|
resumeRecyclerView.layoutManager?.onRestoreInstanceState(recycle)
|
||||||
}
|
}
|
||||||
|
state.getParcelable<Parcelable>("bookmarkRecyclerView")?.let { recycle ->
|
||||||
|
bookmarkRecyclerView.layoutManager?.onRestoreInstanceState(recycle)
|
||||||
|
}
|
||||||
|
//state.getInt("previewViewpager").let { recycle ->
|
||||||
|
// previewViewpager.setCurrentItem(recycle,true)
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
val previewAdapter = HomeScrollAdapter(fragment = fragment)
|
||||||
return super.getItemCount() + headItems
|
private val resumeAdapter = HomeChildItemAdapter(
|
||||||
}
|
fragment,
|
||||||
|
id = "resumeAdapter".hashCode(),
|
||||||
override fun getItemId(position: Int): Long {
|
|
||||||
if (position == 0) return 0//previewData.hashCode().toLong()
|
|
||||||
return super.getItemId(position - headItems)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
|
||||||
when (holder) {
|
|
||||||
is HeaderViewHolder -> {
|
|
||||||
holder.onViewDetachedFromWindow()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> super.onViewDetachedFromWindow(holder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
|
|
||||||
when (holder) {
|
|
||||||
is HeaderViewHolder -> {
|
|
||||||
holder.onViewAttachedToWindow()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> super.onViewAttachedToWindow(holder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HeaderViewHolder
|
|
||||||
constructor(
|
|
||||||
val binding: ViewBinding,
|
|
||||||
val viewModel: HomeViewModel,
|
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
|
||||||
private var previewAdapter: HomeScrollAdapter = HomeScrollAdapter()
|
|
||||||
private var resumeAdapter: HomeChildItemAdapter = HomeChildItemAdapter(
|
|
||||||
ArrayList(),
|
|
||||||
nextFocusUp = itemView.nextFocusUpId,
|
nextFocusUp = itemView.nextFocusUpId,
|
||||||
nextFocusDown = itemView.nextFocusDownId
|
nextFocusDown = itemView.nextFocusDownId
|
||||||
) { callback ->
|
) { callback ->
|
||||||
|
@ -207,8 +186,9 @@ class HomeParentItemAdapterPreview(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private var bookmarkAdapter: HomeChildItemAdapter = HomeChildItemAdapter(
|
private val bookmarkAdapter = HomeChildItemAdapter(
|
||||||
ArrayList(),
|
fragment,
|
||||||
|
id = "bookmarkAdapter".hashCode(),
|
||||||
nextFocusUp = itemView.nextFocusUpId,
|
nextFocusUp = itemView.nextFocusUpId,
|
||||||
nextFocusDown = itemView.nextFocusDownId
|
nextFocusDown = itemView.nextFocusDownId
|
||||||
) { callback ->
|
) { callback ->
|
||||||
|
@ -217,7 +197,10 @@ class HomeParentItemAdapterPreview(
|
||||||
return@HomeChildItemAdapter
|
return@HomeChildItemAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
(callback.view.context?.getActivity() as? MainActivity)?.loadPopup(callback.card, load = false)
|
(callback.view.context?.getActivity() as? MainActivity)?.loadPopup(
|
||||||
|
callback.card,
|
||||||
|
load = false
|
||||||
|
)
|
||||||
/*
|
/*
|
||||||
callback.view.context?.getActivity()?.showOptionSelectStringRes(
|
callback.view.context?.getActivity()?.showOptionSelectStringRes(
|
||||||
callback.view,
|
callback.view,
|
||||||
|
@ -267,7 +250,6 @@ class HomeParentItemAdapterPreview(
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private val previewViewpager: ViewPager2 =
|
private val previewViewpager: ViewPager2 =
|
||||||
itemView.findViewById(R.id.home_preview_viewpager)
|
itemView.findViewById(R.id.home_preview_viewpager)
|
||||||
|
|
||||||
|
@ -275,38 +257,24 @@ class HomeParentItemAdapterPreview(
|
||||||
itemView.findViewById(R.id.home_preview_viewpager_text)
|
itemView.findViewById(R.id.home_preview_viewpager_text)
|
||||||
|
|
||||||
// private val previewHeader: FrameLayout = itemView.findViewById(R.id.home_preview)
|
// private val previewHeader: FrameLayout = itemView.findViewById(R.id.home_preview)
|
||||||
private var resumeHolder: View = itemView.findViewById(R.id.home_watch_holder)
|
private val resumeHolder: View = itemView.findViewById(R.id.home_watch_holder)
|
||||||
private var resumeRecyclerView: RecyclerView =
|
private val resumeRecyclerView: RecyclerView =
|
||||||
itemView.findViewById(R.id.home_watch_child_recyclerview)
|
itemView.findViewById(R.id.home_watch_child_recyclerview)
|
||||||
private var bookmarkHolder: View = itemView.findViewById(R.id.home_bookmarked_holder)
|
private val bookmarkHolder: View = itemView.findViewById(R.id.home_bookmarked_holder)
|
||||||
private var bookmarkRecyclerView: RecyclerView =
|
private val bookmarkRecyclerView: RecyclerView =
|
||||||
itemView.findViewById(R.id.home_bookmarked_child_recyclerview)
|
itemView.findViewById(R.id.home_bookmarked_child_recyclerview)
|
||||||
|
|
||||||
private var homeAccount: View? =
|
private val homeAccount: View? = itemView.findViewById(R.id.home_preview_switch_account)
|
||||||
itemView.findViewById(R.id.home_preview_switch_account)
|
private val alternativeHomeAccount: View? =
|
||||||
private var alternativeHomeAccount: View? =
|
|
||||||
itemView.findViewById(R.id.alternative_switch_account)
|
itemView.findViewById(R.id.alternative_switch_account)
|
||||||
|
|
||||||
private var topPadding: View? = itemView.findViewById(R.id.home_padding)
|
private val topPadding: View? = itemView.findViewById(R.id.home_padding)
|
||||||
|
|
||||||
private var alternativeAccountPadding: View? = itemView.findViewById(R.id.alternative_account_padding)
|
private val alternativeAccountPadding: View? =
|
||||||
|
itemView.findViewById(R.id.alternative_account_padding)
|
||||||
|
|
||||||
private val homeNonePadding: View = itemView.findViewById(R.id.home_none_padding)
|
private val homeNonePadding: View = itemView.findViewById(R.id.home_none_padding)
|
||||||
|
|
||||||
private val previewCallback: ViewPager2.OnPageChangeCallback =
|
|
||||||
object : ViewPager2.OnPageChangeCallback() {
|
|
||||||
override fun onPageSelected(position: Int) {
|
|
||||||
previewAdapter.apply {
|
|
||||||
if (position >= itemCount - 1 && hasMoreItems) {
|
|
||||||
hasMoreItems = false // don't make two requests
|
|
||||||
viewModel.loadMoreHomeScrollResponses()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val item = previewAdapter.getItem(position) ?: return
|
|
||||||
onSelect(item, position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onSelect(item: LoadResponse, position: Int) {
|
fun onSelect(item: LoadResponse, position: Int) {
|
||||||
(binding as? FragmentHomeHeadTvBinding)?.apply {
|
(binding as? FragmentHomeHeadTvBinding)?.apply {
|
||||||
homePreviewDescription.isGone =
|
homePreviewDescription.isGone =
|
||||||
|
@ -379,14 +347,14 @@ class HomeParentItemAdapterPreview(
|
||||||
|
|
||||||
homePreviewBookmark.setOnClickListener { fab ->
|
homePreviewBookmark.setOnClickListener { fab ->
|
||||||
fab.context.getActivity()?.showBottomDialog(
|
fab.context.getActivity()?.showBottomDialog(
|
||||||
WatchType.values()
|
WatchType.entries
|
||||||
.map { fab.context.getString(it.stringRes) }
|
.map { fab.context.getString(it.stringRes) }
|
||||||
.toList(),
|
.toList(),
|
||||||
DataStoreHelper.getResultWatchState(id).ordinal,
|
DataStoreHelper.getResultWatchState(id).ordinal,
|
||||||
fab.context.getString(R.string.action_add_to_bookmarks),
|
fab.context.getString(R.string.action_add_to_bookmarks),
|
||||||
showApply = false,
|
showApply = false,
|
||||||
{}) {
|
{}) {
|
||||||
val newValue = WatchType.values()[it]
|
val newValue = WatchType.entries[it]
|
||||||
|
|
||||||
ResultViewModel2().updateWatchStatus(
|
ResultViewModel2().updateWatchStatus(
|
||||||
newValue,
|
newValue,
|
||||||
|
@ -411,40 +379,24 @@ class HomeParentItemAdapterPreview(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onViewDetachedFromWindow() {
|
private val previewCallback: ViewPager2.OnPageChangeCallback =
|
||||||
|
object : ViewPager2.OnPageChangeCallback() {
|
||||||
|
override fun onPageSelected(position: Int) {
|
||||||
|
previewAdapter.apply {
|
||||||
|
if (position >= itemCount - 1 && hasMoreItems) {
|
||||||
|
hasMoreItems = false // don't make two requests
|
||||||
|
viewModel.loadMoreHomeScrollResponses()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val item = previewAdapter.getItemOrNull(position) ?: return
|
||||||
|
onSelect(item, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewDetachedFromWindow() {
|
||||||
previewViewpager.unregisterOnPageChangeCallback(previewCallback)
|
previewViewpager.unregisterOnPageChangeCallback(previewCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onViewAttachedToWindow() {
|
|
||||||
previewViewpager.registerOnPageChangeCallback(previewCallback)
|
|
||||||
|
|
||||||
binding.root.findViewTreeLifecycleOwner()?.apply {
|
|
||||||
observe(viewModel.preview) {
|
|
||||||
updatePreview(it)
|
|
||||||
}
|
|
||||||
if (binding is FragmentHomeHeadTvBinding) {
|
|
||||||
observe(viewModel.apiName) { name ->
|
|
||||||
binding.homePreviewChangeApi.text = name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
observe(viewModel.resumeWatching) {
|
|
||||||
updateResume(it)
|
|
||||||
}
|
|
||||||
observe(viewModel.bookmarks) {
|
|
||||||
updateBookmarks(it)
|
|
||||||
}
|
|
||||||
observe(viewModel.availableWatchStatusTypes) { (checked, visible) ->
|
|
||||||
for ((chip, watch) in toggleList) {
|
|
||||||
chip.apply {
|
|
||||||
isVisible = visible.contains(watch)
|
|
||||||
isChecked = checked.contains(watch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toggleListHolder?.isGone = visible.isEmpty()
|
|
||||||
}
|
|
||||||
} ?: debugException { "Expected findViewTreeLifecycleOwner" }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val toggleList = listOf<Pair<Chip, WatchType>>(
|
private val toggleList = listOf<Pair<Chip, WatchType>>(
|
||||||
Pair(itemView.findViewById(R.id.home_type_watching_btt), WatchType.WATCHING),
|
Pair(itemView.findViewById(R.id.home_type_watching_btt), WatchType.WATCHING),
|
||||||
Pair(itemView.findViewById(R.id.home_type_completed_btt), WatchType.COMPLETED),
|
Pair(itemView.findViewById(R.id.home_type_completed_btt), WatchType.COMPLETED),
|
||||||
|
@ -455,6 +407,8 @@ class HomeParentItemAdapterPreview(
|
||||||
|
|
||||||
private val toggleListHolder: ChipGroup? = itemView.findViewById(R.id.home_type_holder)
|
private val toggleListHolder: ChipGroup? = itemView.findViewById(R.id.home_type_holder)
|
||||||
|
|
||||||
|
fun bind() = Unit
|
||||||
|
|
||||||
init {
|
init {
|
||||||
previewViewpager.setPageTransformer(HomeScrollTransformer())
|
previewViewpager.setPageTransformer(HomeScrollTransformer())
|
||||||
|
|
||||||
|
@ -561,7 +515,9 @@ class HomeParentItemAdapterPreview(
|
||||||
|
|
||||||
when (preview) {
|
when (preview) {
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
if (!previewAdapter.setItems(
|
previewAdapter.submitList(preview.value.second)
|
||||||
|
previewAdapter.hasMoreItems = preview.value.first
|
||||||
|
/*if (!.setItems(
|
||||||
preview.value.second,
|
preview.value.second,
|
||||||
preview.value.first
|
preview.value.first
|
||||||
)
|
)
|
||||||
|
@ -573,15 +529,16 @@ class HomeParentItemAdapterPreview(
|
||||||
previewViewpager.fakeDragBy(1f)
|
previewViewpager.fakeDragBy(1f)
|
||||||
previewViewpager.endFakeDrag()
|
previewViewpager.endFakeDrag()
|
||||||
previewCallback.onPageSelected(0)
|
previewCallback.onPageSelected(0)
|
||||||
|
//previewHeader.isVisible = true
|
||||||
|
}*/
|
||||||
|
|
||||||
previewViewpager.isVisible = true
|
previewViewpager.isVisible = true
|
||||||
previewViewpagerText.isVisible = true
|
previewViewpagerText.isVisible = true
|
||||||
alternativeAccountPadding?.isVisible = false
|
alternativeAccountPadding?.isVisible = false
|
||||||
//previewHeader.isVisible = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
previewAdapter.setItems(listOf(), false)
|
previewAdapter.submitList(listOf())
|
||||||
previewViewpager.setCurrentItem(0, false)
|
previewViewpager.setCurrentItem(0, false)
|
||||||
previewViewpager.isVisible = false
|
previewViewpager.isVisible = false
|
||||||
previewViewpagerText.isVisible = false
|
previewViewpagerText.isVisible = false
|
||||||
|
@ -593,12 +550,12 @@ class HomeParentItemAdapterPreview(
|
||||||
|
|
||||||
private fun updateResume(resumeWatching: List<SearchResponse>) {
|
private fun updateResume(resumeWatching: List<SearchResponse>) {
|
||||||
resumeHolder.isVisible = resumeWatching.isNotEmpty()
|
resumeHolder.isVisible = resumeWatching.isNotEmpty()
|
||||||
resumeAdapter.updateList(resumeWatching)
|
resumeAdapter.submitList(resumeWatching)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
binding is FragmentHomeHeadBinding ||
|
binding is FragmentHomeHeadBinding ||
|
||||||
binding is FragmentHomeHeadTvBinding &&
|
binding is FragmentHomeHeadTvBinding &&
|
||||||
binding.root.context.isEmulatorSettings()
|
isLayout(EMULATOR)
|
||||||
) {
|
) {
|
||||||
val title = (binding as? FragmentHomeHeadBinding)?.homeWatchParentItemTitle
|
val title = (binding as? FragmentHomeHeadBinding)?.homeWatchParentItemTitle
|
||||||
?: (binding as? FragmentHomeHeadTvBinding)?.homeWatchParentItemTitle
|
?: (binding as? FragmentHomeHeadTvBinding)?.homeWatchParentItemTitle
|
||||||
|
@ -623,12 +580,12 @@ class HomeParentItemAdapterPreview(
|
||||||
private fun updateBookmarks(data: Pair<Boolean, List<SearchResponse>>) {
|
private fun updateBookmarks(data: Pair<Boolean, List<SearchResponse>>) {
|
||||||
val (visible, list) = data
|
val (visible, list) = data
|
||||||
bookmarkHolder.isVisible = visible
|
bookmarkHolder.isVisible = visible
|
||||||
bookmarkAdapter.updateList(list)
|
bookmarkAdapter.submitList(list)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
binding is FragmentHomeHeadBinding ||
|
binding is FragmentHomeHeadBinding ||
|
||||||
binding is FragmentHomeHeadTvBinding &&
|
binding is FragmentHomeHeadTvBinding &&
|
||||||
binding.root.context.isEmulatorSettings()
|
isLayout(EMULATOR)
|
||||||
) {
|
) {
|
||||||
val title = (binding as? FragmentHomeHeadBinding)?.homeBookmarkParentItemTitle
|
val title = (binding as? FragmentHomeHeadBinding)?.homeBookmarkParentItemTitle
|
||||||
?: (binding as? FragmentHomeHeadTvBinding)?.homeBookmarkParentItemTitle
|
?: (binding as? FragmentHomeHeadTvBinding)?.homeBookmarkParentItemTitle
|
||||||
|
@ -653,5 +610,35 @@ class HomeParentItemAdapterPreview(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onViewAttachedToWindow() {
|
||||||
|
previewViewpager.registerOnPageChangeCallback(previewCallback)
|
||||||
|
|
||||||
|
binding.root.findViewTreeLifecycleOwner()?.apply {
|
||||||
|
observe(viewModel.preview) {
|
||||||
|
updatePreview(it)
|
||||||
|
}
|
||||||
|
if (binding is FragmentHomeHeadTvBinding) {
|
||||||
|
observe(viewModel.apiName) { name ->
|
||||||
|
binding.homePreviewChangeApi.text = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
observe(viewModel.resumeWatching) {
|
||||||
|
updateResume(it)
|
||||||
|
}
|
||||||
|
observe(viewModel.bookmarks) {
|
||||||
|
updateBookmarks(it)
|
||||||
|
}
|
||||||
|
observe(viewModel.availableWatchStatusTypes) { (checked, visible) ->
|
||||||
|
for ((chip, watch) in toggleList) {
|
||||||
|
chip.apply {
|
||||||
|
isVisible = visible.contains(watch)
|
||||||
|
isChecked = checked.contains(watch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toggleListHolder?.isGone = visible.isEmpty()
|
||||||
|
}
|
||||||
|
} ?: debugException { "Expected findViewTreeLifecycleOwner" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,86 +4,56 @@ import android.content.res.Configuration
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import androidx.viewbinding.ViewBinding
|
|
||||||
import com.lagradost.cloudstream3.LoadResponse
|
import com.lagradost.cloudstream3.LoadResponse
|
||||||
import com.lagradost.cloudstream3.databinding.HomeScrollViewBinding
|
import com.lagradost.cloudstream3.databinding.HomeScrollViewBinding
|
||||||
import com.lagradost.cloudstream3.databinding.HomeScrollViewTvBinding
|
import com.lagradost.cloudstream3.databinding.HomeScrollViewTvBinding
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.NoStateAdapter
|
||||||
|
import com.lagradost.cloudstream3.ui.ViewHolderState
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
|
|
||||||
class HomeScrollAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
class HomeScrollAdapter(
|
||||||
private var items: MutableList<LoadResponse> = mutableListOf()
|
fragment: Fragment
|
||||||
|
) : NoStateAdapter<LoadResponse>(fragment) {
|
||||||
var hasMoreItems: Boolean = false
|
var hasMoreItems: Boolean = false
|
||||||
|
|
||||||
fun getItem(position: Int): LoadResponse? {
|
override fun onCreateContent(parent: ViewGroup): ViewHolderState<Any> {
|
||||||
return items.getOrNull(position)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setItems(newItems: List<LoadResponse>, hasNext: Boolean): Boolean {
|
|
||||||
val isSame = newItems.firstOrNull()?.url == items.firstOrNull()?.url
|
|
||||||
hasMoreItems = hasNext
|
|
||||||
|
|
||||||
val diffResult = DiffUtil.calculateDiff(
|
|
||||||
HomeScrollDiffCallback(this.items, newItems)
|
|
||||||
)
|
|
||||||
|
|
||||||
items.clear()
|
|
||||||
items.addAll(newItems)
|
|
||||||
|
|
||||||
|
|
||||||
diffResult.dispatchUpdatesTo(this)
|
|
||||||
|
|
||||||
return isSame
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
||||||
val inflater = LayoutInflater.from(parent.context)
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
val binding = if (isTvSettings()) {
|
val binding = if (isLayout(TV or EMULATOR)) {
|
||||||
HomeScrollViewTvBinding.inflate(inflater, parent, false)
|
HomeScrollViewTvBinding.inflate(inflater, parent, false)
|
||||||
} else {
|
} else {
|
||||||
HomeScrollViewBinding.inflate(inflater, parent, false)
|
HomeScrollViewBinding.inflate(inflater, parent, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return CardViewHolder(
|
return ViewHolderState(binding)
|
||||||
binding,
|
|
||||||
//forceHorizontalPosters
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindContent(
|
||||||
when (holder) {
|
holder: ViewHolderState<Any>,
|
||||||
is CardViewHolder -> {
|
item: LoadResponse,
|
||||||
holder.bind(items[position])
|
position: Int,
|
||||||
}
|
) {
|
||||||
}
|
val binding = holder.view
|
||||||
}
|
val itemView = holder.itemView
|
||||||
|
|
||||||
class CardViewHolder
|
|
||||||
constructor(
|
|
||||||
val binding: ViewBinding,
|
|
||||||
//private val forceHorizontalPosters: Boolean? = null
|
|
||||||
) :
|
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
|
||||||
|
|
||||||
fun bind(card: LoadResponse) {
|
|
||||||
val isHorizontal =
|
val isHorizontal =
|
||||||
binding is HomeScrollViewTvBinding || itemView.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
binding is HomeScrollViewTvBinding || itemView.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
|
|
||||||
val posterUrl =
|
val posterUrl =
|
||||||
if (isHorizontal) card.backgroundPosterUrl ?: card.posterUrl else card.posterUrl
|
if (isHorizontal) item.backgroundPosterUrl ?: item.posterUrl else item.posterUrl
|
||||||
?: card.backgroundPosterUrl
|
?: item.backgroundPosterUrl
|
||||||
|
|
||||||
when (binding) {
|
when (binding) {
|
||||||
is HomeScrollViewBinding -> {
|
is HomeScrollViewBinding -> {
|
||||||
binding.homeScrollPreview.setImage(posterUrl)
|
binding.homeScrollPreview.setImage(posterUrl)
|
||||||
binding.homeScrollPreviewTags.apply {
|
binding.homeScrollPreviewTags.apply {
|
||||||
text = card.tags?.joinToString(" • ") ?: ""
|
text = item.tags?.joinToString(" • ") ?: ""
|
||||||
isGone = card.tags.isNullOrEmpty()
|
isGone = item.tags.isNullOrEmpty()
|
||||||
maxLines = 2
|
maxLines = 2
|
||||||
}
|
}
|
||||||
binding.homeScrollPreviewTitle.text = card.name
|
binding.homeScrollPreviewTitle.text = item.name
|
||||||
}
|
}
|
||||||
|
|
||||||
is HomeScrollViewTvBinding -> {
|
is HomeScrollViewTvBinding -> {
|
||||||
|
@ -92,24 +62,3 @@ class HomeScrollAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HomeScrollDiffCallback(
|
|
||||||
private val oldList: List<LoadResponse>,
|
|
||||||
private val newList: List<LoadResponse>
|
|
||||||
) :
|
|
||||||
DiffUtil.Callback() {
|
|
||||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
|
||||||
oldList[oldItemPosition].url == newList[newItemPosition].url
|
|
||||||
|
|
||||||
override fun getOldListSize() = oldList.size
|
|
||||||
|
|
||||||
override fun getNewListSize() = newList.size
|
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
|
||||||
oldList[oldItemPosition] == newList[newItemPosition]
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return items.size
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -34,7 +34,8 @@ import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.addProgramsToContinueWatching
|
import com.lagradost.cloudstream3.utils.AppUtils.addProgramsToContinueWatching
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
@ -52,6 +53,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
|
||||||
class HomeViewModel : ViewModel() {
|
class HomeViewModel : ViewModel() {
|
||||||
|
@ -124,7 +126,7 @@ class HomeViewModel : ViewModel() {
|
||||||
|
|
||||||
private val _resumeWatching = MutableLiveData<List<SearchResponse>>()
|
private val _resumeWatching = MutableLiveData<List<SearchResponse>>()
|
||||||
private val _preview = MutableLiveData<Resource<Pair<Boolean, List<LoadResponse>>>>()
|
private val _preview = MutableLiveData<Resource<Pair<Boolean, List<LoadResponse>>>>()
|
||||||
private val previewResponses = mutableListOf<LoadResponse>()
|
private val previewResponses = CopyOnWriteArrayList<LoadResponse>()
|
||||||
private val previewResponsesAdded = mutableSetOf<String>()
|
private val previewResponsesAdded = mutableSetOf<String>()
|
||||||
|
|
||||||
val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching
|
val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching
|
||||||
|
@ -132,7 +134,7 @@ class HomeViewModel : ViewModel() {
|
||||||
|
|
||||||
private fun loadResumeWatching() = viewModelScope.launchSafe {
|
private fun loadResumeWatching() = viewModelScope.launchSafe {
|
||||||
val resumeWatchingResult = getResumeWatching()
|
val resumeWatchingResult = getResumeWatching()
|
||||||
if (isTrueTvSettings() && resumeWatchingResult != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (isLayout(TV) && resumeWatchingResult != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
ioSafe {
|
ioSafe {
|
||||||
// this WILL crash on non tvs, so keep this inside a try catch
|
// this WILL crash on non tvs, so keep this inside a try catch
|
||||||
activity?.addProgramsToContinueWatching(resumeWatchingResult)
|
activity?.addProgramsToContinueWatching(resumeWatchingResult)
|
||||||
|
@ -326,7 +328,13 @@ class HomeViewModel : ViewModel() {
|
||||||
val filteredList =
|
val filteredList =
|
||||||
context?.filterHomePageListByFilmQuality(list) ?: list
|
context?.filterHomePageListByFilmQuality(list) ?: list
|
||||||
expandable[list.name] =
|
expandable[list.name] =
|
||||||
ExpandableHomepageList(filteredList, 1, home.hasNext)
|
ExpandableHomepageList(
|
||||||
|
filteredList.copy(
|
||||||
|
list = CopyOnWriteArrayList(
|
||||||
|
filteredList.list
|
||||||
|
)
|
||||||
|
), 1, home.hasNext
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,8 +349,7 @@ class HomeViewModel : ViewModel() {
|
||||||
val currentList =
|
val currentList =
|
||||||
items.shuffled().filter { it.list.isNotEmpty() }
|
items.shuffled().filter { it.list.isNotEmpty() }
|
||||||
.flatMap { it.list }
|
.flatMap { it.list }
|
||||||
.distinctBy { it.url }
|
.distinctBy { it.url }.toList()
|
||||||
.toList()
|
|
||||||
|
|
||||||
if (currentList.isNotEmpty()) {
|
if (currentList.isNotEmpty()) {
|
||||||
val randomItems =
|
val randomItems =
|
||||||
|
|
|
@ -49,7 +49,10 @@ import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
|
||||||
import com.lagradost.cloudstream3.ui.result.txt
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment
|
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.loadResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.reduceDragSensitivity
|
import com.lagradost.cloudstream3.utils.AppUtils.reduceDragSensitivity
|
||||||
|
@ -57,6 +60,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
const val LIBRARY_FOLDER = "library_folder"
|
const val LIBRARY_FOLDER = "library_folder"
|
||||||
|
@ -101,7 +105,7 @@ class LibraryFragment : Fragment() {
|
||||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
val layout =
|
val layout =
|
||||||
if (SettingsFragment.isTvSettings()) R.layout.fragment_library_tv else R.layout.fragment_library
|
if (isLayout(TV or EMULATOR)) R.layout.fragment_library_tv else R.layout.fragment_library
|
||||||
val root = inflater.inflate(layout, container, false)
|
val root = inflater.inflate(layout, container, false)
|
||||||
binding = try {
|
binding = try {
|
||||||
FragmentLibraryBinding.bind(root)
|
FragmentLibraryBinding.bind(root)
|
||||||
|
@ -160,7 +164,8 @@ class LibraryFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the color for the search exit icon to the correct theme text color
|
// Set the color for the search exit icon to the correct theme text color
|
||||||
val searchExitIcon = binding?.mainSearch?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
|
val searchExitIcon =
|
||||||
|
binding?.mainSearch?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
|
||||||
val searchExitIconColor = TypedValue()
|
val searchExitIconColor = TypedValue()
|
||||||
|
|
||||||
activity?.theme?.resolveAttribute(android.R.attr.textColor, searchExitIconColor, true)
|
activity?.theme?.resolveAttribute(android.R.attr.textColor, searchExitIconColor, true)
|
||||||
|
@ -220,7 +225,7 @@ class LibraryFragment : Fragment() {
|
||||||
settingsManager.getBoolean(
|
settingsManager.getBoolean(
|
||||||
getString(R.string.random_button_key),
|
getString(R.string.random_button_key),
|
||||||
false
|
false
|
||||||
) && !SettingsFragment.isTvSettings()
|
) && isLayout(PHONE)
|
||||||
binding?.libraryRandom?.visibility = View.GONE
|
binding?.libraryRandom?.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,9 +312,8 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
binding?.viewpager?.setPageTransformer(LibraryScrollTransformer())
|
binding?.viewpager?.setPageTransformer(LibraryScrollTransformer())
|
||||||
|
|
||||||
binding?.viewpager?.adapter =
|
binding?.viewpager?.adapter = ViewpagerAdapter(
|
||||||
binding?.viewpager?.adapter ?: ViewpagerAdapter(
|
fragment = this,
|
||||||
mutableListOf(),
|
|
||||||
{ isScrollingDown: Boolean ->
|
{ isScrollingDown: Boolean ->
|
||||||
if (isScrollingDown) {
|
if (isScrollingDown) {
|
||||||
binding?.sortFab?.shrink()
|
binding?.sortFab?.shrink()
|
||||||
|
@ -332,7 +336,10 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
when (searchClickCallback.action) {
|
when (searchClickCallback.action) {
|
||||||
SEARCH_ACTION_SHOW_METADATA -> {
|
SEARCH_ACTION_SHOW_METADATA -> {
|
||||||
(activity as? MainActivity)?.loadPopup(searchClickCallback.card, load = false)
|
(activity as? MainActivity)?.loadPopup(
|
||||||
|
searchClickCallback.card,
|
||||||
|
load = false
|
||||||
|
)
|
||||||
/*activity?.showPluginSelectionDialog(
|
/*activity?.showPluginSelectionDialog(
|
||||||
syncId,
|
syncId,
|
||||||
syncName,
|
syncName,
|
||||||
|
@ -390,7 +397,11 @@ class LibraryFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(viewpager.adapter as? ViewpagerAdapter)?.pages = pages
|
(viewpager.adapter as? ViewpagerAdapter)?.submitList(pages.map {
|
||||||
|
it.copy(
|
||||||
|
items = CopyOnWriteArrayList(it.items)
|
||||||
|
)
|
||||||
|
})
|
||||||
//fix focus on the viewpager itself
|
//fix focus on the viewpager itself
|
||||||
(viewpager.getChildAt(0) as RecyclerView).apply {
|
(viewpager.getChildAt(0) as RecyclerView).apply {
|
||||||
tag = "tv_no_focus_tag"
|
tag = "tv_no_focus_tag"
|
||||||
|
@ -398,10 +409,10 @@ class LibraryFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using notifyItemRangeChanged keeps the animations when sorting
|
// Using notifyItemRangeChanged keeps the animations when sorting
|
||||||
viewpager.adapter?.notifyItemRangeChanged(
|
/*viewpager.adapter?.notifyItemRangeChanged(
|
||||||
0,
|
0,
|
||||||
viewpager.adapter?.itemCount ?: 0
|
viewpager.adapter?.itemCount ?: 0
|
||||||
)
|
)*/
|
||||||
|
|
||||||
libraryViewModel.currentPage.value?.let { page ->
|
libraryViewModel.currentPage.value?.let { page ->
|
||||||
binding?.viewpager?.setCurrentItem(page, false)
|
binding?.viewpager?.setCurrentItem(page, false)
|
||||||
|
@ -459,12 +470,14 @@ class LibraryFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}.attach()
|
}.attach()
|
||||||
|
|
||||||
binding?.libraryTabLayout?.addOnTabSelectedListener(object: TabLayout.OnTabSelectedListener {
|
binding?.libraryTabLayout?.addOnTabSelectedListener(object :
|
||||||
|
TabLayout.OnTabSelectedListener {
|
||||||
override fun onTabSelected(tab: TabLayout.Tab?) {
|
override fun onTabSelected(tab: TabLayout.Tab?) {
|
||||||
binding?.libraryTabLayout?.selectedTabPosition?.let { page ->
|
binding?.libraryTabLayout?.selectedTabPosition?.let { page ->
|
||||||
libraryViewModel.switchPage(page)
|
libraryViewModel.switchPage(page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTabUnselected(tab: TabLayout.Tab?) = Unit
|
override fun onTabUnselected(tab: TabLayout.Tab?) = Unit
|
||||||
override fun onTabReselected(tab: TabLayout.Tab?) = Unit
|
override fun onTabReselected(tab: TabLayout.Tab?) = Unit
|
||||||
})
|
})
|
||||||
|
@ -564,8 +577,9 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
(binding?.viewpager?.adapter as? ViewpagerAdapter)?.rebind()
|
binding?.viewpager?.adapter?.notifyDataSetChanged()
|
||||||
super.onConfigurationChanged(newConfig)
|
super.onConfigurationChanged(newConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,7 +113,7 @@ class LibraryViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val desiredSortingMethod =
|
val desiredSortingMethod =
|
||||||
ListSorting.values().getOrNull(DataStoreHelper.librarySortingMode)
|
ListSorting.entries.getOrNull(DataStoreHelper.librarySortingMode)
|
||||||
if (desiredSortingMethod != null && library.supportedListSorting.contains(desiredSortingMethod)) {
|
if (desiredSortingMethod != null && library.supportedListSorting.contains(desiredSortingMethod)) {
|
||||||
sort(desiredSortingMethod, null, pages)
|
sort(desiredSortingMethod, null, pages)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,71 +1,95 @@
|
||||||
package com.lagradost.cloudstream3.ui.library
|
package com.lagradost.cloudstream3.ui.library
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.os.Bundle
|
||||||
|
import android.os.Parcelable
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.doOnAttach
|
import androidx.core.view.doOnAttach
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.RecyclerView.OnFlingListener
|
import androidx.recyclerview.widget.RecyclerView.OnFlingListener
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.databinding.LibraryViewpagerPageBinding
|
import com.lagradost.cloudstream3.databinding.LibraryViewpagerPageBinding
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
|
import com.lagradost.cloudstream3.ui.BaseAdapter
|
||||||
|
import com.lagradost.cloudstream3.ui.BaseDiffCallback
|
||||||
|
import com.lagradost.cloudstream3.ui.ViewHolderState
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||||
|
|
||||||
|
class ViewpagerAdapterViewHolderState(val binding: LibraryViewpagerPageBinding) :
|
||||||
|
ViewHolderState<Bundle>(binding) {
|
||||||
|
override fun save(): Bundle =
|
||||||
|
Bundle().apply {
|
||||||
|
putParcelable(
|
||||||
|
"pageRecyclerview",
|
||||||
|
binding.pageRecyclerview.layoutManager?.onSaveInstanceState()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun restore(state: Bundle) {
|
||||||
|
state.getParcelable<Parcelable>("pageRecyclerview")?.let { recycle ->
|
||||||
|
binding.pageRecyclerview.layoutManager?.onRestoreInstanceState(recycle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ViewpagerAdapter(
|
class ViewpagerAdapter(
|
||||||
var pages: List<SyncAPI.Page>,
|
fragment: Fragment,
|
||||||
val scrollCallback: (isScrollingDown: Boolean) -> Unit,
|
val scrollCallback: (isScrollingDown: Boolean) -> Unit,
|
||||||
val clickCallback: (SearchClickCallback) -> Unit
|
val clickCallback: (SearchClickCallback) -> Unit
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : BaseAdapter<SyncAPI.Page, Bundle>(fragment,
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
id = "ViewpagerAdapter".hashCode(),
|
||||||
return PageViewHolder(
|
diffCallback = BaseDiffCallback(
|
||||||
|
itemSame = { a, b ->
|
||||||
|
a.title == b.title
|
||||||
|
},
|
||||||
|
contentSame = { a, b ->
|
||||||
|
a.items == b.items && a.title == b.title
|
||||||
|
}
|
||||||
|
)) {
|
||||||
|
override fun onCreateContent(parent: ViewGroup): ViewHolderState<Bundle> {
|
||||||
|
return ViewpagerAdapterViewHolderState(
|
||||||
LibraryViewpagerPageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
LibraryViewpagerPageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onUpdateContent(
|
||||||
when (holder) {
|
holder: ViewHolderState<Bundle>,
|
||||||
is PageViewHolder -> {
|
item: SyncAPI.Page,
|
||||||
holder.bind(pages[position], position, unbound.remove(position))
|
position: Int
|
||||||
}
|
) {
|
||||||
}
|
val binding = holder.view
|
||||||
|
if (binding !is LibraryViewpagerPageBinding) return
|
||||||
|
(binding.pageRecyclerview.adapter as? PageAdapter)?.updateList(item.items)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val unbound = mutableSetOf<Int>()
|
override fun onBindContent(holder: ViewHolderState<Bundle>, item: SyncAPI.Page, position: Int) {
|
||||||
|
val binding = holder.view
|
||||||
|
if (binding !is LibraryViewpagerPageBinding) return
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to mark all pages for re-binding and forces all items to be refreshed
|
|
||||||
* Without this the pages will still use the same adapters
|
|
||||||
**/
|
|
||||||
fun rebind() {
|
|
||||||
unbound.addAll(0..pages.size)
|
|
||||||
this.notifyItemRangeChanged(0, pages.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class PageViewHolder(private val binding: LibraryViewpagerPageBinding) :
|
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
|
||||||
fun bind(page: SyncAPI.Page, position: Int, rebind: Boolean) {
|
|
||||||
binding.pageRecyclerview.tag = position
|
binding.pageRecyclerview.tag = position
|
||||||
binding.pageRecyclerview.apply {
|
binding.pageRecyclerview.apply {
|
||||||
spanCount =
|
spanCount =
|
||||||
this@PageViewHolder.itemView.context.getSpanCount() ?: 3
|
binding.root.context.getSpanCount() ?: 3
|
||||||
if (adapter == null || rebind) {
|
if (adapter == null) { // || rebind
|
||||||
// Only add the items after it has been attached since the items rely on ItemWidth
|
// Only add the items after it has been attached since the items rely on ItemWidth
|
||||||
// Which is only determined after the recyclerview is attached.
|
// Which is only determined after the recyclerview is attached.
|
||||||
// If this fails then item height becomes 0 when there is only one item
|
// If this fails then item height becomes 0 when there is only one item
|
||||||
doOnAttach {
|
doOnAttach {
|
||||||
adapter = PageAdapter(
|
adapter = PageAdapter(
|
||||||
page.items.toMutableList(),
|
item.items.toMutableList(),
|
||||||
this,
|
this,
|
||||||
clickCallback
|
clickCallback
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
(adapter as? PageAdapter)?.updateList(page.items)
|
(adapter as? PageAdapter)?.updateList(item.items)
|
||||||
scrollToPosition(0)
|
// scrollToPosition(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
@ -73,7 +97,7 @@ class ViewpagerAdapter(
|
||||||
val diff = scrollY - oldScrollY
|
val diff = scrollY - oldScrollY
|
||||||
|
|
||||||
//Expand the top Appbar based on scroll direction up/down, simulate phone behavior
|
//Expand the top Appbar based on scroll direction up/down, simulate phone behavior
|
||||||
if (SettingsFragment.isTvSettings()) {
|
if (isLayout(TV or EMULATOR)) {
|
||||||
binding.root.rootView.findViewById<AppBarLayout>(R.id.search_bar)
|
binding.root.rootView.findViewById<AppBarLayout>(R.id.search_bar)
|
||||||
.apply {
|
.apply {
|
||||||
if (diff <= 0)
|
if (diff <= 0)
|
||||||
|
@ -97,8 +121,3 @@ class ViewpagerAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return pages.size
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -46,6 +46,10 @@ import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvid
|
||||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
||||||
import com.lagradost.cloudstream3.ui.result.setText
|
import com.lagradost.cloudstream3.ui.result.setText
|
||||||
import com.lagradost.cloudstream3.ui.result.txt
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.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
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
|
import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
|
@ -77,7 +81,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
private var isVerticalOrientation: Boolean = false
|
private var isVerticalOrientation: Boolean = false
|
||||||
protected open var lockRotation = true
|
protected open var lockRotation = true
|
||||||
protected open var isFullScreenPlayer = true
|
protected open var isFullScreenPlayer = true
|
||||||
protected open var isTv = false
|
|
||||||
protected var playerBinding: PlayerCustomLayoutBinding? = null
|
protected var playerBinding: PlayerCustomLayoutBinding? = null
|
||||||
|
|
||||||
private var durationMode : Boolean by UserPreferenceDelegate("duration_mode", false)
|
private var durationMode : Boolean by UserPreferenceDelegate("duration_mode", false)
|
||||||
|
@ -1204,7 +1207,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
|
|
||||||
// netflix capture back and hide ~monke
|
// netflix capture back and hide ~monke
|
||||||
KeyEvent.KEYCODE_BACK -> {
|
KeyEvent.KEYCODE_BACK -> {
|
||||||
if (isShowing && isTv) {
|
if (isShowing && isLayout(TV or EMULATOR)) {
|
||||||
onClickChange()
|
onClickChange()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -1514,7 +1517,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// cs3 is peak media center
|
// cs3 is peak media center
|
||||||
setRemainingTimeCounter(durationMode || SettingsFragment.isTrueTvSettings())
|
setRemainingTimeCounter(durationMode || Globals.isLayout(Globals.TV))
|
||||||
playerBinding?.exoPosition?.doOnTextChanged { _, _, _, _ ->
|
playerBinding?.exoPosition?.doOnTextChanged { _, _, _, _ ->
|
||||||
updateRemainingTime()
|
updateRemainingTime()
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,10 @@ import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSub
|
||||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
||||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
|
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
|
||||||
import com.lagradost.cloudstream3.ui.result.*
|
import com.lagradost.cloudstream3.ui.result.*
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY
|
import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1
|
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1
|
||||||
import com.lagradost.cloudstream3.utils.*
|
import com.lagradost.cloudstream3.utils.*
|
||||||
|
@ -1275,8 +1278,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
// this is used instead of layout-television to follow the settings and some TV devices are not classified as TV for some reason
|
// this is used instead of layout-television to follow the settings and some TV devices are not classified as TV for some reason
|
||||||
isTv = isTvSettings()
|
layout = if (isLayout(TV or EMULATOR)) R.layout.fragment_player_tv else R.layout.fragment_player
|
||||||
layout = if (isTv) R.layout.fragment_player_tv else R.layout.fragment_player
|
|
||||||
|
|
||||||
viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java]
|
viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java]
|
||||||
sync = ViewModelProvider(this)[SyncViewModel::class.java]
|
sync = ViewModelProvider(this)[SyncViewModel::class.java]
|
||||||
|
|
|
@ -9,6 +9,9 @@ import android.util.Log
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.core.graphics.scale
|
import androidx.core.graphics.scale
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
@ -63,7 +66,7 @@ interface IPreviewGenerator {
|
||||||
companion object {
|
companion object {
|
||||||
fun new(): IPreviewGenerator {
|
fun new(): IPreviewGenerator {
|
||||||
/** because TV has low ram + not show we disable this for now */
|
/** because TV has low ram + not show we disable this for now */
|
||||||
return if (SettingsFragment.isTrueTvSettings()) {
|
return if (isLayout(TV)) {
|
||||||
empty()
|
empty()
|
||||||
} else {
|
} else {
|
||||||
PreviewGenerator()
|
PreviewGenerator()
|
||||||
|
|
|
@ -34,7 +34,8 @@ import com.lagradost.cloudstream3.ui.search.SearchAdapter
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchViewModel
|
import com.lagradost.cloudstream3.ui.search.SearchViewModel
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.ownShow
|
import com.lagradost.cloudstream3.utils.AppUtils.ownShow
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper
|
import com.lagradost.cloudstream3.utils.UIHelper
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
|
@ -173,7 +174,7 @@ class QuickSearchFragment : Fragment() {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding?.quickSearchMasterRecycler?.adapter =
|
binding?.quickSearchMasterRecycler?.adapter =
|
||||||
ParentItemAdapter(mutableListOf(), { callback ->
|
ParentItemAdapter(fragment = this, id = "quickSearchMasterRecycler".hashCode(), { callback ->
|
||||||
SearchHelper.handleSearchClickCallback(callback)
|
SearchHelper.handleSearchClickCallback(callback)
|
||||||
//when (callback.action) {
|
//when (callback.action) {
|
||||||
//SEARCH_ACTION_LOAD -> {
|
//SEARCH_ACTION_LOAD -> {
|
||||||
|
@ -277,7 +278,7 @@ class QuickSearchFragment : Fragment() {
|
||||||
activity?.popCurrentPage()
|
activity?.popCurrentPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTrueTvSettings()) {
|
if (isLayout(TV)) {
|
||||||
binding?.quickSearch?.requestFocus()
|
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_DOWNLOAD
|
||||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
|
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
|
@ -172,15 +174,13 @@ class EpisodeAdapter(
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(card: ResultEpisode) {
|
fun bind(card: ResultEpisode) {
|
||||||
localCard = card
|
localCard = card
|
||||||
|
|
||||||
val setWidth =
|
val setWidth =
|
||||||
if (isTvSettings()) TV_EP_SIZE_LARGE.toPx else ViewGroup.LayoutParams.MATCH_PARENT
|
if (isLayout(TV or EMULATOR)) TV_EP_SIZE_LARGE.toPx else ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
|
||||||
binding.episodeLinHolder.layoutParams.width = setWidth
|
binding.episodeLinHolder.layoutParams.width = setWidth
|
||||||
binding.episodeHolderLarge.layoutParams.width = setWidth
|
binding.episodeHolderLarge.layoutParams.width = setWidth
|
||||||
binding.episodeHolder.layoutParams.width = setWidth
|
binding.episodeHolder.layoutParams.width = setWidth
|
||||||
|
|
||||||
val isTrueTv = isTrueTvSettings()
|
|
||||||
|
|
||||||
binding.apply {
|
binding.apply {
|
||||||
downloadButton.isVisible = hasDownloadSupport
|
downloadButton.isVisible = hasDownloadSupport
|
||||||
|
@ -246,12 +246,21 @@ class EpisodeAdapter(
|
||||||
episodeDescript.apply {
|
episodeDescript.apply {
|
||||||
text = card.description.html()
|
text = card.description.html()
|
||||||
isGone = text.isNullOrBlank()
|
isGone = text.isNullOrBlank()
|
||||||
|
|
||||||
|
var isExpanded = false
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
|
if (isLayout(TV)) {
|
||||||
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
|
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 {
|
episodePoster.setOnClickListener {
|
||||||
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
||||||
}
|
}
|
||||||
|
@ -266,7 +275,7 @@ class EpisodeAdapter(
|
||||||
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTrueTv) {
|
if (isLayout(TV)) {
|
||||||
itemView.isFocusable = true
|
itemView.isFocusable = true
|
||||||
itemView.isFocusableInTouchMode = true
|
itemView.isFocusableInTouchMode = true
|
||||||
//itemView.touchscreenBlocksFocus = false
|
//itemView.touchscreenBlocksFocus = false
|
||||||
|
@ -291,11 +300,9 @@ class EpisodeAdapter(
|
||||||
) : RecyclerView.ViewHolder(binding.root) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(card: ResultEpisode) {
|
fun bind(card: ResultEpisode) {
|
||||||
val isTrueTv = isTrueTvSettings()
|
|
||||||
|
|
||||||
binding.episodeHolder.layoutParams.apply {
|
binding.episodeHolder.layoutParams.apply {
|
||||||
width =
|
width =
|
||||||
if (isTvSettings()) TV_EP_SIZE_SMALL.toPx else ViewGroup.LayoutParams.MATCH_PARENT
|
if (isLayout(TV or EMULATOR)) TV_EP_SIZE_SMALL.toPx else ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.apply {
|
binding.apply {
|
||||||
|
@ -352,7 +359,7 @@ class EpisodeAdapter(
|
||||||
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTrueTv) {
|
if (isLayout(TV)) {
|
||||||
itemView.isFocusable = true
|
itemView.isFocusable = true
|
||||||
itemView.isFocusableInTouchMode = true
|
itemView.isFocusableInTouchMode = true
|
||||||
//itemView.touchscreenBlocksFocus = false
|
//itemView.touchscreenBlocksFocus = false
|
||||||
|
|
|
@ -5,7 +5,8 @@ import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.databinding.ResultMiniImageBinding
|
import com.lagradost.cloudstream3.databinding.ResultMiniImageBinding
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
|
|
||||||
/*
|
/*
|
||||||
class ImageAdapter(context: Context, val resource: Int) : ArrayAdapter<Int>(context, resource) {
|
class ImageAdapter(context: Context, val resource: Int) : ArrayAdapter<Int>(context, resource) {
|
||||||
|
@ -83,7 +84,7 @@ class ImageAdapter(
|
||||||
this.nextFocusUpId = nextFocusUp
|
this.nextFocusUpId = nextFocusUp
|
||||||
}
|
}
|
||||||
if (clickCallback != null) {
|
if (clickCallback != null) {
|
||||||
if (isTrueTvSettings()) {
|
if (isLayout(TV)) {
|
||||||
isClickable = true
|
isClickable = true
|
||||||
isLongClickable = true
|
isLongClickable = true
|
||||||
isFocusable = true
|
isFocusable = true
|
||||||
|
|
|
@ -2,9 +2,6 @@ package com.lagradost.cloudstream3.ui.result
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.ClipData
|
|
||||||
import android.content.ClipboardManager
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Rect
|
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
|
||||||
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
||||||
import com.lagradost.cloudstream3.CommonActivity
|
import com.lagradost.cloudstream3.CommonActivity
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
|
||||||
import com.lagradost.cloudstream3.DubStatus
|
import com.lagradost.cloudstream3.DubStatus
|
||||||
import com.lagradost.cloudstream3.LoadResponse
|
import com.lagradost.cloudstream3.LoadResponse
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
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.SearchAdapter
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
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.isCastApiAvailable
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
|
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.SingleSelectionHelper.showDialog
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper
|
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.colorFromAttribute
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
|
@ -688,14 +683,15 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
||||||
resultNextAiringTime.setText(d.nextAiringDate)
|
resultNextAiringTime.setText(d.nextAiringDate)
|
||||||
resultPoster.setImage(d.posterImage)
|
resultPoster.setImage(d.posterImage)
|
||||||
resultPosterBackground.setImage(d.posterBackgroundImage)
|
resultPosterBackground.setImage(d.posterBackgroundImage)
|
||||||
resultDescription.setTextHtml(d.plotText)
|
|
||||||
resultDescription.setOnClickListener {
|
var isExpanded = false
|
||||||
activity?.let { activity ->
|
resultDescription.apply {
|
||||||
activity.showBottomDialogText(
|
setTextHtml(d.plotText)
|
||||||
d.titleText.asString(activity),
|
setOnClickListener {
|
||||||
d.plotText.asString(activity).html(),
|
isExpanded = !isExpanded
|
||||||
{}
|
maxLines = if (isExpanded) {
|
||||||
)
|
Integer.MAX_VALUE
|
||||||
|
} else 10
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -758,14 +754,8 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
||||||
resultReloadConnectionOpenInBrowser.isVisible = data is Resource.Failure
|
resultReloadConnectionOpenInBrowser.isVisible = data is Resource.Failure
|
||||||
|
|
||||||
resultTitle.setOnLongClickListener {
|
resultTitle.setOnLongClickListener {
|
||||||
val titleToCopy = resultTitle.text
|
clipboardHelper(txt(R.string.title), resultTitle.text)
|
||||||
val clipboardManager =
|
true
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -901,14 +891,6 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
||||||
observe(viewModel.recommendations) { recommendations ->
|
observe(viewModel.recommendations) { recommendations ->
|
||||||
setRecommendations(recommendations, null)
|
setRecommendations(recommendations, null)
|
||||||
}
|
}
|
||||||
observe(viewModel.episodeSynopsis) { description ->
|
|
||||||
activity?.let { activity ->
|
|
||||||
activity.showBottomDialogText(
|
|
||||||
activity.getString(R.string.synopsis),
|
|
||||||
description.html()
|
|
||||||
) { viewModel.releaseEpisodeSynopsis() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -39,7 +39,10 @@ import com.lagradost.cloudstream3.ui.result.ResultFragment.updateUIEvent
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchAdapter
|
import com.lagradost.cloudstream3.ui.search.SearchAdapter
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isRtl
|
import com.lagradost.cloudstream3.utils.AppUtils.isRtl
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
||||||
|
@ -308,9 +311,10 @@ class ResultFragmentTv : Fragment() {
|
||||||
resultEpisodesShowButton to resultEpisodesShowText
|
resultEpisodesShowButton to resultEpisodesShowText
|
||||||
).forEach { (button , text) ->
|
).forEach { (button , text) ->
|
||||||
|
|
||||||
button.setOnFocusChangeListener { _, hasFocus ->
|
button.setOnFocusChangeListener { view, hasFocus ->
|
||||||
if (!hasFocus) {
|
if (!hasFocus) {
|
||||||
text.isSelected = false
|
text.isSelected = false
|
||||||
|
if (view.id == R.id.result_episodes_show_button) toggleEpisodes(false)
|
||||||
return@setOnFocusChangeListener
|
return@setOnFocusChangeListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,10 +380,6 @@ class ResultFragmentTv : Fragment() {
|
||||||
|
|
||||||
resultMetaSite.isFocusable = false
|
resultMetaSite.isFocusable = false
|
||||||
|
|
||||||
//resultReloadConnectionOpenInBrowser.setOnClickListener {view ->
|
|
||||||
// view.context?.openBrowser(storedData?.url ?: return@setOnClickListener, fallbackWebview = true)
|
|
||||||
//}
|
|
||||||
|
|
||||||
resultSeasonSelection.setAdapter()
|
resultSeasonSelection.setAdapter()
|
||||||
resultRangeSelection.setAdapter()
|
resultRangeSelection.setAdapter()
|
||||||
resultDubSelection.setAdapter()
|
resultDubSelection.setAdapter()
|
||||||
|
@ -457,11 +457,12 @@ class ResultFragmentTv : Fragment() {
|
||||||
observeNullable(viewModel.resumeWatching) { resume ->
|
observeNullable(viewModel.resumeWatching) { resume ->
|
||||||
binding?.apply {
|
binding?.apply {
|
||||||
|
|
||||||
// > resultResumeSeries is visible when not null
|
|
||||||
if (resume == null) {
|
if (resume == null) {
|
||||||
resultResumeSeries.isVisible = false
|
|
||||||
return@observeNullable
|
return@observeNullable
|
||||||
}
|
}
|
||||||
|
resultResumeSeries.isVisible = true
|
||||||
|
resultPlayMovie.isVisible = false
|
||||||
|
resultPlaySeries.isVisible = false
|
||||||
|
|
||||||
// show progress no matter if series or movie
|
// show progress no matter if series or movie
|
||||||
resume.progress?.let { progress ->
|
resume.progress?.let { progress ->
|
||||||
|
@ -476,10 +477,6 @@ class ResultFragmentTv : Fragment() {
|
||||||
resultResumeProgressHolder.isVisible = false
|
resultResumeProgressHolder.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
resultPlayMovie.isVisible = false
|
|
||||||
resultPlaySeries.isVisible = false
|
|
||||||
resultResumeSeries.isVisible = true
|
|
||||||
|
|
||||||
focusPlayButton()
|
focusPlayButton()
|
||||||
// Stops last button right focus if it is a movie
|
// Stops last button right focus if it is a movie
|
||||||
if (resume.isMovie)
|
if (resume.isMovie)
|
||||||
|
@ -603,7 +600,7 @@ class ResultFragmentTv : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
observeNullable(viewModel.subscribeStatus) { isSubscribed ->
|
observeNullable(viewModel.subscribeStatus) { isSubscribed ->
|
||||||
binding?.resultSubscribe?.isVisible = isSubscribed != null && requireContext().isEmulatorSettings()
|
binding?.resultSubscribe?.isVisible = isSubscribed != null && isLayout(EMULATOR)
|
||||||
binding?.resultSubscribeButton?.apply {
|
binding?.resultSubscribeButton?.apply {
|
||||||
|
|
||||||
if (isSubscribed == null) return@observeNullable
|
if (isSubscribed == null) return@observeNullable
|
||||||
|
@ -646,15 +643,14 @@ class ResultFragmentTv : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
observeNullable(viewModel.movie) { data ->
|
observeNullable(viewModel.movie) { data ->
|
||||||
if (data == null) return@observeNullable
|
if (data == null ) {
|
||||||
|
return@observeNullable
|
||||||
|
}
|
||||||
|
|
||||||
binding?.apply {
|
binding?.apply {
|
||||||
resultPlayMovie.isVisible = (data is Resource.Success) && !comingSoon
|
|
||||||
resultPlaySeries.isVisible = false
|
|
||||||
resultEpisodesShow.isVisible = false
|
|
||||||
|
|
||||||
(data as? Resource.Success)?.value?.let { (text, ep) ->
|
(data as? Resource.Success)?.value?.let { (text, ep) ->
|
||||||
//resultPlayMovieText.setText(text)
|
|
||||||
resultPlayMovieButton.setOnClickListener {
|
resultPlayMovieButton.setOnClickListener {
|
||||||
viewModel.handleAction(
|
viewModel.handleAction(
|
||||||
EpisodeClickEvent(ACTION_CLICK_DEFAULT, ep)
|
EpisodeClickEvent(ACTION_CLICK_DEFAULT, ep)
|
||||||
|
@ -666,14 +662,17 @@ class ResultFragmentTv : Fragment() {
|
||||||
)
|
)
|
||||||
return@setOnLongClickListener true
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
//focusPlayButton()
|
|
||||||
|
resultPlayMovie.isVisible = !comingSoon && resultResumeSeries.isGone
|
||||||
|
if (comingSoon)
|
||||||
|
resultBookmarkButton.requestFocus()
|
||||||
|
else
|
||||||
resultPlayMovieButton.requestFocus()
|
resultPlayMovieButton.requestFocus()
|
||||||
|
|
||||||
// Stops last button right focus
|
// Stops last button right focus
|
||||||
resultSearchButton.nextFocusRightId = R.id.result_search_Button
|
resultSearchButton.nextFocusRightId = R.id.result_search_Button
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//focusPlayButton()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
observeNullable(viewModel.selectPopup) { popup ->
|
observeNullable(viewModel.selectPopup) { popup ->
|
||||||
|
@ -754,6 +753,8 @@ class ResultFragmentTv : Fragment() {
|
||||||
observe(viewModel.recommendations) { recommendations ->
|
observe(viewModel.recommendations) { recommendations ->
|
||||||
setRecommendations(recommendations, null)
|
setRecommendations(recommendations, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isLayout(TV)) {
|
||||||
observe(viewModel.episodeSynopsis) { description ->
|
observe(viewModel.episodeSynopsis) { description ->
|
||||||
view.context?.let { ctx ->
|
view.context?.let { ctx ->
|
||||||
val builder: AlertDialog.Builder =
|
val builder: AlertDialog.Builder =
|
||||||
|
@ -766,6 +767,7 @@ class ResultFragmentTv : Fragment() {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Used to request focus the first time the episodes are loaded.
|
// Used to request focus the first time the episodes are loaded.
|
||||||
var hasLoadedEpisodesOnce = false
|
var hasLoadedEpisodesOnce = false
|
||||||
|
@ -774,16 +776,14 @@ class ResultFragmentTv : Fragment() {
|
||||||
|
|
||||||
binding?.apply {
|
binding?.apply {
|
||||||
|
|
||||||
resultPlayMovie.isVisible = false
|
if (comingSoon)
|
||||||
resultPlaySeries.isVisible = true && !comingSoon
|
resultBookmarkButton.requestFocus()
|
||||||
resultEpisodes.isVisible = true && !comingSoon
|
|
||||||
resultEpisodesShow.isVisible = true && !comingSoon
|
|
||||||
|
|
||||||
// resultEpisodeLoading.isVisible = episodes is Resource.Loading
|
// resultEpisodeLoading.isVisible = episodes is Resource.Loading
|
||||||
if (episodes is Resource.Success) {
|
if (episodes is Resource.Success) {
|
||||||
val first = episodes.value.firstOrNull()
|
val first = episodes.value.firstOrNull()
|
||||||
if (first != null) {
|
if (first != null) {
|
||||||
resultPlaySeriesText.text = //"${getString(R.string.season_short)}${first.season}:${getString(R.string.episode_short)}${first.episode}"
|
resultPlaySeriesText.text =
|
||||||
when {
|
when {
|
||||||
first.season != null ->
|
first.season != null ->
|
||||||
"${getString(R.string.season_short)}${first.season}:${getString(R.string.episode_short)}${first.episode}"
|
"${getString(R.string.season_short)}${first.season}:${getString(R.string.episode_short)}${first.episode}"
|
||||||
|
@ -805,8 +805,9 @@ class ResultFragmentTv : Fragment() {
|
||||||
}
|
}
|
||||||
if (!hasLoadedEpisodesOnce) {
|
if (!hasLoadedEpisodesOnce) {
|
||||||
hasLoadedEpisodesOnce = true
|
hasLoadedEpisodesOnce = true
|
||||||
focusPlayButton()
|
resultPlaySeries.isVisible = resultResumeSeries.isGone && !comingSoon
|
||||||
resultPlaySeries.requestFocus()
|
resultEpisodesShow.isVisible = true && !comingSoon
|
||||||
|
resultPlaySeriesButton.requestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -874,8 +875,17 @@ class ResultFragmentTv : Fragment() {
|
||||||
resultNextAiring.setText(d.nextAiringEpisode)
|
resultNextAiring.setText(d.nextAiringEpisode)
|
||||||
resultNextAiringTime.setText(d.nextAiringDate)
|
resultNextAiringTime.setText(d.nextAiringDate)
|
||||||
resultPoster.setImage(d.posterImage)
|
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 ->
|
view.context?.let { ctx ->
|
||||||
val builder: AlertDialog.Builder =
|
val builder: AlertDialog.Builder =
|
||||||
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
|
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
|
||||||
|
@ -884,6 +894,8 @@ class ResultFragmentTv : Fragment() {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val error = listOf(
|
val error = listOf(
|
||||||
R.drawable.profile_bg_dark_blue,
|
R.drawable.profile_bg_dark_blue,
|
||||||
|
@ -904,9 +916,6 @@ class ResultFragmentTv : Fragment() {
|
||||||
)
|
)
|
||||||
comingSoon = d.comingSoon
|
comingSoon = d.comingSoon
|
||||||
resultTvComingSoon.isVisible = d.comingSoon
|
resultTvComingSoon.isVisible = d.comingSoon
|
||||||
resultPlayMovie.isGone = d.comingSoon
|
|
||||||
resultPlaySeries.isGone = d.comingSoon
|
|
||||||
resultDataHolder.isGone = d.comingSoon
|
|
||||||
|
|
||||||
UIHelper.populateChips(resultTag, d.tags)
|
UIHelper.populateChips(resultTag, d.tags)
|
||||||
resultCastItems.isGone = d.actors.isNullOrEmpty()
|
resultCastItems.isGone = d.actors.isNullOrEmpty()
|
||||||
|
|
|
@ -81,12 +81,12 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setSubscribedData
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setSubscribedData
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setVideoWatchState
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setVideoWatchState
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.updateSubscribedData
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.updateSubscribedData
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
/** This starts at 1 */
|
/** This starts at 1 */
|
||||||
data class EpisodeRange(
|
data class EpisodeRange(
|
||||||
// used to index data
|
// used to index data
|
||||||
|
@ -928,15 +928,20 @@ class ResultViewModel2 : ViewModel() {
|
||||||
) {
|
) {
|
||||||
val isSubscribed = _subscribeStatus.value ?: return
|
val isSubscribed = _subscribeStatus.value ?: return
|
||||||
val response = currentResponse ?: return
|
val response = currentResponse ?: return
|
||||||
if (response !is EpisodeResponse) return
|
|
||||||
|
|
||||||
val currentId = currentId ?: return
|
val currentId = currentId ?: return
|
||||||
|
|
||||||
|
// This might be a bit confusing, but even if the loadresponse is not a EpisodeResponse
|
||||||
|
// _subscribeStatus might be true.
|
||||||
|
|
||||||
if (isSubscribed) {
|
if (isSubscribed) {
|
||||||
removeSubscribedData(currentId)
|
removeSubscribedData(currentId)
|
||||||
statusChangedCallback?.invoke(false)
|
statusChangedCallback?.invoke(false)
|
||||||
_subscribeStatus.postValue(false)
|
_subscribeStatus.postValue(if (response is EpisodeResponse) false else null)
|
||||||
|
MainActivity.reloadLibraryEvent(true)
|
||||||
} else {
|
} else {
|
||||||
|
if (response !is EpisodeResponse) {
|
||||||
|
return
|
||||||
|
}
|
||||||
checkAndWarnDuplicates(
|
checkAndWarnDuplicates(
|
||||||
context,
|
context,
|
||||||
LibraryListType.SUBSCRIPTIONS,
|
LibraryListType.SUBSCRIPTIONS,
|
||||||
|
@ -981,8 +986,8 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
|
|
||||||
_subscribeStatus.postValue(true)
|
_subscribeStatus.postValue(true)
|
||||||
|
|
||||||
statusChangedCallback?.invoke(true)
|
statusChangedCallback?.invoke(true)
|
||||||
|
MainActivity.reloadLibraryEvent(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1693,14 +1698,8 @@ class ResultViewModel2 : ViewModel() {
|
||||||
LoadType.ExternalApp,
|
LoadType.ExternalApp,
|
||||||
txt(R.string.episode_action_copy_link)
|
txt(R.string.episode_action_copy_link)
|
||||||
) { (result, index) ->
|
) { (result, index) ->
|
||||||
val act = activity ?: return@acquireSingleLink
|
|
||||||
val serviceClipboard =
|
|
||||||
(act.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)
|
|
||||||
?: return@acquireSingleLink
|
|
||||||
val link = result.links[index]
|
val link = result.links[index]
|
||||||
val clip = ClipData.newPlainText(link.name, link.url)
|
clipboardHelper(txt(link.name), link.url)
|
||||||
serviceClipboard.setPrimaryClip(clip)
|
|
||||||
showToast(R.string.copy_link_toast, Toast.LENGTH_SHORT)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2052,12 +2051,15 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun postSubscription(loadResponse: LoadResponse) {
|
private fun postSubscription(loadResponse: LoadResponse) {
|
||||||
if (loadResponse.isEpisodeBased()) {
|
|
||||||
val id = loadResponse.getId()
|
val id = loadResponse.getId()
|
||||||
val data = getSubscribedData(id)
|
val data = getSubscribedData(id)
|
||||||
|
if (loadResponse.isEpisodeBased()) {
|
||||||
updateSubscribedData(id, data, loadResponse as? EpisodeResponse)
|
updateSubscribedData(id, data, loadResponse as? EpisodeResponse)
|
||||||
val isSubscribed = data != null
|
_subscribeStatus.postValue(data != null)
|
||||||
_subscribeStatus.postValue(isSubscribed)
|
}
|
||||||
|
// lets say that we have subscribed, then we must be able to unsubscribe no matter what
|
||||||
|
else if (data != null) {
|
||||||
|
_subscribeStatus.postValue(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2591,6 +2593,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
override var posterHeaders: Map<String, String>? = null,
|
override var posterHeaders: Map<String, String>? = null,
|
||||||
override var backgroundPosterUrl: String? = null,
|
override var backgroundPosterUrl: String? = null,
|
||||||
override var contentRating: String? = null,
|
override var contentRating: String? = null,
|
||||||
|
val id : Int?,
|
||||||
) : LoadResponse
|
) : LoadResponse
|
||||||
|
|
||||||
fun loadSmall(activity: Activity?, searchResponse : SearchResponse) = ioSafe {
|
fun loadSmall(activity: Activity?, searchResponse : SearchResponse) = ioSafe {
|
||||||
|
@ -2600,7 +2603,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
val api = APIHolder.getApiFromNameNull(searchResponse.apiName) ?: APIHolder.getApiFromUrlNull(searchResponse.url) ?: APIRepository.noneApi
|
val api = APIHolder.getApiFromNameNull(searchResponse.apiName) ?: APIHolder.getApiFromUrlNull(searchResponse.url) ?: APIRepository.noneApi
|
||||||
val repo = APIRepository(api)
|
val repo = APIRepository(api)
|
||||||
val response = LoadResponseFromSearch(name = searchResponse.name, url = searchResponse.url, apiName = api.name, type = searchResponse.type ?: TvType.Others,
|
val response = LoadResponseFromSearch(name = searchResponse.name, url = searchResponse.url, apiName = api.name, type = searchResponse.type ?: TvType.Others,
|
||||||
posterUrl = searchResponse.posterUrl).apply {
|
posterUrl = searchResponse.posterUrl, id = searchResponse.id).apply {
|
||||||
if (searchResponse is SyncAPI.LibraryItem) {
|
if (searchResponse is SyncAPI.LibraryItem) {
|
||||||
this.plot = searchResponse.plot
|
this.plot = searchResponse.plot
|
||||||
this.rating = searchResponse.personalRating?.times(100) ?: searchResponse.rating
|
this.rating = searchResponse.personalRating?.times(100) ?: searchResponse.rating
|
||||||
|
@ -2612,12 +2615,14 @@ class ResultViewModel2 : ViewModel() {
|
||||||
this.tags = searchResponse.tags
|
this.tags = searchResponse.tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val mainId = searchResponse.id ?: response.getId()
|
val mainId = response.getId()
|
||||||
|
|
||||||
postSuccessful(
|
postSuccessful(
|
||||||
loadResponse = response,
|
loadResponse = response,
|
||||||
mainId = mainId,
|
mainId = mainId,
|
||||||
apiRepository = repo, updateEpisodes = false, updateFillers = false)
|
apiRepository = repo,
|
||||||
|
updateEpisodes = false,
|
||||||
|
updateFillers = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(
|
fun load(
|
||||||
|
|
|
@ -6,7 +6,8 @@ import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.lagradost.cloudstream3.databinding.ResultSelectionBinding
|
import com.lagradost.cloudstream3.databinding.ResultSelectionBinding
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
|
|
||||||
typealias SelectData = Pair<UiText?, Any>
|
typealias SelectData = Pair<UiText?, Any>
|
||||||
|
|
||||||
|
@ -72,8 +73,7 @@ class SelectAdaptor(val callback: (Any) -> Unit) : RecyclerView.Adapter<Recycler
|
||||||
fun bind(
|
fun bind(
|
||||||
data: SelectData, isSelected: Boolean, callback: (Any) -> Unit
|
data: SelectData, isSelected: Boolean, callback: (Any) -> Unit
|
||||||
) {
|
) {
|
||||||
val isTrueTv = isTrueTvSettings()
|
if (isLayout(TV)) {
|
||||||
if (isTrueTv) {
|
|
||||||
item.isFocusable = true
|
item.isFocusable = true
|
||||||
item.isFocusableInTouchMode = true
|
item.isFocusableInTouchMode = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,13 @@ sealed class UiText {
|
||||||
|
|
||||||
data class DynamicString(val value: String) : UiText() {
|
data class DynamicString(val value: String) : UiText() {
|
||||||
override fun toString(): String = value
|
override fun toString(): String = value
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is DynamicString) return false
|
||||||
|
return this.value == other.value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int = value.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
class StringResource(
|
class StringResource(
|
||||||
|
@ -27,6 +34,16 @@ sealed class UiText {
|
||||||
) : UiText() {
|
) : UiText() {
|
||||||
override fun toString(): String =
|
override fun toString(): String =
|
||||||
"resId = $resId\nargs = ${args.toList().map { "(${it::class} = $it)" }}"
|
"resId = $resId\nargs = ${args.toList().map { "(${it::class} = $it)" }}"
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is StringResource) return false
|
||||||
|
return this.resId == other.resId && this.args == other.args
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = resId
|
||||||
|
result = 31 * result + args.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun asStringNull(context: Context?): String? {
|
fun asStringNull(context: Context?): String? {
|
||||||
|
|
|
@ -46,6 +46,7 @@ import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
|
import com.lagradost.cloudstream3.ui.BaseAdapter
|
||||||
import com.lagradost.cloudstream3.ui.home.HomeFragment
|
import com.lagradost.cloudstream3.ui.home.HomeFragment
|
||||||
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips
|
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips
|
||||||
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.currentSpan
|
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.currentSpan
|
||||||
|
@ -54,8 +55,9 @@ import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.updateChips
|
||||||
import com.lagradost.cloudstream3.ui.home.ParentItemAdapter
|
import com.lagradost.cloudstream3.ui.home.ParentItemAdapter
|
||||||
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
||||||
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.ownHide
|
import com.lagradost.cloudstream3.utils.AppUtils.ownHide
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.ownShow
|
import com.lagradost.cloudstream3.utils.AppUtils.ownShow
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
||||||
|
@ -107,13 +109,16 @@ class SearchFragment : Fragment() {
|
||||||
)
|
)
|
||||||
bottomSheetDialog?.ownShow()
|
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)
|
val root = inflater.inflate(layout, container, false)
|
||||||
// TODO TRYCATCH
|
FragmentSearchBinding.bind(root)
|
||||||
binding = FragmentSearchBinding.bind(root)
|
} catch (t : Throwable) {
|
||||||
|
FragmentSearchBinding.inflate(inflater)
|
||||||
|
}
|
||||||
|
|
||||||
return root
|
return binding?.root
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fixGrid() {
|
private fun fixGrid() {
|
||||||
|
@ -157,7 +162,8 @@ class SearchFragment : Fragment() {
|
||||||
**/
|
**/
|
||||||
fun search(query: String?) {
|
fun search(query: String?) {
|
||||||
if (query == null) return
|
if (query == null) return
|
||||||
|
// don't resume state from prev search
|
||||||
|
(binding?.searchMasterRecycler?.adapter as? BaseAdapter<*,*>)?.clear()
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
val default = enumValues<TvType>().sorted().filter { it != TvType.NSFW }
|
val default = enumValues<TvType>().sorted().filter { it != TvType.NSFW }
|
||||||
.map { it.ordinal.toString() }.toSet()
|
.map { it.ordinal.toString() }.toSet()
|
||||||
|
@ -369,7 +375,7 @@ class SearchFragment : Fragment() {
|
||||||
|
|
||||||
selectedSearchTypes = DataStoreHelper.searchPreferenceTags.toMutableList()
|
selectedSearchTypes = DataStoreHelper.searchPreferenceTags.toMutableList()
|
||||||
|
|
||||||
if (isTrueTvSettings()) {
|
if (isLayout(TV)) {
|
||||||
binding?.searchFilter?.isFocusable = true
|
binding?.searchFilter?.isFocusable = true
|
||||||
binding?.searchFilter?.isFocusableInTouchMode = true
|
binding?.searchFilter?.isFocusableInTouchMode = true
|
||||||
}
|
}
|
||||||
|
@ -502,8 +508,8 @@ class SearchFragment : Fragment() {
|
||||||
}*/
|
}*/
|
||||||
//main_search.onActionViewExpanded()*/
|
//main_search.onActionViewExpanded()*/
|
||||||
|
|
||||||
val masterAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
|
val masterAdapter =
|
||||||
ParentItemAdapter(mutableListOf(), { callback ->
|
ParentItemAdapter(fragment = this, id = "masterAdapter".hashCode(), { callback ->
|
||||||
SearchHelper.handleSearchClickCallback(callback)
|
SearchHelper.handleSearchClickCallback(callback)
|
||||||
}, { item ->
|
}, { item ->
|
||||||
bottomSheetDialog = activity?.loadHomepageList(item, dismissCallback = {
|
bottomSheetDialog = activity?.loadHomepageList(item, dismissCallback = {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.lagradost.cloudstream3.ui.search
|
package com.lagradost.cloudstream3.ui.search
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.lagradost.cloudstream3.CommonActivity.activity
|
import com.lagradost.cloudstream3.CommonActivity.activity
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
|
@ -10,7 +9,8 @@ import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_PLAY_FILE
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
|
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
|
||||||
import com.lagradost.cloudstream3.ui.result.START_ACTION_LOAD_EP
|
import com.lagradost.cloudstream3.ui.result.START_ACTION_LOAD_EP
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
|
@ -56,7 +56,7 @@ object SearchHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SEARCH_ACTION_SHOW_METADATA -> {
|
SEARCH_ACTION_SHOW_METADATA -> {
|
||||||
if(!isTvSettings()) { // we only want this on phone as UI is not done yet on tv
|
if(isLayout(PHONE)) { // we only want this on phone as UI is not done yet on tv
|
||||||
(activity as? MainActivity?)?.apply {
|
(activity as? MainActivity?)?.apply {
|
||||||
loadPopup(callback.card)
|
loadPopup(callback.card)
|
||||||
} ?: kotlin.run {
|
} ?: kotlin.run {
|
||||||
|
|
|
@ -17,7 +17,8 @@ import com.lagradost.cloudstream3.SearchQuality
|
||||||
import com.lagradost.cloudstream3.SearchResponse
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
import com.lagradost.cloudstream3.isMovieType
|
import com.lagradost.cloudstream3.isMovieType
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
|
||||||
|
@ -164,7 +165,7 @@ object SearchResultBuilder {
|
||||||
|
|
||||||
bg.isFocusable = false
|
bg.isFocusable = false
|
||||||
bg.isFocusableInTouchMode = false
|
bg.isFocusableInTouchMode = false
|
||||||
if(!isTrueTvSettings()) {
|
if(!isLayout(TV)) {
|
||||||
bg.setOnClickListener {
|
bg.setOnClickListener {
|
||||||
click(it)
|
click(it)
|
||||||
}
|
}
|
||||||
|
@ -207,7 +208,7 @@ object SearchResultBuilder {
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (isTrueTvSettings()) {
|
if (isLayout(TV)) {
|
||||||
// bg.isFocusable = true
|
// bg.isFocusable = true
|
||||||
// bg.isFocusableInTouchMode = true
|
// bg.isFocusableInTouchMode = true
|
||||||
// bg.touchscreenBlocksFocus = false
|
// bg.touchscreenBlocksFocus = false
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,8 +29,10 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.simklAp
|
||||||
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
||||||
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI
|
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI
|
||||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||||
|
@ -76,7 +78,7 @@ class SettingsAccount : PreferenceFragmentCompat() {
|
||||||
showAccountSwitch(activity, api)
|
showAccountSwitch(activity, api)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTvSettings()) {
|
if (isLayout(TV or EMULATOR)) {
|
||||||
binding.accountSwitchAccount.requestFocus()
|
binding.accountSwitchAccount.requestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,7 +142,7 @@ class SettingsAccount : PreferenceFragmentCompat() {
|
||||||
binding.loginUsernameInput to api.requiresUsername
|
binding.loginUsernameInput to api.requiresUsername
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isTvSettings()) {
|
if (isLayout(TV or EMULATOR)) {
|
||||||
visibilityMap.forEach { (input, isVisible) ->
|
visibilityMap.forEach { (input, isVisible) ->
|
||||||
input.isVisible = isVisible
|
input.isVisible = isVisible
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
package com.lagradost.cloudstream3.ui.settings
|
package com.lagradost.cloudstream3.ui.settings
|
||||||
|
|
||||||
import android.app.UiModeManager
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -16,16 +12,20 @@ import androidx.core.view.updateLayoutParams
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.context
|
import com.lagradost.cloudstream3.BuildConfig
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.databinding.MainSettingsBinding
|
import com.lagradost.cloudstream3.databinding.MainSettingsBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
|
||||||
import com.lagradost.cloudstream3.ui.home.HomeFragment
|
import com.lagradost.cloudstream3.ui.home.HomeFragment
|
||||||
import com.lagradost.cloudstream3.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.navigate
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
|
@ -33,10 +33,6 @@ import java.io.File
|
||||||
|
|
||||||
class SettingsFragment : Fragment() {
|
class SettingsFragment : Fragment() {
|
||||||
companion object {
|
companion object {
|
||||||
var beneneCount = 0
|
|
||||||
|
|
||||||
private var isTv: Boolean = false
|
|
||||||
private var isTrueTv: Boolean = false
|
|
||||||
|
|
||||||
fun PreferenceFragmentCompat?.getPref(id: Int): Preference? {
|
fun PreferenceFragmentCompat?.getPref(id: Int): Preference? {
|
||||||
if (this == null) return null
|
if (this == null) return null
|
||||||
|
@ -53,12 +49,12 @@ class SettingsFragment : Fragment() {
|
||||||
* On TV you cannot properly scroll to the bottom of settings, this fixes that.
|
* On TV you cannot properly scroll to the bottom of settings, this fixes that.
|
||||||
* */
|
* */
|
||||||
fun PreferenceFragmentCompat.setPaddingBottom() {
|
fun PreferenceFragmentCompat.setPaddingBottom() {
|
||||||
if (isTvSettings()) {
|
if (isLayout(TV or EMULATOR)) {
|
||||||
listView?.setPadding(0, 0, 0, 100.toPx)
|
listView?.setPadding(0, 0, 0, 100.toPx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun PreferenceFragmentCompat.setToolBarScrollFlags() {
|
fun PreferenceFragmentCompat.setToolBarScrollFlags() {
|
||||||
if (isTvSettings()) {
|
if (isLayout(TV or EMULATOR)) {
|
||||||
val settingsAppbar = view?.findViewById<MaterialToolbar>(R.id.settings_toolbar)
|
val settingsAppbar = view?.findViewById<MaterialToolbar>(R.id.settings_toolbar)
|
||||||
|
|
||||||
settingsAppbar?.updateLayoutParams<AppBarLayout.LayoutParams> {
|
settingsAppbar?.updateLayoutParams<AppBarLayout.LayoutParams> {
|
||||||
|
@ -67,7 +63,7 @@ class SettingsFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun Fragment?.setToolBarScrollFlags() {
|
fun Fragment?.setToolBarScrollFlags() {
|
||||||
if (isTvSettings()) {
|
if (isLayout(TV or EMULATOR)) {
|
||||||
val settingsAppbar = this?.view?.findViewById<MaterialToolbar>(R.id.settings_toolbar)
|
val settingsAppbar = this?.view?.findViewById<MaterialToolbar>(R.id.settings_toolbar)
|
||||||
|
|
||||||
settingsAppbar?.updateLayoutParams<AppBarLayout.LayoutParams> {
|
settingsAppbar?.updateLayoutParams<AppBarLayout.LayoutParams> {
|
||||||
|
@ -86,7 +82,7 @@ class SettingsFragment : Fragment() {
|
||||||
activity?.onBackPressedDispatcher?.onBackPressed()
|
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fixPaddingStatusbar(settingsToolbar)
|
UIHelper.fixPaddingStatusbar(settingsToolbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Fragment?.setUpToolbar(@StringRes title: Int) {
|
fun Fragment?.setUpToolbar(@StringRes title: Int) {
|
||||||
|
@ -101,7 +97,7 @@ class SettingsFragment : Fragment() {
|
||||||
activity?.onBackPressedDispatcher?.onBackPressed()
|
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fixPaddingStatusbar(settingsToolbar)
|
UIHelper.fixPaddingStatusbar(settingsToolbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFolderSize(dir: File): Long {
|
fun getFolderSize(dir: File): Long {
|
||||||
|
@ -117,60 +113,7 @@ class SettingsFragment : Fragment() {
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.getLayoutInt(): Int {
|
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
|
||||||
return settingsManager.getInt(this.getString(R.string.app_layout_key), -1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.isTvSettings(): Boolean {
|
|
||||||
var value = getLayoutInt()
|
|
||||||
if (value == -1) {
|
|
||||||
value = if (isAutoTv()) 1 else 0
|
|
||||||
}
|
|
||||||
return value == 1 || value == 2
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Context.isTrueTvSettings(): Boolean {
|
|
||||||
var value = getLayoutInt()
|
|
||||||
if (value == -1) {
|
|
||||||
value = if (isAutoTv()) 1 else 0
|
|
||||||
}
|
|
||||||
return value == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.updateTv() {
|
|
||||||
isTrueTv = isTrueTvSettings()
|
|
||||||
isTv = isTvSettings()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isTrueTvSettings(): Boolean {
|
|
||||||
return isTrueTv
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isTvSettings(): Boolean {
|
|
||||||
return isTv
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.isEmulatorSettings(): Boolean {
|
|
||||||
return getLayoutInt() == 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// phone exclusive
|
|
||||||
fun isTruePhone(): Boolean {
|
|
||||||
return !isTrueTvSettings() && !isTvSettings() && context?.isEmulatorSettings() != true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Context.isAutoTv(): Boolean {
|
|
||||||
val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager?
|
|
||||||
// AFT = Fire TV
|
|
||||||
val model = Build.MODEL.lowercase()
|
|
||||||
return uiModeManager?.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION || Build.MODEL.contains(
|
|
||||||
"AFT"
|
|
||||||
) || model.contains("firestick") || model.contains("fire tv") || model.contains("chromecast")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
binding = null
|
binding = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
@ -195,8 +138,6 @@ class SettingsFragment : Fragment() {
|
||||||
|
|
||||||
// used to debug leaks showToast(activity,"${VideoDownloadManager.downloadStatusEvent.size} : ${VideoDownloadManager.downloadProgressEvent.size}")
|
// used to debug leaks showToast(activity,"${VideoDownloadManager.downloadStatusEvent.size} : ${VideoDownloadManager.downloadProgressEvent.size}")
|
||||||
|
|
||||||
val isTrueTv = isTrueTvSettings()
|
|
||||||
|
|
||||||
for (syncApi in accountManagers) {
|
for (syncApi in accountManagers) {
|
||||||
val login = syncApi.loginInfo()
|
val login = syncApi.loginInfo()
|
||||||
val pic = login?.profilePicture ?: continue
|
val pic = login?.profilePicture ?: continue
|
||||||
|
@ -224,7 +165,7 @@ class SettingsFragment : Fragment() {
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
navigate(navigationId)
|
navigate(navigationId)
|
||||||
}
|
}
|
||||||
if (isTrueTv) {
|
if (isLayout(TV)) {
|
||||||
isFocusable = true
|
isFocusable = true
|
||||||
isFocusableInTouchMode = true
|
isFocusableInTouchMode = true
|
||||||
}
|
}
|
||||||
|
@ -232,9 +173,20 @@ class SettingsFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default focus on TV
|
// Default focus on TV
|
||||||
if (isTrueTv) {
|
if (isLayout(TV)) {
|
||||||
settingsGeneral.requestFocus()
|
settingsGeneral.requestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val appVersion = getString(R.string.app_version)
|
||||||
|
val commitInfo = getString(R.string.commit_hash)
|
||||||
|
val buildDate = BuildConfig.BUILDDATE
|
||||||
|
|
||||||
|
binding?.buildDate?.text = buildDate
|
||||||
|
|
||||||
|
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.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.network.initClient
|
import com.lagradost.cloudstream3.network.initClient
|
||||||
import com.lagradost.cloudstream3.ui.EasterEggMonke
|
import com.lagradost.cloudstream3.ui.EasterEggMonke
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.beneneCount
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
||||||
|
@ -378,30 +379,30 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
SettingsFragment.beneneCount =
|
beneneCount =
|
||||||
settingsManager.getInt(getString(R.string.benene_count), 0)
|
settingsManager.getInt(getString(R.string.benene_count), 0)
|
||||||
getPref(R.string.benene_count)?.let { pref ->
|
getPref(R.string.benene_count)?.let { pref ->
|
||||||
pref.summary =
|
pref.summary =
|
||||||
if (SettingsFragment.beneneCount <= 0) getString(R.string.benene_count_text_none) else getString(
|
if (beneneCount <= 0) getString(R.string.benene_count_text_none) else getString(
|
||||||
R.string.benene_count_text
|
R.string.benene_count_text
|
||||||
).format(
|
).format(
|
||||||
SettingsFragment.beneneCount
|
beneneCount
|
||||||
)
|
)
|
||||||
|
|
||||||
pref.setOnPreferenceClickListener {
|
pref.setOnPreferenceClickListener {
|
||||||
try {
|
try {
|
||||||
SettingsFragment.beneneCount++
|
beneneCount++
|
||||||
if (SettingsFragment.beneneCount%20 == 0) {
|
if (beneneCount%20 == 0) {
|
||||||
val intent = Intent(context, EasterEggMonke::class.java)
|
val intent = Intent(context, EasterEggMonke::class.java)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
settingsManager.edit().putInt(
|
settingsManager.edit().putInt(
|
||||||
getString(R.string.benene_count),
|
getString(R.string.benene_count),
|
||||||
SettingsFragment.beneneCount
|
beneneCount
|
||||||
)
|
)
|
||||||
.apply()
|
.apply()
|
||||||
it.summary =
|
it.summary =
|
||||||
getString(R.string.benene_count_text).format(SettingsFragment.beneneCount)
|
getString(R.string.benene_count_text).format(beneneCount)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,11 @@ import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.SearchQuality
|
import com.lagradost.cloudstream3.SearchQuality
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.updateTv
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
|
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
package com.lagradost.cloudstream3.ui.settings
|
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.Bundle
|
||||||
import android.os.TransactionTooLargeException
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
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.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.network.initClient
|
import com.lagradost.cloudstream3.network.initClient
|
||||||
import com.lagradost.cloudstream3.services.BackupWorkManager
|
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.getPref
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
||||||
|
@ -30,6 +27,7 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
|
@ -117,22 +115,15 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
||||||
binding.text1.text = text
|
binding.text1.text = text
|
||||||
|
|
||||||
binding.copyBtt.setOnClickListener {
|
binding.copyBtt.setOnClickListener {
|
||||||
// Can crash on too much text
|
clipboardHelper(txt("Logcat"), text)
|
||||||
try {
|
|
||||||
val serviceClipboard =
|
|
||||||
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?)
|
|
||||||
?: return@setOnClickListener
|
|
||||||
val clip = ClipData.newPlainText("logcat", text)
|
|
||||||
serviceClipboard.setPrimaryClip(clip)
|
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
} catch (e: TransactionTooLargeException) {
|
|
||||||
showToast(R.string.clipboard_too_large)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.clearBtt.setOnClickListener {
|
binding.clearBtt.setOnClickListener {
|
||||||
Runtime.getRuntime().exec("logcat -c")
|
Runtime.getRuntime().exec("logcat -c")
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.saveBtt.setOnClickListener {
|
binding.saveBtt.setOnClickListener {
|
||||||
var fileStream: OutputStream? = null
|
var fileStream: OutputStream? = null
|
||||||
try {
|
try {
|
||||||
|
@ -153,9 +144,11 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
||||||
fileStream?.closeQuietly()
|
fileStream?.closeQuietly()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.closeBtt.setOnClickListener {
|
binding.closeBtt.setOnClickListener {
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
return@setOnPreferenceClickListener true
|
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.FOCUS_SELF
|
||||||
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
||||||
import com.lagradost.cloudstream3.ui.result.setText
|
import com.lagradost.cloudstream3.ui.result.setText
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.downloadAllPluginsDialog
|
import com.lagradost.cloudstream3.utils.AppUtils.downloadAllPluginsDialog
|
||||||
|
@ -97,7 +98,7 @@ class ExtensionsFragment : Fragment() {
|
||||||
nextLeft = R.id.nav_rail_view
|
nextLeft = R.id.nav_rail_view
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!isTrueTvSettings())
|
if (!isLayout(TV))
|
||||||
binding?.addRepoButton?.let { button ->
|
binding?.addRepoButton?.let { button ->
|
||||||
button.post {
|
button.post {
|
||||||
setPadding(
|
setPadding(
|
||||||
|
@ -286,7 +287,7 @@ class ExtensionsFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isTv = isTrueTvSettings()
|
val isTv = isLayout(TV)
|
||||||
binding?.apply {
|
binding?.apply {
|
||||||
addRepoButton.isGone = isTv
|
addRepoButton.isGone = isTv
|
||||||
addRepoButtonImageviewHolder.isVisible = isTv
|
addRepoButtonImageviewHolder.isVisible = isTv
|
||||||
|
|
|
@ -17,7 +17,8 @@ import com.lagradost.cloudstream3.plugins.PluginManager
|
||||||
import com.lagradost.cloudstream3.plugins.VotingApi.getVotes
|
import com.lagradost.cloudstream3.plugins.VotingApi.getVotes
|
||||||
import com.lagradost.cloudstream3.ui.result.setText
|
import com.lagradost.cloudstream3.ui.result.setText
|
||||||
import com.lagradost.cloudstream3.ui.result.txt
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
|
@ -44,7 +45,7 @@ class PluginAdapter(
|
||||||
private val plugins: MutableList<PluginViewData> = mutableListOf()
|
private val plugins: MutableList<PluginViewData> = mutableListOf()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
val layout = if(isTrueTvSettings()) R.layout.repository_item_tv else R.layout.repository_item
|
val layout = if(isLayout(TV)) R.layout.repository_item_tv else R.layout.repository_item
|
||||||
val inflated = LayoutInflater.from(parent.context).inflate(layout, parent, false)
|
val inflated = LayoutInflater.from(parent.context).inflate(layout, parent, false)
|
||||||
|
|
||||||
return PluginViewHolder(
|
return PluginViewHolder(
|
||||||
|
|
|
@ -17,7 +17,9 @@ import com.lagradost.cloudstream3.mvvm.observe
|
||||||
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips
|
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips
|
||||||
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
||||||
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||||
import com.lagradost.cloudstream3.ui.settings.appLanguages
|
import com.lagradost.cloudstream3.ui.settings.appLanguages
|
||||||
|
@ -155,7 +157,7 @@ class PluginsFragment : Fragment() {
|
||||||
pluginViewModel.handlePluginAction(activity, url, it, isLocal)
|
pluginViewModel.handlePluginAction(activity, url, it, isLocal)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTvSettings()) {
|
if (isLayout(TV or EMULATOR)) {
|
||||||
// Scrolling down does not reveal the whole RecyclerView on TV, add to bypass that.
|
// Scrolling down does not reveal the whole RecyclerView on TV, add to bypass that.
|
||||||
binding?.pluginRecyclerView?.setPadding(0, 0, 0, 200.toPx)
|
binding?.pluginRecyclerView?.setPadding(0, 0, 0, 200.toPx)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
package com.lagradost.cloudstream3.ui.settings.extensions
|
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.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.viewbinding.ViewBinding
|
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.R
|
||||||
import com.lagradost.cloudstream3.databinding.RepositoryItemBinding
|
import com.lagradost.cloudstream3.databinding.RepositoryItemBinding
|
||||||
import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding
|
import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding
|
||||||
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
|
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
|
||||||
import com.lagradost.cloudstream3.ui.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(
|
class RepoAdapter(
|
||||||
val isSetup: Boolean,
|
val isSetup: Boolean,
|
||||||
|
@ -28,7 +24,7 @@ class RepoAdapter(
|
||||||
private val repositories: MutableList<RepositoryData> = mutableListOf()
|
private val repositories: MutableList<RepositoryData> = mutableListOf()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
val layout = if (isTrueTvSettings()) RepositoryItemTvBinding.inflate(
|
val layout = if (isLayout(TV)) RepositoryItemTvBinding.inflate(
|
||||||
LayoutInflater.from(parent.context),
|
LayoutInflater.from(parent.context),
|
||||||
parent,
|
parent,
|
||||||
false
|
false
|
||||||
|
@ -121,13 +117,9 @@ class RepoAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
repositoryItemRoot.setOnLongClickListener {
|
repositoryItemRoot.setOnLongClickListener {
|
||||||
val clipboardManager =
|
val shareableRepoData = "${repositoryData.name} : \n ${repositoryData.url}"
|
||||||
activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?
|
clipboardHelper(txt(R.string.repo_copy_label), shareableRepoData)
|
||||||
clipboardManager?.setPrimaryClip(ClipData.newPlainText("RepoUrl", repositoryData.url))
|
true
|
||||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
|
|
||||||
showToast(R.string.copyRepoUrl, Toast.LENGTH_SHORT)
|
|
||||||
}
|
|
||||||
return@setOnLongClickListener true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mainText.text = repositoryData.name
|
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.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
import com.lagradost.cloudstream3.mvvm.observeNullable
|
import com.lagradost.cloudstream3.mvvm.observeNullable
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ class TestFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTrueTvSettings()) {
|
if (isLayout(TV)) {
|
||||||
providerTest.playPauseButton?.isFocusableInTouchMode = true
|
providerTest.playPauseButton?.isFocusableInTouchMode = true
|
||||||
providerTest.playPauseButton?.requestFocus()
|
providerTest.playPauseButton?.requestFocus()
|
||||||
}
|
}
|
||||||
|
@ -75,7 +76,7 @@ class TestFragment : Fragment() {
|
||||||
|
|
||||||
fun focusRecyclerView() {
|
fun focusRecyclerView() {
|
||||||
// Hack to make it possible to focus the recyclerview.
|
// Hack to make it possible to focus the recyclerview.
|
||||||
if (isTrueTvSettings()) {
|
if (isLayout(TV)) {
|
||||||
providerTestRecyclerView.requestFocus()
|
providerTestRecyclerView.requestFocus()
|
||||||
providerTestAppbar.setExpanded(false, true)
|
providerTestAppbar.setExpanded(false, true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
|
||||||
import androidx.media3.common.text.Cue
|
import androidx.media3.common.text.Cue
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.google.android.gms.cast.TextTrackStyle
|
import com.google.android.gms.cast.TextTrackStyle
|
||||||
import com.google.android.gms.cast.TextTrackStyle.*
|
import com.google.android.gms.cast.TextTrackStyle.*
|
||||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog
|
import com.jaredrummler.android.colorpicker.ColorPickerDialog
|
||||||
|
@ -24,7 +24,9 @@ import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.databinding.ChromecastSubtitleSettingsBinding
|
import com.lagradost.cloudstream3.databinding.ChromecastSubtitleSettingsBinding
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
import com.lagradost.cloudstream3.utils.Event
|
import com.lagradost.cloudstream3.utils.Event
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
||||||
|
@ -173,7 +175,7 @@ class ChromecastSubtitlesFragment : Fragment() {
|
||||||
state = getCurrentSavedStyle()
|
state = getCurrentSavedStyle()
|
||||||
context?.updateState()
|
context?.updateState()
|
||||||
|
|
||||||
val isTvSettings = isTvSettings()
|
val isTvSettings = isLayout(TV or EMULATOR)
|
||||||
|
|
||||||
fun View.setFocusableInTv() {
|
fun View.setFocusableInTv() {
|
||||||
this.isFocusableInTouchMode = isTvSettings
|
this.isFocusableInTouchMode = isTvSettings
|
||||||
|
|
|
@ -28,7 +28,9 @@ import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.databinding.SubtitleSettingsBinding
|
import com.lagradost.cloudstream3.databinding.SubtitleSettingsBinding
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
import com.lagradost.cloudstream3.utils.Event
|
import com.lagradost.cloudstream3.utils.Event
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
||||||
|
@ -252,7 +254,7 @@ class SubtitlesFragment : Fragment() {
|
||||||
state = getCurrentSavedStyle()
|
state = getCurrentSavedStyle()
|
||||||
context?.updateState()
|
context?.updateState()
|
||||||
|
|
||||||
val isTvTrueSettings = isTrueTvSettings()
|
val isTvTrueSettings = isLayout(TV)
|
||||||
|
|
||||||
fun View.setFocusableInTv() {
|
fun View.setFocusableInTv() {
|
||||||
this.isFocusableInTouchMode = isTvTrueSettings
|
this.isFocusableInTouchMode = isTvTrueSettings
|
||||||
|
|
|
@ -61,8 +61,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStri
|
||||||
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
|
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
|
||||||
import com.lagradost.cloudstream3.ui.WebviewFragment
|
import com.lagradost.cloudstream3.ui.WebviewFragment
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
|
||||||
import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel.Companion.downloadAll
|
import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel.Companion.downloadAll
|
||||||
import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
|
import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
@ -78,7 +77,6 @@ import okhttp3.Cache
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import kotlin.system.measureTimeMillis
|
|
||||||
|
|
||||||
object AppUtils {
|
object AppUtils {
|
||||||
fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) {
|
fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) {
|
||||||
|
@ -583,7 +581,7 @@ object AppUtils {
|
||||||
//private val viewModel: ResultViewModel by activityViewModels()
|
//private val viewModel: ResultViewModel by activityViewModels()
|
||||||
|
|
||||||
private fun getResultsId(): Int {
|
private fun getResultsId(): Int {
|
||||||
return if (isTvSettings()) {
|
return if (Globals.isLayout(Globals.TV or Globals.EMULATOR)) {
|
||||||
R.id.global_to_navigation_results_tv
|
R.id.global_to_navigation_results_tv
|
||||||
} else {
|
} else {
|
||||||
R.id.global_to_navigation_results_phone
|
R.id.global_to_navigation_results_phone
|
||||||
|
@ -707,7 +705,7 @@ object AppUtils {
|
||||||
* Sets the focus to the negative button when in TV and Emulator layout.
|
* Sets the focus to the negative button when in TV and Emulator layout.
|
||||||
**/
|
**/
|
||||||
fun AlertDialog.setDefaultFocus(buttonFocus: Int = DialogInterface.BUTTON_NEGATIVE) {
|
fun AlertDialog.setDefaultFocus(buttonFocus: Int = DialogInterface.BUTTON_NEGATIVE) {
|
||||||
if (!isTvSettings()) return
|
if (!Globals.isLayout(Globals.TV or Globals.EMULATOR)) return
|
||||||
this.getButton(buttonFocus).run {
|
this.getButton(buttonFocus).run {
|
||||||
isFocusableInTouchMode = true
|
isFocusableInTouchMode = true
|
||||||
requestFocus()
|
requestFocus()
|
||||||
|
|
|
@ -32,7 +32,6 @@ import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getDefaultSharedPrefs
|
import com.lagradost.cloudstream3.utils.DataStore.getDefaultSharedPrefs
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getSharedPrefs
|
import com.lagradost.cloudstream3.utils.DataStore.getSharedPrefs
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.mapper
|
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.checkWrite
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.setupStream
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.setupStream
|
||||||
|
@ -256,8 +255,12 @@ object BackupUtils {
|
||||||
map: Map<String, T>?,
|
map: Map<String, T>?,
|
||||||
isEditingAppSettings: Boolean = false
|
isEditingAppSettings: Boolean = false
|
||||||
) {
|
) {
|
||||||
map?.filter { it.key.isTransferable() }?.forEach {
|
val editor = DataStore.editor(this, isEditingAppSettings)
|
||||||
setKeyRaw(it.key, it.value, isEditingAppSettings)
|
map?.forEach {
|
||||||
|
if (it.key.isTransferable()) {
|
||||||
|
editor.setKeyRaw(it.key, it.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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 {
|
object DataStore {
|
||||||
val mapper: JsonMapper = JsonMapper.builder().addModule(kotlinModule())
|
val mapper: JsonMapper = JsonMapper.builder().addModule(kotlinModule())
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
||||||
|
@ -66,22 +88,10 @@ object DataStore {
|
||||||
return "${folder}/${path}"
|
return "${folder}/${path}"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> Context.setKeyRaw(path: String, value: T, isEditingAppSettings: Boolean = false) {
|
fun editor(context : Context, isEditingAppSettings: Boolean = false) : Editor {
|
||||||
try {
|
|
||||||
val editor: SharedPreferences.Editor =
|
val editor: SharedPreferences.Editor =
|
||||||
if (isEditingAppSettings) getDefaultSharedPrefs().edit() else getSharedPrefs().edit()
|
if (isEditingAppSettings) context.getDefaultSharedPrefs().edit() else context.getSharedPrefs().edit()
|
||||||
when (value) {
|
return Editor(editor)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.getDefaultSharedPrefs(): SharedPreferences {
|
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.BottomSelectionDialogBinding
|
||||||
import com.lagradost.cloudstream3.databinding.BottomTextDialogBinding
|
import com.lagradost.cloudstream3.databinding.BottomTextDialogBinding
|
||||||
import com.lagradost.cloudstream3.databinding.OptionsPopupTvBinding
|
import com.lagradost.cloudstream3.databinding.OptionsPopupTvBinding
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
|
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
|
@ -54,7 +56,7 @@ object SingleSelectionHelper {
|
||||||
) {
|
) {
|
||||||
if (this == null) return
|
if (this == null) return
|
||||||
|
|
||||||
if (isTvSettings()) {
|
if (isLayout(TV or EMULATOR)) {
|
||||||
val binding = OptionsPopupTvBinding.inflate(layoutInflater)
|
val binding = OptionsPopupTvBinding.inflate(layoutInflater)
|
||||||
val dialog = AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
val dialog = AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
||||||
.setView(binding.root)
|
.setView(binding.root)
|
||||||
|
|
|
@ -5,6 +5,8 @@ import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.AppOpsManager
|
import android.app.AppOpsManager
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
@ -14,12 +16,15 @@ import android.graphics.Color
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.TransactionTooLargeException
|
||||||
|
import android.util.Log
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.view.ViewGroup.MarginLayoutParams
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.ListAdapter
|
import android.widget.ListAdapter
|
||||||
import android.widget.ListView
|
import android.widget.ListView
|
||||||
|
import android.widget.Toast.LENGTH_LONG
|
||||||
import androidx.annotation.AttrRes
|
import androidx.annotation.AttrRes
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
|
@ -30,14 +35,12 @@ import androidx.appcompat.view.menu.MenuBuilder
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.graphics.alpha
|
import androidx.core.graphics.alpha
|
||||||
import androidx.core.graphics.blue
|
import androidx.core.graphics.blue
|
||||||
import androidx.core.graphics.drawable.toBitmapOrNull
|
import androidx.core.graphics.drawable.toBitmapOrNull
|
||||||
import androidx.core.graphics.green
|
import androidx.core.graphics.green
|
||||||
import androidx.core.graphics.red
|
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.marginBottom
|
||||||
import androidx.core.view.marginLeft
|
import androidx.core.view.marginLeft
|
||||||
import androidx.core.view.marginRight
|
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.Chip
|
||||||
import com.google.android.material.chip.ChipDrawable
|
import com.google.android.material.chip.ChipDrawable
|
||||||
import com.google.android.material.chip.ChipGroup
|
import com.google.android.material.chip.ChipGroup
|
||||||
|
import com.lagradost.cloudstream3.AcraApplication.Companion.context
|
||||||
import com.lagradost.cloudstream3.CommonActivity.activity
|
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.R
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.ui.result.UiImage
|
import com.lagradost.cloudstream3.ui.result.UiImage
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
import com.lagradost.cloudstream3.ui.result.UiText
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
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 jp.wasabeef.glide.transformations.BlurTransformation
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
object UIHelper {
|
object UIHelper {
|
||||||
val Int.toPx: Int get() = (this * Resources.getSystem().displayMetrics.density).toInt()
|
val Int.toPx: Int get() = (this * Resources.getSystem().displayMetrics.density).toInt()
|
||||||
val Float.toPx: Float get() = (this * Resources.getSystem().displayMetrics.density)
|
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.
|
* Sets ListView height dynamically based on the height of the items.
|
||||||
|
@ -434,7 +469,7 @@ object UIHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.getStatusBarHeight(): Int {
|
fun Context.getStatusBarHeight(): Int {
|
||||||
if (isTvSettings()) {
|
if (isLayout(Globals.TV or EMULATOR)) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -536,7 +571,7 @@ object UIHelper {
|
||||||
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
changeStatusBarState(isEmulatorSettings())
|
changeStatusBarState(isLayout(EMULATOR))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.shouldShowPIPMode(isInPlayer: Boolean): Boolean {
|
fun Context.shouldShowPIPMode(isInPlayer: Boolean): Boolean {
|
||||||
|
|
|
@ -41,22 +41,34 @@
|
||||||
android:layout_marginStart="10dp"
|
android:layout_marginStart="10dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<FrameLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/resultview_preview_title"
|
android:id="@+id/resultview_preview_title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
android:textColor="?attr/textColor"
|
android:textColor="?attr/textColor"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:layout_gravity="start|center_vertical"
|
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:layout_marginEnd="25dp"
|
tools:text="The Perfect Run" />
|
||||||
tools:text="The Perfect Run">
|
|
||||||
|
|
||||||
</TextView>
|
<ImageView
|
||||||
|
android:id="@+id/resultview_preview_subscribe"
|
||||||
|
android:layout_width="25dp"
|
||||||
|
android:layout_height="25dp"
|
||||||
|
android:layout_gravity="end|center_vertical"
|
||||||
|
|
||||||
|
android:layout_margin="5dp"
|
||||||
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/subscribe_tooltip"
|
||||||
|
android:elevation="10dp"
|
||||||
|
android:nextFocusDown="@id/resultview_preview_bookmark"
|
||||||
|
android:src="@drawable/baseline_notifications_none_24"
|
||||||
|
app:tint="?attr/textColor" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/resultview_preview_favorite"
|
android:id="@+id/resultview_preview_favorite"
|
||||||
|
@ -66,11 +78,13 @@
|
||||||
|
|
||||||
android:layout_margin="5dp"
|
android:layout_margin="5dp"
|
||||||
android:background="?android:attr/selectableItemBackgroundBorderless"
|
android:background="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/favorite"
|
||||||
android:elevation="10dp"
|
android:elevation="10dp"
|
||||||
android:nextFocusDown="@id/resultview_preview_bookmark"
|
android:nextFocusDown="@id/resultview_preview_bookmark"
|
||||||
android:src="@drawable/ic_baseline_favorite_border_24"
|
android:src="@drawable/ic_baseline_favorite_border_24"
|
||||||
app:tint="?attr/textColor" />
|
app:tint="?attr/textColor" />
|
||||||
</FrameLayout>
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<com.lagradost.cloudstream3.widget.FlowLayout
|
<com.lagradost.cloudstream3.widget.FlowLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -134,38 +148,38 @@
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:padding="7dp"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="7dp">
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/resultview_preview_bookmark"
|
android:id="@+id/resultview_preview_bookmark"
|
||||||
|
style="@style/BlackButton"
|
||||||
|
android:layout_width="50dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:nextFocusUp="@id/resultview_preview_favorite"
|
|
||||||
android:nextFocusRight="@id/resultview_preview_more_info"
|
android:nextFocusRight="@id/resultview_preview_more_info"
|
||||||
|
|
||||||
tools:visibility="visible"
|
android:nextFocusUp="@id/resultview_preview_favorite"
|
||||||
|
|
||||||
app:icon="@drawable/ic_baseline_bookmark_24"
|
app:icon="@drawable/ic_baseline_bookmark_24"
|
||||||
tools:text="Bookmark"
|
tools:text="Bookmark"
|
||||||
style="@style/BlackButton"
|
|
||||||
|
|
||||||
android:layout_width="50dp" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/resultview_preview_more_info"
|
android:id="@+id/resultview_preview_more_info"
|
||||||
|
style="@style/WhiteButton"
|
||||||
|
android:layout_width="50dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:nextFocusUp="@id/resultview_preview_favorite"
|
|
||||||
android:nextFocusLeft="@id/resultview_preview_bookmark"
|
android:nextFocusLeft="@id/resultview_preview_bookmark"
|
||||||
|
|
||||||
tools:visibility="visible"
|
android:nextFocusUp="@id/resultview_preview_favorite"
|
||||||
|
|
||||||
app:icon="@drawable/ic_baseline_open_in_new_24"
|
|
||||||
android:text="@string/home_more_info"
|
android:text="@string/home_more_info"
|
||||||
style="@style/WhiteButton"
|
app:icon="@drawable/ic_baseline_open_in_new_24"
|
||||||
|
|
||||||
android:layout_width="50dp" />
|
tools:visibility="visible" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
|
@ -409,8 +409,8 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
|
android:maxLines="10"
|
||||||
android:foreground="@drawable/outline_drawable"
|
android:foreground="@drawable/outline_drawable"
|
||||||
android:maxLength="1000"
|
|
||||||
android:nextFocusUp="@id/result_back"
|
android:nextFocusUp="@id/result_back"
|
||||||
android:nextFocusDown="@id/result_bookmark_Button"
|
android:nextFocusDown="@id/result_bookmark_Button"
|
||||||
android:paddingTop="5dp"
|
android:paddingTop="5dp"
|
||||||
|
|
|
@ -271,7 +271,9 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:id="@+id/result_play_movie"
|
android:id="@+id/result_play_movie"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/result_play_movie_button"
|
android:id="@+id/result_play_movie_button"
|
||||||
|
@ -323,7 +325,9 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/result_resume_series_button"
|
android:id="@+id/result_resume_series_button"
|
||||||
|
|
|
@ -105,9 +105,10 @@
|
||||||
android:text="@string/extensions" />
|
android:text="@string/extensions" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/app_version_info"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center_horizontal"
|
android:layout_gravity="center"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -119,7 +120,7 @@
|
||||||
android:textColor="?attr/textColor" />
|
android:textColor="?attr/textColor" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView3"
|
android:id="@+id/delimiter0"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
|
@ -135,6 +136,25 @@
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:text="@string/commit_hash"
|
android:text="@string/commit_hash"
|
||||||
android:textColor="?attr/textColor" />
|
android:textColor="?attr/textColor" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/delimiter1"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:text="•"
|
||||||
|
android:textColor="?attr/textColor" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/build_date"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:textColor="?attr/textColor" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/outline_drawable"
|
android:background="@drawable/outline_drawable"
|
||||||
android:nextFocusRight="@id/action_button"
|
android:nextFocusRight="@id/action_settings"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
|
@ -117,6 +117,9 @@
|
||||||
android:background="@drawable/outline_drawable"
|
android:background="@drawable/outline_drawable"
|
||||||
android:contentDescription="@string/title_settings"
|
android:contentDescription="@string/title_settings"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
android:focusable="true"
|
||||||
|
android:nextFocusLeft="@id/repository_item_root"
|
||||||
|
android:nextFocusRight="@id/action_button"
|
||||||
app:srcCompat="@drawable/ic_baseline_tune_24"
|
app:srcCompat="@drawable/ic_baseline_tune_24"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
@ -130,7 +133,7 @@
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:contentDescription="@string/download"
|
android:contentDescription="@string/download"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:nextFocusLeft="@id/repository_item_root"
|
android:nextFocusLeft="@id/action_settings"
|
||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
tools:src="@drawable/ic_baseline_add_24" />
|
tools:src="@drawable/ic_baseline_add_24" />
|
||||||
|
|
||||||
|
|
|
@ -174,8 +174,8 @@
|
||||||
<string name="sort_close">Close</string>
|
<string name="sort_close">Close</string>
|
||||||
<string name="sort_clear">Clear</string>
|
<string name="sort_clear">Clear</string>
|
||||||
<string name="sort_save">Save</string>
|
<string name="sort_save">Save</string>
|
||||||
<string name="copyTitle">Title copied!</string>
|
<string name="repo_copy_label">Repository name and URL</string>
|
||||||
<string name="copyRepoUrl">Repo URL copied!</string>
|
<string name="toast_copied">copied!</string>
|
||||||
<string name="subscribe_tooltip">New episode notification</string>
|
<string name="subscribe_tooltip">New episode notification</string>
|
||||||
<string name="result_search_tooltip">Search in other extensions</string>
|
<string name="result_search_tooltip">Search in other extensions</string>
|
||||||
<string name="recommendations_tooltip">Show recommendations</string>
|
<string name="recommendations_tooltip">Show recommendations</string>
|
||||||
|
@ -647,6 +647,8 @@
|
||||||
<string name="history">History</string>
|
<string name="history">History</string>
|
||||||
<string name="enable_skip_op_from_database_des">Show skip popups for opening/ending</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_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_mark_as_watched">Mark as watched</string>
|
||||||
<string name="action_remove_from_watched">Remove from 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>
|
<string name="confirm_exit_dialog">Are you sure you want to exit\?</string>
|
||||||
|
|
|
@ -4,6 +4,7 @@ buildscript {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:8.2.1")
|
classpath("com.android.tools.build:gradle:8.2.1")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22")
|
||||||
|
|
Loading…
Reference in New Issue