mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Merge remote-tracking branch 'origin/master' into batteryoptimization
# Conflicts: # app/src/main/AndroidManifest.xml
This commit is contained in:
commit
3be0c2f293
23 changed files with 358 additions and 51 deletions
|
@ -212,6 +212,7 @@ dependencies {
|
||||||
implementation("androidx.palette:palette-ktx:1.0.0") // Palette For Images -> Colors
|
implementation("androidx.palette:palette-ktx:1.0.0") // Palette For Images -> Colors
|
||||||
implementation("androidx.tvprovider:tvprovider:1.0.0")
|
implementation("androidx.tvprovider:tvprovider:1.0.0")
|
||||||
implementation("com.github.discord:OverlappingPanels:0.1.5") // Gestures
|
implementation("com.github.discord:OverlappingPanels:0.1.5") // Gestures
|
||||||
|
implementation ("androidx.biometric:biometric:1.2.0-alpha05") // Fingerprint Authentication
|
||||||
implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0") // SeekBar Preview
|
implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0") // SeekBar Preview
|
||||||
|
|
||||||
// Extensions & Other Libs
|
// Extensions & Other Libs
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" /> <!-- Used for Android TV watch next -->
|
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" /> <!-- Used for Android TV watch next -->
|
||||||
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" /> <!-- Used for updates without prompt -->
|
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" /> <!-- Used for updates without prompt -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- Used for update service -->
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- Used for update service -->
|
||||||
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
|
||||||
<!-- Required for getting arbitrary Aniyomi packages -->
|
<!-- Required for getting arbitrary Aniyomi packages -->
|
||||||
|
|
|
@ -319,6 +319,7 @@ object CommonActivity {
|
||||||
"Banana" -> R.style.OverlayPrimaryColorBanana
|
"Banana" -> R.style.OverlayPrimaryColorBanana
|
||||||
"Party" -> R.style.OverlayPrimaryColorParty
|
"Party" -> R.style.OverlayPrimaryColorParty
|
||||||
"Pink" -> R.style.OverlayPrimaryColorPink
|
"Pink" -> R.style.OverlayPrimaryColorPink
|
||||||
|
"Lavender" -> R.style.OverlayPrimaryColorLavender
|
||||||
"Monet" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
"Monet" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||||
R.style.OverlayPrimaryColorMonet else R.style.OverlayPrimaryColorNormal
|
R.style.OverlayPrimaryColorMonet else R.style.OverlayPrimaryColorNormal
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import android.widget.Toast.LENGTH_LONG
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
|
@ -28,6 +29,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isInvisible
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.marginStart
|
import androidx.core.view.marginStart
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
@ -112,6 +114,7 @@ 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.SettingsFragment.Companion.isEmulatorSettings
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTruePhone
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
|
||||||
|
@ -131,11 +134,15 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
||||||
import com.lagradost.cloudstream3.utils.BackupUtils.backup
|
import com.lagradost.cloudstream3.utils.BackupUtils.backup
|
||||||
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
|
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
|
||||||
|
import com.lagradost.cloudstream3.utils.BiometricAuthenticator
|
||||||
|
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
|
||||||
|
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.accounts
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
|
||||||
import com.lagradost.cloudstream3.utils.Event
|
import com.lagradost.cloudstream3.utils.Event
|
||||||
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
||||||
|
@ -284,7 +291,7 @@ var app = Requests(responseParser = object : ResponseParser {
|
||||||
defaultHeaders = mapOf("user-agent" to USER_AGENT)
|
defaultHeaders = mapOf("user-agent" to USER_AGENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
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
|
||||||
|
@ -1169,7 +1176,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
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 (isTrueTvSettings() && 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)
|
||||||
|
@ -1181,14 +1188,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
newLocalBinding.focusOutline.isVisible = false
|
newLocalBinding.focusOutline.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isTrueTvSettings()) {
|
if (isTrueTvSettings()) {
|
||||||
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
|
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
|
||||||
centerView(newFocus)
|
centerView(newFocus)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ActivityMainBinding.bind(newLocalBinding.root) // this may crash
|
ActivityMainBinding.bind(newLocalBinding.root) // this may crash
|
||||||
} else {
|
} else {
|
||||||
val newLocalBinding = ActivityMainBinding.inflate(layoutInflater, null, false)
|
val newLocalBinding = ActivityMainBinding.inflate(layoutInflater, null, false)
|
||||||
|
@ -1201,6 +1206,25 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
}
|
}
|
||||||
changeStatusBarState(isEmulatorSettings())
|
changeStatusBarState(isEmulatorSettings())
|
||||||
|
|
||||||
|
/** Biometric stuff for users without accounts **/
|
||||||
|
val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_key), false)
|
||||||
|
val noAccounts = settingsManager.getBoolean(getString(R.string.skip_startup_account_select_key), false) || accounts.count() <= 1
|
||||||
|
|
||||||
|
if (isTruePhone() && authEnabled && noAccounts) {
|
||||||
|
if (deviceHasPasswordPinLock(this)) {
|
||||||
|
startBiometricAuthentication(this, R.string.biometric_authentication_title, false)
|
||||||
|
|
||||||
|
BiometricAuthenticator.promptInfo?.let {
|
||||||
|
BiometricAuthenticator.biometricPrompt?.authenticate(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide background while authenticating, Sorry moms & dads 🙏
|
||||||
|
binding?.navHostFragment?.isInvisible = true
|
||||||
|
} else {
|
||||||
|
showToast(R.string.phone_not_secured, LENGTH_LONG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Automatically enable jsdelivr if cant connect to raw.githubusercontent.com
|
// Automatically enable jsdelivr if cant connect to raw.githubusercontent.com
|
||||||
if (this.getKey<Boolean>(getString(R.string.jsdelivr_proxy_key)) == null && isNetworkAvailable()) {
|
if (this.getKey<Boolean>(getString(R.string.jsdelivr_proxy_key)) == null && isNetworkAvailable()) {
|
||||||
main {
|
main {
|
||||||
|
@ -1740,6 +1764,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Biometric stuff **/
|
||||||
|
override fun onAuthenticationSuccess() {
|
||||||
|
// make background (nav host fragment) visible again
|
||||||
|
binding?.navHostFragment?.isInvisible = false
|
||||||
|
}
|
||||||
|
|
||||||
private var backPressedCallback: OnBackPressedCallback? = null
|
private var backPressedCallback: OnBackPressedCallback? = null
|
||||||
|
|
||||||
private fun attachBackPressedCallback() {
|
private fun attachBackPressedCallback() {
|
||||||
|
|
|
@ -49,11 +49,15 @@ inline fun debugWarning(assert: () -> Boolean, message: () -> String) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** NOTE: Only one observer at a time per value */
|
||||||
fun <T> LifecycleOwner.observe(liveData: LiveData<T>, action: (t: T) -> Unit) {
|
fun <T> LifecycleOwner.observe(liveData: LiveData<T>, action: (t: T) -> Unit) {
|
||||||
|
liveData.removeObservers(this)
|
||||||
liveData.observe(this) { it?.let { t -> action(t) } }
|
liveData.observe(this) { it?.let { t -> action(t) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** NOTE: Only one observer at a time per value */
|
||||||
fun <T> LifecycleOwner.observeNullable(liveData: LiveData<T>, action: (t: T) -> Unit) {
|
fun <T> LifecycleOwner.observeNullable(liveData: LiveData<T>, action: (t: T) -> Unit) {
|
||||||
|
liveData.removeObservers(this)
|
||||||
liveData.observe(this) { action(it) }
|
liveData.observe(this) { action(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ package com.lagradost.cloudstream3.ui.account
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
@ -17,13 +19,17 @@ 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.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||||
|
import com.lagradost.cloudstream3.utils.BiometricAuthenticator
|
||||||
|
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
|
||||||
|
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.accounts
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.accounts
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.selectedKeyIndex
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.selectedKeyIndex
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setAccount
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setAccount
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||||
|
|
||||||
class AccountSelectActivity : AppCompatActivity() {
|
class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.BiometricAuthCallback {
|
||||||
|
|
||||||
lateinit var viewModel: AccountViewModel
|
lateinit var viewModel: AccountViewModel
|
||||||
|
|
||||||
|
@ -41,13 +47,34 @@ class AccountSelectActivity : AppCompatActivity() {
|
||||||
)
|
)
|
||||||
|
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val skipStartup = settingsManager.getBoolean(
|
val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_key), false)
|
||||||
getString(R.string.skip_startup_account_select_key),
|
val skipStartup = settingsManager.getBoolean(getString(R.string.skip_startup_account_select_key), false
|
||||||
false
|
|
||||||
) || accounts.count() <= 1
|
) || accounts.count() <= 1
|
||||||
|
|
||||||
viewModel = ViewModelProvider(this)[AccountViewModel::class.java]
|
viewModel = ViewModelProvider(this)[AccountViewModel::class.java]
|
||||||
|
|
||||||
|
fun askBiometricAuth() {
|
||||||
|
|
||||||
|
if (isTruePhone() && authEnabled) {
|
||||||
|
if (deviceHasPasswordPinLock(this)) {
|
||||||
|
startBiometricAuthentication(this, R.string.biometric_authentication_title, false)
|
||||||
|
|
||||||
|
BiometricAuthenticator.promptInfo?.let {
|
||||||
|
BiometricAuthenticator.biometricPrompt?.authenticate(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showToast(R.string.phone_not_secured, Toast.LENGTH_LONG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
observe(viewModel.isAllowedLogin) { isAllowedLogin ->
|
||||||
|
if (isAllowedLogin) {
|
||||||
|
// We are allowed to continue to MainActivity
|
||||||
|
navigateToMainActivity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Don't show account selection if there is only
|
// Don't show account selection if there is only
|
||||||
// one account that exists
|
// one account that exists
|
||||||
if (!isEditingFromMainActivity && skipStartup) {
|
if (!isEditingFromMainActivity && skipStartup) {
|
||||||
|
@ -55,12 +82,6 @@ class AccountSelectActivity : AppCompatActivity() {
|
||||||
if (currentAccount?.lockPin != null) {
|
if (currentAccount?.lockPin != null) {
|
||||||
CommonActivity.init(this)
|
CommonActivity.init(this)
|
||||||
viewModel.handleAccountSelect(currentAccount, this, true)
|
viewModel.handleAccountSelect(currentAccount, this, true)
|
||||||
observe(viewModel.isAllowedLogin) { isAllowedLogin ->
|
|
||||||
if (isAllowedLogin) {
|
|
||||||
// We are allowed to continue to MainActivity
|
|
||||||
navigateToMainActivity()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (accounts.count() > 1) {
|
if (accounts.count() > 1) {
|
||||||
showToast(this, getString(
|
showToast(this, getString(
|
||||||
|
@ -88,12 +109,6 @@ class AccountSelectActivity : AppCompatActivity() {
|
||||||
// Handle the selected account
|
// Handle the selected account
|
||||||
accountSelectCallback = {
|
accountSelectCallback = {
|
||||||
viewModel.handleAccountSelect(it, this)
|
viewModel.handleAccountSelect(it, this)
|
||||||
observe(viewModel.isAllowedLogin) { isAllowedLogin ->
|
|
||||||
if (isAllowedLogin) {
|
|
||||||
// We are allowed to continue to MainActivity
|
|
||||||
navigateToMainActivity()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
accountCreateCallback = { viewModel.handleAccountUpdate(it, this) },
|
accountCreateCallback = { viewModel.handleAccountUpdate(it, this) },
|
||||||
accountEditCallback = {
|
accountEditCallback = {
|
||||||
|
@ -158,6 +173,8 @@ class AccountSelectActivity : AppCompatActivity() {
|
||||||
} else 6
|
} else 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
askBiometricAuth()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToMainActivity() {
|
private fun navigateToMainActivity() {
|
||||||
|
@ -165,4 +182,8 @@ class AccountSelectActivity : AppCompatActivity() {
|
||||||
startActivity(mainIntent)
|
startActivity(mainIntent)
|
||||||
finish() // Finish the account selection activity
|
finish() // Finish the account selection activity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationSuccess() {
|
||||||
|
Log.i(BiometricAuthenticator.TAG,"Authentication successful in AccountSelectActivity")
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -131,6 +131,18 @@ class LibraryFragment : Fragment() {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateRandom() {
|
||||||
|
val position = libraryViewModel.currentPage.value ?: 0
|
||||||
|
val pages = (libraryViewModel.pages.value as? Resource.Success)?.value ?: return
|
||||||
|
if (toggleRandomButton) {
|
||||||
|
listLibraryItems.clear()
|
||||||
|
listLibraryItems.addAll(pages[position].items)
|
||||||
|
binding?.libraryRandom?.isVisible = listLibraryItems.isNotEmpty()
|
||||||
|
} else {
|
||||||
|
binding?.libraryRandom?.isGone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("ResourceType", "CutPasteId")
|
@SuppressLint("ResourceType", "CutPasteId")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
@ -395,15 +407,7 @@ class LibraryFragment : Fragment() {
|
||||||
binding?.viewpager?.setCurrentItem(page, false)
|
binding?.viewpager?.setCurrentItem(page, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(libraryViewModel.currentPage){
|
updateRandom()
|
||||||
if (toggleRandomButton) {
|
|
||||||
listLibraryItems.clear()
|
|
||||||
listLibraryItems.addAll(pages[it].items)
|
|
||||||
libraryRandom.isVisible = listLibraryItems.isNotEmpty()
|
|
||||||
} else {
|
|
||||||
libraryRandom.isGone = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only stop loading after 300ms to hide the fade effect the viewpager produces when updating
|
// Only stop loading after 300ms to hide the fade effect the viewpager produces when updating
|
||||||
// Without this there would be a flashing effect:
|
// Without this there would be a flashing effect:
|
||||||
|
@ -481,6 +485,7 @@ class LibraryFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(libraryViewModel.currentPage) { position ->
|
observe(libraryViewModel.currentPage) { position ->
|
||||||
|
updateRandom()
|
||||||
val all = binding?.viewpager?.allViews?.toList()
|
val all = binding?.viewpager?.allViews?.toList()
|
||||||
?.filterIsInstance<AutofitRecyclerView>()
|
?.filterIsInstance<AutofitRecyclerView>()
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
||||||
|
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.AccountManagmentBinding
|
import com.lagradost.cloudstream3.databinding.AccountManagmentBinding
|
||||||
|
@ -32,7 +33,10 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSet
|
||||||
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.utils.AppUtils.html
|
||||||
|
import com.lagradost.cloudstream3.utils.BackupUtils
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogText
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
|
@ -256,6 +260,19 @@ class SettingsAccount : PreferenceFragmentCompat() {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
setPreferencesFromResource(R.xml.settings_account, rootKey)
|
setPreferencesFromResource(R.xml.settings_account, rootKey)
|
||||||
|
|
||||||
|
getPref(R.string.biometric_key)?.setOnPreferenceClickListener {
|
||||||
|
|
||||||
|
BackupUtils.backup(activity)
|
||||||
|
val title = activity?.getString(R.string.biometric_setting)
|
||||||
|
val warning = activity?.getString(R.string.biometric_warning)
|
||||||
|
activity?.showBottomDialogText(
|
||||||
|
title as String,
|
||||||
|
warning.html()
|
||||||
|
) { onDialogDismissedEvent }
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
val syncApis =
|
val syncApis =
|
||||||
listOf(
|
listOf(
|
||||||
R.string.mal_key to malApi,
|
R.string.mal_key to malApi,
|
||||||
|
|
|
@ -66,6 +66,7 @@ object BackupUtils {
|
||||||
|
|
||||||
OPEN_SUBTITLES_USER_KEY,
|
OPEN_SUBTITLES_USER_KEY,
|
||||||
"nginx_user", // Nginx user key
|
"nginx_user", // Nginx user key
|
||||||
|
"biometric_key" // can lock down users if backup is shared on a incompatible device
|
||||||
)
|
)
|
||||||
|
|
||||||
/** false if blacklisted key */
|
/** false if blacklisted key */
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.KeyguardManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||||
|
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||||
|
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
|
import com.lagradost.cloudstream3.R
|
||||||
|
|
||||||
|
object BiometricAuthenticator {
|
||||||
|
|
||||||
|
private const val MAX_FAILED_ATTEMPTS = 3
|
||||||
|
private var failedAttempts = 0
|
||||||
|
const val TAG = "cs3Auth"
|
||||||
|
|
||||||
|
private var biometricManager: BiometricManager? = null
|
||||||
|
var biometricPrompt: BiometricPrompt? = null
|
||||||
|
var promptInfo: BiometricPrompt.PromptInfo? = null
|
||||||
|
|
||||||
|
var authCallback: BiometricAuthCallback? = null // listen to authentication success
|
||||||
|
|
||||||
|
private fun initializeBiometrics(activity: Activity) {
|
||||||
|
val executor = ContextCompat.getMainExecutor(activity)
|
||||||
|
|
||||||
|
biometricManager = BiometricManager.from(activity)
|
||||||
|
|
||||||
|
biometricPrompt = BiometricPrompt(
|
||||||
|
activity as FragmentActivity,
|
||||||
|
executor,
|
||||||
|
object : BiometricPrompt.AuthenticationCallback() {
|
||||||
|
|
||||||
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||||
|
super.onAuthenticationError(errorCode, errString)
|
||||||
|
showToast("$errString")
|
||||||
|
Log.e(TAG, "$errorCode")
|
||||||
|
failedAttempts++
|
||||||
|
|
||||||
|
if (failedAttempts >= MAX_FAILED_ATTEMPTS) {
|
||||||
|
failedAttempts = 0
|
||||||
|
activity.finish()
|
||||||
|
} else {
|
||||||
|
failedAttempts = 0
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||||
|
super.onAuthenticationSucceeded(result)
|
||||||
|
failedAttempts = 0
|
||||||
|
authCallback?.onAuthenticationSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAuthenticationFailed() {
|
||||||
|
super.onAuthenticationFailed()
|
||||||
|
failedAttempts++
|
||||||
|
if (failedAttempts >= MAX_FAILED_ATTEMPTS) {
|
||||||
|
failedAttempts = 0
|
||||||
|
activity.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
// authentication dialog prompt builder
|
||||||
|
private fun authenticationDialog(
|
||||||
|
activity: Activity,
|
||||||
|
title: Int,
|
||||||
|
setDeviceCred: Boolean,
|
||||||
|
) {
|
||||||
|
val description = activity.getString(R.string.biometric_prompt_description)
|
||||||
|
|
||||||
|
if (setDeviceCred) {
|
||||||
|
// For API level > 30, Newer API setAllowedAuthenticators is used
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
|
||||||
|
val authFlag = DEVICE_CREDENTIAL or BIOMETRIC_WEAK or BIOMETRIC_STRONG
|
||||||
|
promptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setTitle(activity.getString(title))
|
||||||
|
.setDescription(description)
|
||||||
|
.setAllowedAuthenticators(authFlag)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// for apis < 30
|
||||||
|
promptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setTitle(activity.getString(title))
|
||||||
|
.setDescription(description)
|
||||||
|
.setDeviceCredentialAllowed(true)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// fallback for A12+ when both fingerprint & Face unlock is absent but PIN is set
|
||||||
|
promptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setTitle(activity.getString(title))
|
||||||
|
.setDescription(description)
|
||||||
|
.setDeviceCredentialAllowed(true)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isBiometricHardWareAvailable(): Boolean {
|
||||||
|
// authentication occurs only when this is true and device is truly capable
|
||||||
|
var result = false
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
|
||||||
|
when (biometricManager?.canAuthenticate(
|
||||||
|
DEVICE_CREDENTIAL or BIOMETRIC_STRONG or BIOMETRIC_WEAK
|
||||||
|
)) {
|
||||||
|
BiometricManager.BIOMETRIC_SUCCESS -> result = true
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true
|
||||||
|
BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
when (biometricManager?.canAuthenticate()) {
|
||||||
|
BiometricManager.BIOMETRIC_SUCCESS -> result = true
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true
|
||||||
|
BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true
|
||||||
|
BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks if device is secured i.e has at least some type of lock
|
||||||
|
fun deviceHasPasswordPinLock(context: Context?): Boolean {
|
||||||
|
val keyMgr =
|
||||||
|
context?.getSystemService(AppCompatActivity.KEYGUARD_SERVICE) as? KeyguardManager
|
||||||
|
return keyMgr?.isKeyguardSecure ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// function to start authentication in any fragment or activity
|
||||||
|
fun startBiometricAuthentication(activity: Activity, title: Int, setDeviceCred: Boolean) {
|
||||||
|
initializeBiometrics(activity)
|
||||||
|
|
||||||
|
if (isBiometricHardWareAvailable()) {
|
||||||
|
authCallback = activity as? BiometricAuthCallback
|
||||||
|
authenticationDialog(activity, title, setDeviceCred)
|
||||||
|
promptInfo?.let { biometricPrompt?.authenticate(it) }
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (deviceHasPasswordPinLock(activity)) {
|
||||||
|
authCallback = activity as? BiometricAuthCallback
|
||||||
|
authenticationDialog(activity, R.string.password_pin_authentication_title, true)
|
||||||
|
promptInfo?.let { biometricPrompt?.authenticate(it) }
|
||||||
|
|
||||||
|
} else {
|
||||||
|
showToast(R.string.biometric_unsupported)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BiometricAuthCallback {
|
||||||
|
fun onAuthenticationSuccess()
|
||||||
|
}
|
||||||
|
}
|
|
@ -531,7 +531,6 @@ object UIHelper {
|
||||||
WindowInsetsControllerCompat(window, View(this)).show(WindowInsetsCompat.Type.systemBars())
|
WindowInsetsControllerCompat(window, View(this)).show(WindowInsetsCompat.Type.systemBars())
|
||||||
|
|
||||||
} else {*/ /** WINDOW COMPAT IS BUGGY DUE TO FU*KED UP PLAYER AND TRAILERS **/
|
} else {*/ /** WINDOW COMPAT IS BUGGY DUE TO FU*KED UP PLAYER AND TRAILERS **/
|
||||||
Suppress("DEPRECATION")
|
|
||||||
window.decorView.systemUiVisibility =
|
window.decorView.systemUiVisibility =
|
||||||
(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)
|
||||||
//}
|
//}
|
||||||
|
|
11
app/src/main/res/drawable/ic_fingerprint.xml
Normal file
11
app/src/main/res/drawable/ic_fingerprint.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/white"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:width="24dp">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M17.81,4.47c-0.08,0 -0.16,-0.02 -0.23,-0.06C15.66,3.42 14,3 12.01,3c-1.98,0 -3.86,0.47 -5.57,1.41 -0.24,0.13 -0.54,0.04 -0.68,-0.2 -0.13,-0.24 -0.04,-0.55 0.2,-0.68C7.82,2.52 9.86,2 12.01,2c2.13,0 3.99,0.47 6.03,1.52 0.25,0.13 0.34,0.43 0.21,0.67 -0.09,0.18 -0.26,0.28 -0.44,0.28zM3.5,9.72c-0.1,0 -0.2,-0.03 -0.29,-0.09 -0.23,-0.16 -0.28,-0.47 -0.12,-0.7 0.99,-1.4 2.25,-2.5 3.75,-3.27C9.98,4.04 14,4.03 17.15,5.65c1.5,0.77 2.76,1.86 3.75,3.25 0.16,0.22 0.11,0.54 -0.12,0.7 -0.23,0.16 -0.54,0.11 -0.7,-0.12 -0.9,-1.26 -2.04,-2.25 -3.39,-2.94 -2.87,-1.47 -6.54,-1.47 -9.4,0.01 -1.36,0.7 -2.5,1.7 -3.4,2.96 -0.08,0.14 -0.23,0.21 -0.39,0.21zM9.75,21.79c-0.13,0 -0.26,-0.05 -0.35,-0.15 -0.87,-0.87 -1.34,-1.43 -2.01,-2.64 -0.69,-1.23 -1.05,-2.73 -1.05,-4.34 0,-2.97 2.54,-5.39 5.66,-5.39s5.66,2.42 5.66,5.39c0,0.28 -0.22,0.5 -0.5,0.5s-0.5,-0.22 -0.5,-0.5c0,-2.42 -2.09,-4.39 -4.66,-4.39s-4.66,1.97 -4.66,4.39c0,1.44 0.32,2.77 0.93,3.85 0.64,1.15 1.08,1.64 1.85,2.42 0.19,0.2 0.19,0.51 0,0.71 -0.11,0.1 -0.24,0.15 -0.37,0.15zM16.92,19.94c-1.19,0 -2.24,-0.3 -3.1,-0.89 -1.49,-1.01 -2.38,-2.65 -2.38,-4.39 0,-0.28 0.22,-0.5 0.5,-0.5s0.5,0.22 0.5,0.5c0,1.41 0.72,2.74 1.94,3.56 0.71,0.48 1.54,0.71 2.54,0.71 0.24,0 0.64,-0.03 1.04,-0.1 0.27,-0.05 0.53,0.13 0.58,0.41 0.05,0.27 -0.13,0.53 -0.41,0.58 -0.57,0.11 -1.07,0.12 -1.21,0.12zM14.91,22c-0.04,0 -0.09,-0.01 -0.13,-0.02 -1.59,-0.44 -2.63,-1.03 -3.72,-2.1 -1.4,-1.39 -2.17,-3.24 -2.17,-5.22 0,-1.62 1.38,-2.94 3.08,-2.94s3.08,1.32 3.08,2.94c0,1.07 0.93,1.94 2.08,1.94s2.08,-0.87 2.08,-1.94c0,-3.77 -3.25,-6.83 -7.25,-6.83 -2.84,0 -5.44,1.58 -6.61,4.03 -0.39,0.81 -0.59,1.76 -0.59,2.8 0,0.78 0.07,2.01 0.67,3.61 0.1,0.26 -0.03,0.55 -0.29,0.64 -0.26,0.1 -0.55,-0.04 -0.64,-0.29 -0.49,-1.31 -0.73,-2.61 -0.73,-3.96 0,-1.2 0.23,-2.29 0.68,-3.24 1.33,-2.79 4.28,-4.6 7.51,-4.6 4.55,0 8.25,3.51 8.25,7.83 0,1.62 -1.38,2.94 -3.08,2.94s-3.08,-1.32 -3.08,-2.94c0,-1.07 -0.93,-1.94 -2.08,-1.94s-2.08,0.87 -2.08,1.94c0,1.71 0.66,3.31 1.87,4.51 0.95,0.94 1.86,1.46 3.27,1.85 0.27,0.07 0.42,0.35 0.35,0.61 -0.05,0.23 -0.26,0.38 -0.47,0.38z"/>
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/library_icon.xml
Normal file
10
app/src/main/res/drawable/library_icon.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960"
|
||||||
|
android:tint="?attr/white">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L400,160L480,240L800,240Q833,240 856.5,263.5Q880,287 880,320L447,320L367,240L160,240Q160,240 160,240Q160,240 160,240L160,720Q160,720 160,720Q160,720 160,720L256,400L940,400L837,743Q829,769 807.5,784.5Q786,800 760,800L160,800ZM244,720L760,720L832,480L316,480L244,720ZM244,720L316,480L316,480L244,720L244,720ZM160,320L160,240Q160,240 160,240Q160,240 160,240L160,240L160,320L160,320Z"/>
|
||||||
|
</vector>
|
|
@ -10,7 +10,7 @@
|
||||||
android:title="@string/title_search" />
|
android:title="@string/title_search" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/navigation_library"
|
android:id="@+id/navigation_library"
|
||||||
android:icon="@drawable/ic_outline_account_circle_24"
|
android:icon="@drawable/library_icon"
|
||||||
android:title="@string/library" />
|
android:title="@string/library" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/navigation_downloads"
|
android:id="@+id/navigation_downloads"
|
||||||
|
|
|
@ -212,6 +212,7 @@
|
||||||
<item>Banana</item>
|
<item>Banana</item>
|
||||||
<item>Fiesta</item>
|
<item>Fiesta</item>
|
||||||
<item>Dolor rosa</item>
|
<item>Dolor rosa</item>
|
||||||
|
<item>Lavanda</item>
|
||||||
<item>Material You</item>
|
<item>Material You</item>
|
||||||
<item>Material You (Secondary)</item>
|
<item>Material You (Secondary)</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
@ -235,6 +236,7 @@
|
||||||
<item>Banana</item>
|
<item>Banana</item>
|
||||||
<item>Party</item>
|
<item>Party</item>
|
||||||
<item>Pink</item>
|
<item>Pink</item>
|
||||||
|
<item>Lavender</item>
|
||||||
<item>Monet</item>
|
<item>Monet</item>
|
||||||
<item>Monet2</item>
|
<item>Monet2</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
|
@ -221,6 +221,7 @@
|
||||||
<item>Bananowy</item>
|
<item>Bananowy</item>
|
||||||
<item>Łososiowy</item>
|
<item>Łososiowy</item>
|
||||||
<item>Świnko peppowy</item>
|
<item>Świnko peppowy</item>
|
||||||
|
<item>Lawenda</item>
|
||||||
<item>Material You</item>
|
<item>Material You</item>
|
||||||
<item>Material You (drugorzędny)</item>
|
<item>Material You (drugorzędny)</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
@ -244,6 +245,7 @@
|
||||||
<item>Banana</item>
|
<item>Banana</item>
|
||||||
<item>Party</item>
|
<item>Party</item>
|
||||||
<item>Pink</item>
|
<item>Pink</item>
|
||||||
|
<item>Lavender</item>
|
||||||
<item>Monet</item>
|
<item>Monet</item>
|
||||||
<item>Monet2</item>
|
<item>Monet2</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
|
@ -247,6 +247,7 @@
|
||||||
<item>Muz</item>
|
<item>Muz</item>
|
||||||
<item>Parti</item>
|
<item>Parti</item>
|
||||||
<item>Pembe</item>
|
<item>Pembe</item>
|
||||||
|
<item>Lavanta</item>
|
||||||
<item>Material You</item>
|
<item>Material You</item>
|
||||||
<item>Material You (İkincil)</item>
|
<item>Material You (İkincil)</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
@ -270,6 +271,7 @@
|
||||||
<item>Banana</item>
|
<item>Banana</item>
|
||||||
<item>Party</item>
|
<item>Party</item>
|
||||||
<item>Pink</item>
|
<item>Pink</item>
|
||||||
|
<item>Lavender</item>
|
||||||
<item>Monet</item>
|
<item>Monet</item>
|
||||||
<item>Monet2</item>
|
<item>Monet2</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
|
@ -213,6 +213,7 @@
|
||||||
<item>Vàng</item>
|
<item>Vàng</item>
|
||||||
<item>Hồng</item>
|
<item>Hồng</item>
|
||||||
<item>Hồng đậm</item>
|
<item>Hồng đậm</item>
|
||||||
|
<item>Hoa oải hương</item>
|
||||||
<item>Material You</item>
|
<item>Material You</item>
|
||||||
<item>Material You (Secondary)</item>
|
<item>Material You (Secondary)</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
@ -236,6 +237,7 @@
|
||||||
<item>Banana</item>
|
<item>Banana</item>
|
||||||
<item>Party</item>
|
<item>Party</item>
|
||||||
<item>Pink</item>
|
<item>Pink</item>
|
||||||
|
<item>Lavender</item>
|
||||||
<item>Monet</item>
|
<item>Monet</item>
|
||||||
<item>Monet2</item>
|
<item>Monet2</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
|
@ -284,6 +284,7 @@
|
||||||
<item>Banana</item>
|
<item>Banana</item>
|
||||||
<item>Party</item>
|
<item>Party</item>
|
||||||
<item>Pink Pain</item>
|
<item>Pink Pain</item>
|
||||||
|
<item>Lavender</item>
|
||||||
<item>Material You</item>
|
<item>Material You</item>
|
||||||
<item>Material You (Secondary)</item>
|
<item>Material You (Secondary)</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
@ -307,6 +308,7 @@
|
||||||
<item>Banana</item>
|
<item>Banana</item>
|
||||||
<item>Party</item>
|
<item>Party</item>
|
||||||
<item>Pink</item>
|
<item>Pink</item>
|
||||||
|
<item>Lavender</item>
|
||||||
<item>Monet</item>
|
<item>Monet</item>
|
||||||
<item>Monet2</item>
|
<item>Monet2</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
|
@ -84,6 +84,7 @@
|
||||||
<color name="colorPrimaryOrange">#CE8500</color>
|
<color name="colorPrimaryOrange">#CE8500</color>
|
||||||
<color name="colorPrimaryDandelionYellow">#F5BB00</color>
|
<color name="colorPrimaryDandelionYellow">#F5BB00</color>
|
||||||
<color name="colorPrimaryCoolBlue">#408cac</color>
|
<color name="colorPrimaryCoolBlue">#408cac</color>
|
||||||
|
<color name="colorPrimaryLavender">#6F55AF</color>
|
||||||
|
|
||||||
<color name="colorTestPass">#48E484</color>
|
<color name="colorTestPass">#48E484</color>
|
||||||
<color name="colorTestFail">#ea596e</color>
|
<color name="colorTestFail">#ea596e</color>
|
||||||
|
|
|
@ -247,7 +247,7 @@
|
||||||
<string name="backup_failed_error_format">Error backing up %s</string>
|
<string name="backup_failed_error_format">Error backing up %s</string>
|
||||||
<string name="search">Search</string>
|
<string name="search">Search</string>
|
||||||
<string name="library">Library</string>
|
<string name="library">Library</string>
|
||||||
<string name="category_account">Accounts</string>
|
<string name="category_account">Accounts and Security</string>
|
||||||
<string name="category_updates">Updates and backup</string>
|
<string name="category_updates">Updates and backup</string>
|
||||||
<string name="settings_info">Info</string>
|
<string name="settings_info">Info</string>
|
||||||
<string name="advanced_search">Advanced Search</string>
|
<string name="advanced_search">Advanced Search</string>
|
||||||
|
@ -751,4 +751,17 @@
|
||||||
<string name="rotate_video_desc">Display a toggle button for screen orientation</string>
|
<string name="rotate_video_desc">Display a toggle button for screen orientation</string>
|
||||||
<string name="auto_rotate_video_desc">Enable automatic switching of screen orientation based on video orientation</string>
|
<string name="auto_rotate_video_desc">Enable automatic switching of screen orientation based on video orientation</string>
|
||||||
<string name="auto_rotate_video">Auto rotate</string>
|
<string name="auto_rotate_video">Auto rotate</string>
|
||||||
|
|
||||||
|
<!-- For Biometrics -->
|
||||||
|
<string name="biometric_authentication_title">Unlock CloudStream</string>
|
||||||
|
<string name="biometric_setting">Lock with Biometrics</string>
|
||||||
|
<string name="biometric_key" translatable="false">biometric_key</string>
|
||||||
|
<string name="password_pin_authentication_title">Password/PIN Authentication</string>
|
||||||
|
<string name="biometric_unsupported">Biometric authentication is not supported on this device</string>
|
||||||
|
<string name="phone_not_secured">Please disable fingerprint authentication if device has no lock.</string>
|
||||||
|
<string name="biometric_setting_summary">Unlock the app with Fingerprint, Face ID, PIN, Pattern and Password.</string>
|
||||||
|
<string name="biometric_prompt_description">This window will close after few failed attempts. You\'ll have to restart the App.</string>
|
||||||
|
<string name="biometric_warning">Your CloudStream data has been backed up now, although probability of this rare case is very low but all
|
||||||
|
devices behave differently, in case you get locked down from accessing the app in worst case scenario,
|
||||||
|
Clear the app data wholly and restore the backup. Any inconvenience if arrived is deeply regretted.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -383,6 +383,16 @@
|
||||||
<item name="android:colorAccent">@color/colorPrimaryCoolBlue</item>
|
<item name="android:colorAccent">@color/colorPrimaryCoolBlue</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="OverlayPrimaryColorLavender">
|
||||||
|
<item name="colorPrimary">@color/colorPrimaryLavender</item>
|
||||||
|
<item name="android:colorPrimary">@color/colorPrimaryLavender</item>
|
||||||
|
<item name="colorPrimaryDark">#6B51AB</item>
|
||||||
|
<item name="colorAccent">#7961B4</item>
|
||||||
|
<item name="colorOnPrimary">@color/whiteText</item>
|
||||||
|
<!-- Needed for leanback fuckery -->
|
||||||
|
<item name="android:colorAccent">@color/colorPrimaryLavender</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="customRatingBar" parent="@style/Widget.AppCompat.RatingBar">
|
<style name="customRatingBar" parent="@style/Widget.AppCompat.RatingBar">
|
||||||
|
|
||||||
<item name="android:progressDrawable">@drawable/abc_ratingbar_indicator_material</item>
|
<item name="android:progressDrawable">@drawable/abc_ratingbar_indicator_material</item>
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
<SwitchPreference
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:icon="@drawable/ic_outline_account_circle_24"
|
|
||||||
android:key="@string/skip_startup_account_select_key"
|
|
||||||
android:title="@string/skip_startup_account_select_pref" />
|
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:icon="@drawable/mal_logo"
|
android:icon="@drawable/mal_logo"
|
||||||
|
@ -22,17 +16,18 @@
|
||||||
<Preference
|
<Preference
|
||||||
android:icon="@drawable/open_subtitles_icon"
|
android:icon="@drawable/open_subtitles_icon"
|
||||||
android:key="@string/opensubtitles_key" />
|
android:key="@string/opensubtitles_key" />
|
||||||
<!-- <Preference-->
|
|
||||||
<!-- android:key="@string/nginx_key"-->
|
|
||||||
<!-- android:icon="@drawable/nginx" />-->
|
|
||||||
|
|
||||||
<!-- <Preference-->
|
<SwitchPreference
|
||||||
<!-- android:title="@string/nginx_info_title"-->
|
android:defaultValue="false"
|
||||||
<!-- android:icon="@drawable/nginx_question"-->
|
android:icon="@drawable/ic_outline_account_circle_24"
|
||||||
<!-- android:summary="@string/nginx_info_summary">-->
|
android:key="@string/skip_startup_account_select_key"
|
||||||
<!-- <intent-->
|
android:title="@string/skip_startup_account_select_pref" />
|
||||||
<!-- android:action="android.intent.action.VIEW"-->
|
|
||||||
<!-- android:data="https://www.sarlays.com/use-nginx-with-cloudstream/" />-->
|
<SwitchPreferenceCompat
|
||||||
<!-- </Preference>-->
|
android:key="@string/biometric_key"
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:summary="@string/biometric_setting_summary"
|
||||||
|
android:icon="@drawable/ic_fingerprint"
|
||||||
|
android:title="@string/biometric_setting" />
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
Loading…
Add table
Add a link
Reference in a new issue