diff --git a/.github/images/download-cloudstream-pre.png b/.github/images/download-cloudstream-pre.png
deleted file mode 100644
index db1f90552..000000000
Binary files a/.github/images/download-cloudstream-pre.png and /dev/null differ
diff --git a/README.md b/README.md
index 237d7d8fc..c2492c5d8 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
# CloudStream
**⚠️ Warning: By default, this app doesn't provide any video sources; you have to install extensions to add functionality to the app.**
-

[](https://discord.gg/5Hus6fM)
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index c714368ee..2ad8451a3 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -9,8 +9,6 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.dokka)
alias(libs.plugins.kotlin.serialization)
- alias(libs.plugins.compose.multiplatform)
- alias(libs.plugins.compose.compiler)
}
val javaTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get())
@@ -290,12 +288,7 @@ dependencies {
implementation(libs.work.runtime.ktx)
implementation(libs.nicehttp) // HTTP Lib
- implementation(libs.bundles.compose)
- implementation(libs.activity.compose)
- implementation(libs.kotlinx.io.core) // Logcat parser
-
implementation(project(":library"))
- implementation(project(":shared"))
}
tasks.register("androidSourcesJar") {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1012efb56..ee4c978f2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -253,10 +253,6 @@
-
-
Unit) {
- if (updaterState.dialog == null) {
- return
- }
-
- val title: String
- val body: @Composable () -> Unit
- val confirmButton: @Composable () -> Unit
- val dismissButton: @Composable () -> Unit
- val cancelable: Boolean
-
- when (val state = updaterState.dialog.state) {
- is GithubUpdateDialogState.Error -> {
- // Only show error if it is initialized by the user
- if (!updaterState.dialog.isFromUser) return
-
- cancelable = true
- title = stringResource(R.string.download_failed)
- body = { Text(text = state.error.getStackTracePretty()) }
- confirmButton = {
- WhiteButton(text = stringResource(R.string.check_for_update), onClick = {
- onAction(SearchForUpdate)
- })
- }
- dismissButton = {
- BlackButton(text = stringResource(R.string.ok), onClick = {
- onAction(Dismiss)
- })
- }
- }
-
- GithubUpdateDialogState.Loading -> {
- // Only show loading if it is initialized by the user
- if (!updaterState.dialog.isFromUser) return
-
- cancelable = true
- title = stringResource(R.string.loading)
- body = {
- LinearProgressIndicator(
- modifier = Modifier.fillMaxWidth(),
- color = MaterialTheme.colorScheme.onBackground,
- trackColor = MaterialTheme.colorScheme.surfaceVariant,
- )
- }
- confirmButton = {}
- dismissButton = {
- BlackButton(text = stringResource(R.string.cancel), onClick = {
- onAction(Dismiss)
- })
- }
- }
-
- GithubUpdateDialogState.NoUpdateFound -> {
- // Only show error if it is initialized by the user
- if (!updaterState.dialog.isFromUser) return
-
- cancelable = true
- title = stringResource(R.string.no_update_found)
- body = {}
- confirmButton = {
- WhiteButton(text = stringResource(R.string.ok), onClick = {
- onAction(Dismiss)
- })
- }
- dismissButton = {}
- }
-
- is GithubUpdateDialogState.UpdateFound -> {
- cancelable = true
- title = stringResource(
- R.string.new_update_format,
- (state.oldSha ?: BuildConfig.VERSION_NAME),
- (state.newSha ?: state.file.displayName)
- )
- body = { Text(text = state.file.changeLog) }
- confirmButton = {
- WhiteButton(text = stringResource(R.string.update), onClick = {
- onAction(Update(state.file))
- })
- }
- dismissButton = {
- BlackButton(text = stringResource(R.string.skip_update), onClick = {
- onAction(SkipUpdate(state.file))
- onAction(Dismiss)
- })
- BlackButton(text = stringResource(R.string.cancel), onClick = {
- onAction(Dismiss)
- })
- }
- }
-
- is GithubUpdateDialogState.DownloadProgress -> {
- cancelable = false
- title = stringResource(R.string.update_notification_downloading)
- body = {
- if (state.total != null) {
- val progress =
- (state.progress.toFloat() / state.total.toFloat()).coerceIn(0.0f, 1.0f)
- Column(horizontalAlignment = Alignment.CenterHorizontally) {
- Text(
- color = MaterialTheme.colorScheme.onBackground,
- style = MaterialTheme.typography.bodyMedium,
- textAlign = TextAlign.Center,
- // Round down the progress to avoid 100%
- text = "${(progress * 100.0f).toInt()}% (${
- formatFileSize(
- LocalContext.current,
- state.progress
- )
- } / ${
- formatFileSize(
- LocalContext.current,
- state.total
- )
- })"
- )
- Spacer(modifier = Modifier.height(MaterialTheme.padding.medium))
- LinearProgressIndicator(
- modifier = Modifier.fillMaxWidth(),
- color = MaterialTheme.colorScheme.onBackground,
- trackColor = MaterialTheme.colorScheme.surfaceVariant,
- progress = { progress }
- )
- }
- } else {
- Column(horizontalAlignment = Alignment.CenterHorizontally) {
- Text(
- color = MaterialTheme.colorScheme.onBackground,
- style = MaterialTheme.typography.bodyMedium,
- textAlign = TextAlign.Center,
- text = formatFileSize(
- LocalContext.current,
- state.progress
- )
- )
- Spacer(modifier = Modifier.height(MaterialTheme.padding.medium))
- LinearProgressIndicator(
- modifier = Modifier.fillMaxWidth(),
- color = MaterialTheme.colorScheme.onBackground,
- trackColor = MaterialTheme.colorScheme.surfaceVariant,
- )
- }
- }
- }
- confirmButton = {}
- dismissButton = {
- BlackButton(text = stringResource(R.string.cancel), onClick = {
- onAction(Dismiss)
- })
- }
- }
- }
-
- AlertDialog(
- properties = DialogProperties(usePlatformDefaultWidth = false),
- containerColor = MaterialTheme.colorScheme.background,
- onDismissRequest = {
- if (cancelable) {
- onAction(Dismiss)
- }
- },
- title = { Text(text = title) },
- text = body,
- confirmButton = confirmButton,
- dismissButton = dismissButton
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt
index 0fdccb476..f4e8768d8 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt
@@ -98,7 +98,7 @@ object VideoClickActionHolder {
?.second
}
- fun getPlayers(context: Context? = null) = allVideoClickActions.filter { it.isPlayer && it.shouldShowSafe(context, null) }
+ fun getPlayers(activity: Activity? = null) = allVideoClickActions.filter { it.isPlayer && it.shouldShowSafe(activity, null) }
}
abstract class VideoClickAction {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt
index e49245904..203a503e9 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt
@@ -37,7 +37,7 @@ fun buildDefaultClient(context: Context, ignoreSSL: Boolean = false): OkHttpClie
safe { Security.insertProviderAt(Conscrypt.newProvider(), 1) }
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
- val dns = settingsManager.getInt(context.getString(R.string.dns_key), 0)
+ val dns = settingsManager.getInt(context.getString(R.string.dns_pref), 0)
val baseClient = OkHttpClient.Builder()
.followRedirects(true)
.followSslRedirects(true)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/receivers/PackageInstallerStatusReceiver.kt b/app/src/main/java/com/lagradost/cloudstream3/receivers/PackageInstallerStatusReceiver.kt
deleted file mode 100644
index 1739b8147..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/receivers/PackageInstallerStatusReceiver.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.lagradost.cloudstream3.receivers
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.pm.PackageInstaller
-import com.lagradost.cloudstream3.utils.getSafeParcelableExtra
-
-/** https://medium.com/@solrudev/painless-building-of-an-android-package-installer-app-d5a09b5df432 */
-class PackageInstallerStatusReceiver : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- when (val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)) {
- PackageInstaller.STATUS_PENDING_USER_ACTION -> {
- intent.getSafeParcelableExtra(Intent.EXTRA_INTENT)?.let { userAction->
- userAction.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- context.startActivity(userAction)
- }
- }
- PackageInstaller.STATUS_SUCCESS -> {
- // do something on success
- }
- else -> {
- val message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
- println("PackageInstallerStatusReceiver: status=$status, message=$message")
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt
index f70458285..92d33d0f3 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt
@@ -1,8 +1,11 @@
package com.lagradost.cloudstream3.ui.account
+import android.os.Build
import android.view.LayoutInflater
import android.view.ViewGroup
+import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
+import coil3.transform.RoundedCornersTransformation
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.AccountListItemAddBinding
import com.lagradost.cloudstream3.databinding.AccountListItemBinding
@@ -15,8 +18,6 @@ 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.ImageLoader.loadImage
-import android.util.TypedValue
-import android.view.View
class AccountAdapter(
private val accountSelectCallback: (DataStoreHelper.Account) -> Unit,
@@ -28,12 +29,6 @@ class AccountAdapter(
companion object {
const val VIEW_TYPE_SELECT_ACCOUNT = 0
const val VIEW_TYPE_EDIT_ACCOUNT = 2
-
- fun View.setRippleForeground() {
- val outValue = TypedValue()
- context.theme.resolveAttribute(android.R.attr.selectableItemBackgroundBorderless, outValue, true)
- foreground = context.getDrawable(outValue.resourceId)
- }
}
@@ -52,25 +47,45 @@ class AccountAdapter(
when (val binding = holder.view) {
is AccountListItemBinding -> binding.apply {
val isTv = isLayout(TV or EMULATOR) || !root.isInTouchMode
+
val isLastUsedAccount = item.keyIndex == DataStoreHelper.selectedKeyIndex
accountName.text = item.name
accountImage.loadImage(item.image)
lockIcon.isVisible = item.lockPin != null
- root.isSelected = isLastUsedAccount
- cardView.isSelected = isLastUsedAccount
+ outline.isVisible = !isTv && isLastUsedAccount
- if (!isTv) cardView.setRippleForeground()
+ if (isTv) {
+ // For emulator but this is fine on TV also
+ root.isFocusableInTouchMode = true
+ if (isLastUsedAccount) {
+ root.requestFocus()
+ }
- if (!isTv) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ root.foreground = ContextCompat.getDrawable(
+ root.context,
+ R.drawable.outline_drawable
+ )
+ }
+ } else {
root.setOnLongClickListener {
showAccountEditDialog(
context = root.context,
account = item,
isNewAccount = false,
- accountEditCallback = { account -> accountEditCallback.invoke(account) },
- accountDeleteCallback = { account -> accountDeleteCallback.invoke(account) }
+ accountEditCallback = { account ->
+ accountEditCallback.invoke(
+ account
+ )
+ },
+ accountDeleteCallback = { account ->
+ accountDeleteCallback.invoke(
+ account
+ )
+ }
)
+
true
}
}
@@ -82,15 +97,30 @@ class AccountAdapter(
is AccountListItemEditBinding -> binding.apply {
val isTv = isLayout(TV or EMULATOR) || !root.isInTouchMode
+
val isLastUsedAccount = item.keyIndex == DataStoreHelper.selectedKeyIndex
accountName.text = item.name
- accountImage.loadImage(item.image)
+ accountImage.loadImage(item.image) {
+ RoundedCornersTransformation(10f)
+ }
lockIcon.isVisible = item.lockPin != null
- root.isSelected = isLastUsedAccount
- cardView.isSelected = isLastUsedAccount
+ outline.isVisible = !isTv && isLastUsedAccount
- if (!isTv) cardView.setRippleForeground()
+ if (isTv) {
+ // For emulator but this is fine on TV also
+ root.isFocusableInTouchMode = true
+ if (isLastUsedAccount) {
+ root.requestFocus()
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ root.foreground = ContextCompat.getDrawable(
+ root.context,
+ R.drawable.outline_drawable
+ )
+ }
+ }
root.setOnClickListener {
showAccountEditDialog(
@@ -98,7 +128,11 @@ class AccountAdapter(
account = item,
isNewAccount = false,
accountEditCallback = { account -> accountEditCallback.invoke(account) },
- accountDeleteCallback = { account -> accountDeleteCallback.invoke(account) }
+ accountDeleteCallback = { account ->
+ accountDeleteCallback.invoke(
+ account
+ )
+ }
)
}
}
@@ -108,9 +142,6 @@ class AccountAdapter(
override fun onBindFooter(holder: ViewHolderState) {
val binding = holder.view as? AccountListItemAddBinding ?: return
binding.apply {
- val isTv = isLayout(TV or EMULATOR) || !root.isInTouchMode
- if (!isTv) cardView.setRippleForeground()
-
root.setOnClickListener {
val accounts = this@AccountAdapter.immutableCurrentList
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt
index c88f70a73..7725bad91 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt
@@ -41,10 +41,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideProgress
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.showInputMethod
import com.lagradost.cloudstream3.utils.UIHelper.showProgress
-import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.setRippleForeground
-import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
-import com.lagradost.cloudstream3.ui.settings.Globals.TV
-import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
object AccountHelper {
fun showAccountEditDialog(
@@ -60,12 +56,6 @@ object AccountHelper {
var currentEditAccount = account
val dialog = builder.show()
- binding.accountImageHolder.requestFocus()
-
- if (!isLayout(TV or EMULATOR)) {
- binding.accountImageHolder.setRippleForeground()
- binding.editProfilePhotoButtonHolder.setRippleForeground()
- }
if (!isNewAccount) binding.title.setText(R.string.edit_account)
@@ -110,7 +100,7 @@ object AccountHelper {
// Handle the profile picture and its interactions
binding.accountImage.loadImage(account.image)
- binding.accountImageHolder.setOnClickListener {
+ binding.accountImage.setOnClickListener {
// Roll the image forwards once
currentEditAccount = currentEditAccount.copy(customImage = null)
currentEditAccount =
@@ -176,12 +166,11 @@ object AccountHelper {
canSetPin = true
- binding.editProfilePhotoButtonHolder.setOnClickListener {
- val builder = AlertDialog.Builder(context, R.style.AlertDialogCustom)
+ binding.editProfilePhotoButton.setOnClickListener {
+ val bottomSheetDialog = BottomSheetDialog(context)
val sheetBinding = BottomInputDialogBinding.inflate(LayoutInflater.from(context))
- builder.setView(sheetBinding.root)
- val dialog = builder.create()
- dialog.show()
+ bottomSheetDialog.setContentView(sheetBinding.root)
+ bottomSheetDialog.show()
sheetBinding.apply {
text1.text = context.getString(R.string.edit_profile_image_title)
@@ -206,7 +195,7 @@ object AccountHelper {
R.string.edit_profile_image_success,
Toast.LENGTH_SHORT
)
- dialog.dismissSafe()
+ bottomSheetDialog.dismissSafe()
},
onError = { _, _ ->
showToast(
@@ -223,7 +212,7 @@ object AccountHelper {
imageLoader.enqueue(request)
}
sheetBinding.cancelBtt.setOnClickListener {
- dialog.dismissSafe()
+ bottomSheetDialog.dismissSafe()
}
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt
index 28f5c15d8..ad323c7d1 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt
@@ -54,15 +54,10 @@ class AccountSelectActivity : FragmentActivity(), BiometricCallback {
false
)
- val isFromMainActivity = intent.getBooleanExtra(
- "isFromMainActivity",
- false
- )
-
// Sometimes we start this activity when we have already logged in
// For example when using cloudstreamsearch://
// In those cases we want to just go to the main activity instantly
- if (hasLoggedIn && !isEditingFromMainActivity && !isFromMainActivity) {
+ if (hasLoggedIn && !isEditingFromMainActivity) {
navigateToMainActivity()
return
}
@@ -103,7 +98,7 @@ class AccountSelectActivity : FragmentActivity(), BiometricCallback {
// Don't show account selection if there is only
// one account that exists
- if (!isFromMainActivity && !isEditingFromMainActivity && skipStartup) {
+ if (!isEditingFromMainActivity && skipStartup) {
val currentAccount = accounts.firstOrNull { it.keyIndex == selectedKeyIndex }
if (currentAccount?.lockPin != null) {
CommonActivity.init(this)
@@ -165,11 +160,6 @@ class AccountSelectActivity : FragmentActivity(), BiometricCallback {
// Scroll to current account (which is focused by default)
val layoutManager = recyclerView.layoutManager as GridLayoutManager
layoutManager.scrollToPositionWithOffset(selectedKeyIndex, 0)
- if (isLayout(TV or EMULATOR)) {
- recyclerView.post {
- layoutManager.findViewByPosition(selectedKeyIndex)?.requestFocus()
- }
- }
}
observe(accountViewModel.isEditing) { isEditing ->
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectLinearItemDecoration.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectLinearItemDecoration.kt
index 91e0caa2a..eb907b344 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectLinearItemDecoration.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectLinearItemDecoration.kt
@@ -8,7 +8,7 @@ class AccountSelectLinearItemDecoration(private val size: Int) : RecyclerView.It
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val layoutParams = view.layoutParams as RecyclerView.LayoutParams
layoutParams.width = size
- layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT
+ layoutParams.height = size
view.layoutParams = layoutParams
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt
index 04f8b0b26..604695859 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt
@@ -18,7 +18,6 @@ import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri
-import androidx.core.view.doOnLayout
import androidx.core.view.isGone
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
@@ -31,9 +30,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.chip.Chip
import com.lagradost.api.Log
-import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.APIHolder.apis
-import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.AllLanguagesName
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.MainAPI
@@ -69,7 +66,6 @@ import com.lagradost.cloudstream3.utils.AppContextUtils.getApiProviderLangSettin
import com.lagradost.cloudstream3.utils.AppContextUtils.isNetworkAvailable
import com.lagradost.cloudstream3.utils.AppContextUtils.isRecyclerScrollable
import com.lagradost.cloudstream3.utils.AppContextUtils.loadSearchResult
-import com.lagradost.cloudstream3.utils.AppContextUtils.openBrowser
import com.lagradost.cloudstream3.utils.AppContextUtils.ownHide
import com.lagradost.cloudstream3.utils.AppContextUtils.ownShow
import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus
@@ -113,14 +109,6 @@ class HomeFragment : BaseFragment(
val errorProfilePic = errorProfilePics.random()
- fun Context.getDisplayName(apiName: String?): String? {
- return when (apiName) {
- noneApi.name -> getString(R.string.none)
- randomApi.name -> getString(R.string.home_random)
- else -> apiName
- }
- }
-
//fun Activity.loadHomepageList(
// item: HomePageList,
// deleteCallback: (() -> Unit)? = null,
@@ -514,10 +502,8 @@ class HomeFragment : BaseFragment(
addAll(remainingApis)
}
- val names = currentValidApis.map {
- val displayName = getDisplayName(it.name)
- if (isMultiLang) "${getFlagFromIso(it.lang)?.plus(" ") ?: ""}$displayName" else displayName
- }
+ val names =
+ currentValidApis.map { if (isMultiLang) "${getFlagFromIso(it.lang)?.plus(" ") ?: ""}${it.name}" else it.name }
val index = currentValidApis.map { it.name }.indexOf(currentApiName)
listView?.setItemChecked(index, true)
arrayAdapter.addAll(names)
@@ -715,38 +701,6 @@ class HomeFragment : BaseFragment(
homeViewModel.queryTextSubmit("")
}
- homePreviewSettingsButton.setOnClickListener { view ->
- val apiName = homeViewModel.apiName.value
- val plugin = APIHolder.getApiFromNameNull(apiName)
- ?.sourcePlugin?.let { PluginManager.plugins[it] } as? Plugin
- val openSettings = plugin?.openSettings
- if (openSettings != null) {
- try {
- val activityContext = view.context.getActivity() ?: view.context
- openSettings.invoke(activityContext)
- } catch (e: Throwable) {
- logError(e)
- }
- }
- }
-
- // Load value for toggling Tv layout real time clock. Hide by default at startup
- // set visibility first, to apply a scroll effect later
- context?.let {
- if (isLayout(TV)) {
- val settingsManager = PreferenceManager.getDefaultSharedPreferences(it)
- val toggleClock =
- settingsManager.getBoolean(
- getString(R.string.tv_layout_clock_key),
- false
- )
- binding.homeClock.isVisible = toggleClock
- } else {
- binding.homeClock.isVisible = false
- }
- }
-
-
homeMasterRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (isLayout(PHONE)) {
@@ -786,26 +740,12 @@ class HomeFragment : BaseFragment(
view.getLocationInWindow(rect)
scrollParent.isVisible = true
scrollParent.translationY = rect[1].toFloat() - 60.toPx
-
- // Move the TV layout real time clock out of the way too
- // We check if we have the correct layout and if the clock is enabled
- if(isLayout(TV) && binding.homeClock.isVisible) {
- val scrollParent = binding.homeClock
-
- val rect = IntArray(2)
- view.getLocationInWindow(rect)
- scrollParent.isVisible = true
- scrollParent.translationY = rect[1].toFloat() - 60.toPx
- }
}
}
super.onScrolled(recyclerView, dx, dy)
- updateTvRailFocus()
}
})
- homeMasterRecycler.doOnLayout { updateTvRailFocus() }
-
}
//Load value for toggling Random button. Hide at startup
@@ -822,10 +762,9 @@ class HomeFragment : BaseFragment(
observe(homeViewModel.apiName) { apiName ->
currentApiName = apiName
- val displayApiName = context?.getDisplayName(apiName) ?: apiName
binding.apply {
- homeApiFab.text = displayApiName
- homeChangeApi.text = displayApiName
+ homeApiFab.text = apiName
+ homeChangeApi.text = apiName
homePreviewReloadProvider.isGone = (apiName == noneApi.name)
homePreviewSearchButton.isGone = (apiName == noneApi.name)
}
@@ -833,12 +772,6 @@ class HomeFragment : BaseFragment(
observe(homeViewModel.page) { data ->
binding.apply {
- if (isLayout(TV or EMULATOR)) {
- val plugin = APIHolder.getApiFromNameNull(homeViewModel.apiName.value)
- ?.sourcePlugin?.let { PluginManager.plugins[it] } as? Plugin
- homePreviewSettingsButton.isGone = plugin?.openSettings == null
- }
-
when (data) {
is Resource.Success -> {
val d = data.value
@@ -854,7 +787,6 @@ class HomeFragment : BaseFragment(
homeLoadingError.isVisible = false
homeMasterRecycler.isVisible = true
homeLoadingShimmer.stopShimmer()
-
//home_loaded?.isVisible = true
if (toggleRandomButton) {
val distinct = d.values
@@ -875,16 +807,26 @@ class HomeFragment : BaseFragment(
homeRandomButtonTv.isGone = true
}
}
- //Open browser directly, without a menu.
+
is Resource.Failure -> {
homeLoadingShimmer.stopShimmer()
homeReloadConnectionerror.setOnClickListener(apiChangeClickListener)
- homeReloadConnectionOpenInBrowser.setOnClickListener {
- val currentApi = currentApiName?.let { getApiFromNameNull(it) }
- ?: homeViewModel.apiName.value?.let { getApiFromNameNull(it) }
- val mainUrl = currentApi?.mainUrl
- if (!mainUrl.isNullOrBlank()) {
- context?.openBrowser(mainUrl)
+ homeReloadConnectionOpenInBrowser.setOnClickListener { view ->
+ val validAPIs = apis//.filter { api -> api.hasMainPage }
+
+ view.popupMenuNoIconsAndNoStringRes(validAPIs.mapIndexed { index, api ->
+ Pair(
+ index,
+ api.name
+ )
+ }) {
+ try {
+ val i = Intent(Intent.ACTION_VIEW)
+ i.data = validAPIs[itemId].mainUrl.toUri()
+ startActivity(i)
+ } catch (e: Exception) {
+ logError(e)
+ }
}
}
@@ -979,29 +921,6 @@ class HomeFragment : BaseFragment(
}*/
}
- private fun updateTvRailFocus() {
- if (!isLayout(TV or EMULATOR)) return
- // home_api_holder is glued to the header row's top by the mirror logic in
- // the scroll listener above. Only target the provider button while the
- // holder is actually on screen; once the header scrolls off (y < 0) let
- // the rail fall back to default focus search so RIGHT enters content rows
- val holder = binding?.homeApiHolder
- val target =
- if (holder != null && holder.isVisible && holder.y >= 0f) R.id.home_change_api
- else View.NO_ID
- val rail = activity?.findViewById(R.id.nav_rail_view) ?: return
- rail.nextFocusRightId = target
- for (focusView in arrayOf(
- R.id.navigation_downloads,
- R.id.navigation_home,
- R.id.navigation_search,
- R.id.navigation_library,
- R.id.navigation_settings,
- )) {
- rail.findViewById(focusView)?.nextFocusRightId = target
- }
- }
-
private fun handleTvBackPress(helper: BackPressedCallbackHelper.CallbackHelper) {
// Only apply custom behavior on TV interface
if (!isLayout(TV)) {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt
index 1f721ee20..6bdd1bf49 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt
@@ -159,7 +159,7 @@ open class ParentItemAdapter(
})
//(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
- if (isLayout(PHONE or EMULATOR)) {
+ if (isLayout(PHONE)) {
homeChildMoreInfo.setOnClickListener {
moreInfoClickCallback.invoke(item)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt
index 9c5685c68..d316f28cd 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt
@@ -108,7 +108,6 @@ import com.lagradost.cloudstream3.utils.PLAYREADY_DRM_UUID
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTagToLanguageName
import com.lagradost.cloudstream3.utils.WIDEVINE_DRM_UUID
import com.lagradost.cloudstream3.utils.videoskip.VideoSkipStamp
-import com.lagradost.cloudstream4.AppSettings
import kotlinx.coroutines.delay
import okhttp3.Interceptor
import org.chromium.net.CronetEngine
@@ -1390,17 +1389,10 @@ class CS3IPlayer : IPlayer {
context.getString(if (context.isUsingMobileData()) R.string.quality_pref_mobile_data_key else R.string.quality_pref_key),
Int.MAX_VALUE
)
- val settings = AppSettings(context)
try {
hasUsedFirstRender = false
- val playWhenReady = if (settings.player.startPaused.get()) {
- false
- } else {
- isPlaying // this keep the current state of the player
- }
-
// ye this has to be a val for whatever reason
// this makes no sense
exoPlayer = buildExoPlayer(
@@ -1412,7 +1404,7 @@ class CS3IPlayer : IPlayer {
playBackSpeed,
cacheSize = cacheSize,
videoBufferMs = videoBufferMs,
- playWhenReady = playWhenReady,
+ playWhenReady = isPlaying, // this keep the current state of the player
subtitleOffset = currentSubtitleOffset,
maxVideoHeight = maxVideoHeight,
audioSources = audioSources,
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt
index f38a9ab99..20c37dcf4 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt
@@ -102,12 +102,12 @@ open class FullScreenPlayer : AbstractPlayerFragment(
private var hideControlsNames = false
protected var subtitleDelay
set(value) = try {
- player.setSubtitleOffset(value)
+ player.setSubtitleOffset(-value)
} catch (e: Exception) {
logError(e)
}
get() = try {
- player.getSubtitleOffset()
+ -player.getSubtitleOffset()
} catch (e: Exception) {
logError(e)
0L
@@ -307,7 +307,6 @@ open class FullScreenPlayer : AbstractPlayerFragment(
playerVideoTitleRez,
playerVideoInfo,
playerGoBackHolder,
- playerVideoClock,
).forEach {
it.animateY(titleMove)
}
@@ -527,7 +526,7 @@ open class FullScreenPlayer : AbstractPlayerFragment(
// Scroll to the first active subtitle
val playerPosition = player.getPosition() ?: 0
- val totalPosition = playerPosition + currentOffset
+ val totalPosition = playerPosition - currentOffset
subtitleAdapter?.updateTime(totalPosition)
subtitleAdapter?.getLatestActiveItem(totalPosition)
@@ -537,11 +536,11 @@ open class FullScreenPlayer : AbstractPlayerFragment(
val str = when {
time > 0L -> {
- txt(R.string.subtitle_offset_extra_hint_before_format, time)
+ txt(R.string.subtitle_offset_extra_hint_later_format, time)
}
time < 0L -> {
- txt(R.string.subtitle_offset_extra_hint_later_format, -time)
+ txt(R.string.subtitle_offset_extra_hint_before_format, -time)
}
else -> {
@@ -559,12 +558,12 @@ open class FullScreenPlayer : AbstractPlayerFragment(
subtitleOffsetRecyclerview.isVisible = subtitles.isNotEmpty()
noSubtitlesLoadedNotice.isVisible = subtitles.isEmpty()
- val initialSubtitlePosition = (player.getPosition() ?: 0) + currentOffset
+ val initialSubtitlePosition = (player.getPosition() ?: 0) - currentOffset
subtitleAdapter =
SubtitleOffsetItemAdapter(initialSubtitlePosition) { subtitleCue ->
val playerPosition = player.getPosition() ?: 0
subtitleOffsetInput.text = Editable.Factory.getInstance()
- ?.newEditable((subtitleCue.startTimeMs - playerPosition).toString())
+ ?.newEditable((playerPosition - subtitleCue.startTimeMs).toString())
}.apply {
submitList(subtitles)
}
@@ -773,7 +772,7 @@ open class FullScreenPlayer : AbstractPlayerFragment(
val showPlayerEpisodes = !isGone && isThereEpisodes()
playerEpisodesButtonRoot.isVisible = showPlayerEpisodes
playerEpisodesButton.isVisible = showPlayerEpisodes
- playerVideoTitleHolder.isGone = togglePlayerTitleGone || playerVideoTitle.text.isBlank()
+ playerVideoTitleHolder.isGone = togglePlayerTitleGone
playerVideoTitleRez.isGone = isGone || playerVideoTitleRez.text.isBlank()
playerEpisodeFiller.isGone = isGone
playerCenterMenu.isGone = isGone
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
index e4616c93b..91e3ff970 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
@@ -8,7 +8,6 @@ import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Bitmap
-import android.graphics.Typeface
import android.os.Build
import android.os.Bundle
import android.text.Spanned
@@ -80,7 +79,6 @@ import com.lagradost.cloudstream3.ui.player.CS3IPlayer.Companion.preferredAudioT
import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.updateForcedEncoding
import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType
import com.lagradost.cloudstream3.ui.player.source_priority.LinkSource
-import com.lagradost.cloudstream3.ui.player.source_priority.ProfileSettings
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getLinkPriority
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
@@ -101,7 +99,6 @@ import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageTagIETF
-import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragmentDialog2
import com.lagradost.cloudstream3.utils.AppContextUtils.getShortSeasonText
import com.lagradost.cloudstream3.utils.AppContextUtils.html
import com.lagradost.cloudstream3.utils.AppContextUtils.sortSubs
@@ -135,7 +132,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.io.Serializable
-import java.lang.ref.WeakReference
import java.util.Calendar
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
@@ -514,7 +510,7 @@ class GeneratorPlayer : FullScreenPlayer() {
showDownloadProgress(DownloadEvent(0, 0, 0, null))
// uiReset() // Removed due to UX
-
+
currentSelectedLink = link
// setEpisodes(viewModel.getAllMeta() ?: emptyList())
setPlayerDimen(null)
@@ -1047,7 +1043,7 @@ class GeneratorPlayer : FullScreenPlayer() {
binding.subtitleSettingsBtt.setOnClickListener {
safe {
- val subtitlesFragment = SubtitlesFragmentDialog2()
+ val subtitlesFragment = SubtitlesFragment()
subtitlesFragment.systemBarsAddPadding = true
subtitlesFragment.show(this.parentFragmentManager, "SubtitleSettings")
}
@@ -1116,29 +1112,21 @@ class GeneratorPlayer : FullScreenPlayer() {
var sourceIndex = 0
var startSource = 0
- // Filtered and sorted links
- var currentHiddenFooter: View? = null
- var filteredLinks: List = emptyList()
+ var sortedUrls = emptyList>()
fun refreshLinks(qualityProfile: Int) {
- val currentLinkUsed = currentSelectedLink
- // Always display current linkFooter
- val sortedLinks = viewModel.state.sortLinks(qualityProfile)
-
- filteredLinks = sortedLinks.filter { it.shouldUseLink || it.link == currentLinkUsed }
-
- if (sortedLinks.isEmpty()) {
+ sortedUrls = viewModel.state.sortLinks(qualityProfile)
+ if (sortedUrls.isEmpty()) {
sourceDialog.findViewById(R.id.sort_sources_holder)?.isGone =
true
} else {
- startSource = filteredLinks.indexOfFirst { it.link == currentLinkUsed }
+ startSource = sortedUrls.indexOf(currentSelectedLink)
sourceIndex = startSource
val sourcesArrayAdapter =
ArrayAdapter(ctx, R.layout.sort_bottom_single_choice)
- sourcesArrayAdapter.addAll(filteredLinks.map { displayLink ->
- val (link, uri) = displayLink.link
+ sourcesArrayAdapter.addAll(sortedUrls.map { (link, uri) ->
val name = link?.name ?: uri?.name ?: "NULL"
"$name ${Qualities.getStringByInt(link?.quality)}"
})
@@ -1154,7 +1142,7 @@ class GeneratorPlayer : FullScreenPlayer() {
}
providerList.setOnItemLongClickListener { _, _, position, _ ->
- sortedLinks.getOrNull(position)?.link?.first?.url?.let {
+ sortedUrls.getOrNull(position)?.first?.url?.let {
clipboardHelper(
txt(R.string.video_source),
it
@@ -1162,25 +1150,6 @@ class GeneratorPlayer : FullScreenPlayer() {
}
true
}
-
- val hiddenLinks = sortedLinks.size - filteredLinks.size
- providerList.removeFooterView(currentHiddenFooter)
-
- if (hiddenLinks > 0) {
- val hiddenLinksFooter: TextView = layoutInflater.inflate(
- R.layout.sort_bottom_footer_add_choice, null
- ) as TextView
-
- providerList.addFooterView(hiddenLinksFooter, null, false)
- currentHiddenFooter = hiddenLinksFooter
-
- val hiddenLinksText =
- ctx.resources.getQuantityString(R.plurals.links_hidden, hiddenLinks)
- .format(hiddenLinks)
- hiddenLinksFooter.text = hiddenLinksText
- hiddenLinksFooter.setCompoundDrawables(null, null, null, null)
- hiddenLinksFooter.setTypeface(null, Typeface.ITALIC)
- }
}
}
@@ -1394,8 +1363,8 @@ class GeneratorPlayer : FullScreenPlayer() {
}
}
if (init) {
- filteredLinks.getOrNull(sourceIndex)?.let {
- loadLink(it.link, true)
+ sortedUrls.getOrNull(sourceIndex)?.let {
+ loadLink(it, true)
}
}
sourceDialog.dismissSafe(activity)
@@ -1563,10 +1532,6 @@ class GeneratorPlayer : FullScreenPlayer() {
}
override fun playerError(exception: Throwable) {
- currentSelectedLink?.let { link ->
- viewModel.modifyState { this.addError(link) }
- }
-
val currentUrl =
currentSelectedLink?.let { it.first?.url ?: it.second?.uri?.toString() } ?: "unknown"
val headers = currentSelectedLink?.first?.headers?.toString() ?: "none"
@@ -1589,22 +1554,8 @@ class GeneratorPlayer : FullScreenPlayer() {
private fun noLinksFound() {
viewModel.forceClearCache = true
- val hiddenLinks = viewModel.state.sortLinks(currentQualityProfile).count { !it.shouldUseLink }
-
- context?.let { ctx ->
- // Display that there are hidden links to the user.
- if (hiddenLinks > 0) {
- val noLinksString = ctx.getString(R.string.no_links_found_toast)
- val hiddenString =
- ctx.resources.getQuantityString(R.plurals.links_hidden, hiddenLinks)
- .format(hiddenLinks)
- val toastText = "$noLinksString\n($hiddenString)"
- showToast(toastText, Toast.LENGTH_SHORT)
- } else {
- showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
- }
- }
+ showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
activity?.popCurrentPage()
}
@@ -1615,9 +1566,7 @@ class GeneratorPlayer : FullScreenPlayer() {
}
val links = viewModel.state.sortLinks(currentQualityProfile)
-
- val firstAvailableLink = links.firstOrNull { it.shouldUseLink }?.link
- if (firstAvailableLink == null) {
+ if (links.isEmpty()) {
noLinksFound()
return
}
@@ -1625,7 +1574,7 @@ class GeneratorPlayer : FullScreenPlayer() {
if (!isPlayerActive.compareAndSet(false, true)) {
return
}
- loadLink(firstAvailableLink, false)
+ loadLink(links.first(), false)
showPlayerMetadata()
}
@@ -1692,26 +1641,25 @@ class GeneratorPlayer : FullScreenPlayer() {
}
}
- private fun getNextLink(): DisplayLink? {
- val links = viewModel.state.sortLinks(currentQualityProfile)
- val currentIndex = links.indexOfFirst { it.link == currentSelectedLink }
- val nextPotentialLink =
- links.withIndex().firstOrNull { it.index > currentIndex && it.value.shouldUseLink }
- return nextPotentialLink?.value
- }
-
override fun hasNextMirror(): Boolean {
- return getNextLink() != null
+ val links = viewModel.state.sortLinks(currentQualityProfile)
+ return links.isNotEmpty() && links.indexOf(currentSelectedLink) + 1 < links.size
}
override fun nextMirror() {
- val nextLink = getNextLink()
- if (nextLink == null) {
+ val links = viewModel.state.sortLinks(currentQualityProfile)
+ if (links.isEmpty()) {
noLinksFound()
return
}
- loadLink(nextLink.link, true)
+ val newIndex = links.indexOf(currentSelectedLink) + 1
+ if (newIndex >= links.size) {
+ noLinksFound()
+ return
+ }
+
+ loadLink(links[newIndex], true)
}
override fun onDestroy() {
@@ -2218,7 +2166,6 @@ class GeneratorPlayer : FullScreenPlayer() {
isPlayerActive.set(false)
binding?.overlayLoadingSkipButton?.isVisible = false
binding?.playerLoadingOverlay?.isVisible = true
- viewModel.modifyState { setError(emptyList()) }
uiReset()
}
@@ -2272,14 +2219,6 @@ class GeneratorPlayer : FullScreenPlayer() {
fromTagToEnglishLanguageName(it)?.lowercase() ?: return@mapNotNull null
} ?: listOf()
}
-
- // Set up TV clock visibility
- if (isLayout(TV)) {
- val showTvClock = settingsManager.getBoolean(ctx.getString(R.string.tv_layout_clock_key), false)
- playerBinding?.playerVideoClock?.isVisible = showTvClock
- } else {
- playerBinding?.playerVideoClock?.isVisible = false
- }
}
unwrapBundle(savedInstanceState)
@@ -2358,23 +2297,19 @@ class GeneratorPlayer : FullScreenPlayer() {
}
}
- observe(viewModel.currentLinks) { (_, instance) ->
+ observe(viewModel.currentLinks) { (links, instance) ->
if (instance != viewModel.state.instance) return@observe // Outdated observe
- val sortedLinks = viewModel.state.sortLinks(currentQualityProfile)
- val usableLinks = sortedLinks.count { link -> link.shouldUseLink }
-
- val turnVisible = usableLinks > 0 && viewModel.generator?.canSkipLoading == true
+ val turnVisible = links.isNotEmpty() && viewModel.generator?.canSkipLoading == true
val wasGone = binding.overlayLoadingSkipButton.isGone
binding.overlayLoadingSkipButton.apply {
isVisible = turnVisible
-
- if (usableLinks == 0) {
+ if (links.isEmpty()) {
setText(R.string.skip_loading)
} else {
@SuppressLint("SetTextI18n")
- text = "${context.getString(R.string.skip_loading)} (${usableLinks})"
+ text = "${context.getString(R.string.skip_loading)} (${links.size})"
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt
index cb8cf8bff..e3c390d50 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt
@@ -10,8 +10,6 @@ import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.safeApiCall
-import com.lagradost.cloudstream3.ui.player.source_priority.ProfileSettings
-import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getLinkPriority
import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
@@ -42,20 +40,12 @@ data class GeneratorState(
val id: Int?,
)
-data class DisplayLink(
- val link: VideoLink,
- // If the link should be displayed and used by the player
- val shouldUseLink: Boolean,
- val priority: Int
-)
-
/** Immutable state of all current links relevant to displaying the video */
// @MustUseReturnValues
// @Immutable
data class VideoState(
val subtitles: PersistentSet = persistentSetOf(),
val links: PersistentSet = persistentSetOf(),
- val erroredLinks: PersistentSet = persistentSetOf(),
val stamps: PersistentList = persistentListOf(),
val loading: Resource = Resource.Loading(),
val generatorState: GeneratorState? = null,
@@ -66,52 +56,19 @@ data class VideoState(
*
* sortedBy is not exactly expensive, but each hasNextMirror does it again, so this alleviates unnecessary recomputation
* */
- private val sortedLinks: ConcurrentHashMap> = ConcurrentHashMap()
+ private val sortedLinks: ConcurrentHashMap> = ConcurrentHashMap()
- /**
- * The cache is guaranteed to be up to date link-wise due to the immutable links.
- * However, hideNegativeSources and hideErrorSources could be updated, which requires clearing the cache.
- */
fun clearSortedLinksCache() = sortedLinks.clear()
- private fun hasLinkErrored(link: VideoLink): Boolean {
- return erroredLinks.any { it == link }
- }
-
- private fun VideoLink.toDisplayLink(
- qualityProfile: Int,
- hideNegativeSources: Boolean,
- hideErrorSources: Boolean
- ): DisplayLink {
- val priority = getLinkPriority(qualityProfile, this.first)
- val shouldHideLink =
- (hideNegativeSources && priority < 0) || (hideErrorSources && hasLinkErrored(this))
- val displayLink = DisplayLink(this, !shouldHideLink, priority)
-
- return displayLink
- }
-
// Modifying sortedLinks is not considered a "visible" side effect, and rerunning it does not change the result
// It is by all standards, idempotent and by extension also pure as it has no "visible" side effect
/** Returns .links in the sorted order according to the qualityProfile.
* Use .links if order is not needed */
@Contract(pure = true)
- fun sortLinks(qualityProfile: Int): List {
- sortedLinks[qualityProfile]?.let {
- return it
- }
-
- val hideNegativeSources =
- QualityDataHelper.getProfileSetting(qualityProfile, ProfileSettings.HideNegativeSources)
- val hideErrorSources =
- QualityDataHelper.getProfileSetting(qualityProfile, ProfileSettings.HideErrorSources)
-
- return links.map { link ->
+ fun sortLinks(qualityProfile: Int): List {
+ return sortedLinks[qualityProfile] ?: links.sortedBy { link ->
// negative because we want to sort highest quality first
- link.toDisplayLink(qualityProfile, hideNegativeSources, hideErrorSources)
- }.sortedBy {
- // negative because we want to sort highest quality first
- -it.priority
+ -getLinkPriority(qualityProfile, link.first)
}.also { value -> sortedLinks[qualityProfile] = value }
}
@@ -156,12 +113,6 @@ data class VideoState(
@JvmName("setVideoSkipStamp")
@Contract(pure = true)
fun set(items: Collection): VideoState = copy(stamps = items.toPersistentList())
-
- @Contract(pure = true)
- fun addError(item: VideoLink): VideoState = copy(erroredLinks = erroredLinks.add(item))
-
- @Contract(pure = true)
- fun setError(items: Collection): VideoState = copy(erroredLinks = items.toPersistentSet())
}
data class VideoLive(
@@ -190,8 +141,9 @@ class PlayerGeneratorViewModel : ViewModel() {
var state = VideoState(instance = 0)
private set
- private val _currentLinks = MutableLiveData>>(null)
- val currentLinks: LiveData>> = _currentLinks
+ private val _currentLinks =
+ MutableLiveData>>>(null)
+ val currentLinks: LiveData>>> = _currentLinks
private val _currentSubtitles = MutableLiveData>>(null)
val currentSubtitles: LiveData>> = _currentSubtitles
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt
index d8d47bd10..15d25148c 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt
@@ -12,16 +12,12 @@ import com.lagradost.cloudstream3.utils.txt
import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
-import java.util.EnumMap
-import java.util.concurrent.ConcurrentHashMap
-import kotlin.also
import kotlin.math.abs
object QualityDataHelper {
private const val VIDEO_SOURCE_PRIORITY = "video_source_priority"
private const val VIDEO_PROFILE_NAME = "video_profile_name"
private const val VIDEO_QUALITY_PRIORITY = "video_quality_priority"
- const val VIDEO_PROFILE_SETTINGS = "video_profile_settings"
// Old key only supporting one type per profile
@Deprecated("Changed to support multiple types per profile")
@@ -57,21 +53,13 @@ object QualityDataHelper {
val types: Set
)
-
- // Map profile and name to priority
- val sourcePriorityCache: ConcurrentHashMap> = ConcurrentHashMap()
-
fun getSourcePriority(profile: Int, name: String?): Int {
if (name == null) return DEFAULT_SOURCE_PRIORITY
-
- return sourcePriorityCache[profile]?.get(name) ?: (getKey(
+ return getKey(
"$currentAccount/$VIDEO_SOURCE_PRIORITY/$profile",
name,
DEFAULT_SOURCE_PRIORITY
- ) ?: DEFAULT_SOURCE_PRIORITY).also {
- sourcePriorityCache.getOrPut(profile) { hashMapOf() }
- sourcePriorityCache[profile]?.set(name, it)
- }
+ ) ?: DEFAULT_SOURCE_PRIORITY
}
fun getAllSourcePriorityNames(profile: Int): List {
@@ -89,8 +77,6 @@ object QualityDataHelper {
} else {
setKey(folder, name, priority)
}
-
- sourcePriorityCache[profile]?.set(name, priority)
}
fun setProfileName(profile: Int, name: String?) {
@@ -107,17 +93,12 @@ object QualityDataHelper {
?: txt(R.string.profile_number, profile)
}
- // Map profile and quality to priority
- val qualityPriorityCache: ConcurrentHashMap> = ConcurrentHashMap()
fun getQualityPriority(profile: Int, quality: Qualities): Int {
- return qualityPriorityCache[profile]?.get(quality) ?: (getKey(
+ return getKey(
"$currentAccount/$VIDEO_QUALITY_PRIORITY/$profile",
quality.value.toString(),
quality.defaultPriority
- )?.also {
- qualityPriorityCache.getOrPut(profile) { EnumMap(Qualities::class.java) }
- qualityPriorityCache[profile]?.set(quality, it)
- }) ?: quality.defaultPriority
+ ) ?: quality.defaultPriority
}
fun setQualityPriority(profile: Int, quality: Qualities, priority: Int) {
@@ -126,24 +107,8 @@ object QualityDataHelper {
quality.value.toString(),
priority
)
- qualityPriorityCache[profile]?.set(quality, priority)
}
- fun setProfileSetting(profile: Int, setting: ProfileSettings, value: T) {
- val folder = "$currentAccount/$VIDEO_PROFILE_SETTINGS/$profile"
- // Prevent unnecessary keys
- if (value == setting.defaultValue) {
- removeKey(folder, setting.key)
- } else {
- setKey(folder, setting.key, value)
- }
- }
-
- inline fun getProfileSetting(profile: Int, setting: ProfileSettings): T {
- val folder = "$currentAccount/$VIDEO_PROFILE_SETTINGS/$profile"
- val value = getKey(folder, setting.key)
- return value ?: setting.defaultValue
- }
@Suppress("DEPRECATION")
fun getQualityProfileTypes(profile: Int): Set {
@@ -259,8 +224,3 @@ object QualityDataHelper {
return Qualities.entries.minBy { abs(it.value - target) }
}
}
-
-sealed class ProfileSettings(val key: String, val defaultValue: T) {
- object HideErrorSources : ProfileSettings("hide_error_sources", false)
- object HideNegativeSources : ProfileSettings("hide_negative_sources", false)
-}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt
index e1b8ad479..6a0f12e9a 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt
@@ -1,6 +1,5 @@
package com.lagradost.cloudstream3.ui.player.source_priority
-import android.app.Activity
import android.app.Dialog
import androidx.annotation.StyleRes
import androidx.core.view.isVisible
@@ -27,7 +26,7 @@ data class LinkSource(
class QualityProfileDialog private constructor(
- val activity: Activity,
+ val activity: FragmentActivity,
@StyleRes val themeRes: Int,
private val links: List,
private val usedProfile: Int?,
@@ -35,7 +34,7 @@ class QualityProfileDialog private constructor(
private val useProfileSelection: Boolean
) : Dialog(activity, themeRes) {
constructor(
- activity: Activity,
+ activity: FragmentActivity,
@StyleRes themeRes: Int,
links: List,
usedProfile: Int,
@@ -43,7 +42,7 @@ class QualityProfileDialog private constructor(
) : this(activity, themeRes, links, usedProfile, profileSelectionCallback, true)
constructor(
- activity: Activity,
+ activity: FragmentActivity,
@StyleRes themeRes: Int,
links: List
) : this(activity, themeRes, links, null, null, false)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt
index 604ec3149..c8ac96ebb 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt
@@ -14,7 +14,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding
class SourcePriorityDialog(
val ctx: Context,
- @StyleRes val themeRes: Int,
+ @StyleRes themeRes: Int,
val links: List,
private val profile: QualityDataHelper.QualityProfile,
/**
@@ -28,79 +28,76 @@ class SourcePriorityDialog(
PlayerSelectSourcePriorityBinding.inflate(LayoutInflater.from(ctx), null, false)
setContentView(binding.root)
fixSystemBarsPadding(binding.root)
+ val sourcesRecyclerView = binding.sortSources
+ val qualitiesRecyclerView = binding.sortQualities
+ val profileText = binding.profileTextEditable
+ val saveBtt = binding.saveBtt
+ val exitBtt = binding.closeBtt
+ val helpBtt = binding.helpBtt
- binding.apply {
- profileTextEditable.setText(
- QualityDataHelper.getProfileName(profile.id).asString(context)
- )
- profileTextEditable.hint = txt(R.string.profile_number, profile.id).asString(context)
+ profileText.setText(QualityDataHelper.getProfileName(profile.id).asString(context))
+ profileText.hint = txt(R.string.profile_number, profile.id).asString(context)
- sortSources.adapter = PriorityAdapter(
- ).apply {
- val sortedLinks = links.map { link ->
- SourcePriority(
- null,
- link.source,
- QualityDataHelper.getSourcePriority(profile.id, link.source)
- )
- }.distinctBy { it.name }.sortedBy { -it.priority }
-
- submitList(sortedLinks)
- }
-
- sortQualities.adapter = PriorityAdapter(
- ).apply {
- submitList(Qualities.entries.mapNotNull {
- SourcePriority(
- it,
- Qualities.getStringByIntFull(it.value).ifBlank { return@mapNotNull null },
- QualityDataHelper.getQualityPriority(profile.id, it)
- )
- }.sortedBy { -it.priority })
- }
-
- @Suppress("UNCHECKED_CAST") // We know the types
- saveBtt.setOnClickListener {
- val qualityAdapter = sortQualities.adapter as? PriorityAdapter
- val sourcesAdapter = sortSources.adapter as? PriorityAdapter
-
- val qualities = qualityAdapter?.immutableCurrentList ?: emptyList()
- val sources = sourcesAdapter?.immutableCurrentList ?: emptyList()
-
- qualities.forEach {
- QualityDataHelper.setQualityPriority(profile.id, it.data, it.priority)
- }
-
- sources.forEach {
- QualityDataHelper.setSourcePriority(profile.id, it.name, it.priority)
- }
-
- qualityAdapter?.submitList(qualities.sortedBy { -it.priority })
- sourcesAdapter?.submitList(sources.sortedBy { -it.priority })
-
- val savedProfileName = profileTextEditable.text.toString()
- if (savedProfileName.isBlank()) {
- QualityDataHelper.setProfileName(profile.id, null)
- } else {
- QualityDataHelper.setProfileName(profile.id, savedProfileName)
- }
- updatedCallback.invoke()
- }
-
- closeBtt.setOnClickListener {
- dismissSafe()
- }
-
- helpBtt.setOnClickListener {
- AlertDialog.Builder(context, R.style.AlertDialogCustom).apply {
- setMessage(R.string.quality_profile_help)
- }.show()
- }
-
- settingsBtt.setOnClickListener {
- SourceProfileSettingsDialog(ctx, themeRes, profile.id).show()
- }
+ sourcesRecyclerView.adapter = PriorityAdapter(
+ ).apply {
+ submitList(links.map { link ->
+ SourcePriority(
+ null,
+ link.source,
+ QualityDataHelper.getSourcePriority(profile.id, link.source)
+ )
+ }.distinctBy { it.name }.sortedBy { -it.priority })
}
+
+ qualitiesRecyclerView.adapter = PriorityAdapter(
+ ).apply {
+ submitList(Qualities.entries.mapNotNull {
+ SourcePriority(
+ it,
+ Qualities.getStringByIntFull(it.value).ifBlank { return@mapNotNull null },
+ QualityDataHelper.getQualityPriority(profile.id, it)
+ )
+ }.sortedBy { -it.priority })
+ }
+
+ @Suppress("UNCHECKED_CAST") // We know the types
+ saveBtt.setOnClickListener {
+ val qualityAdapter = qualitiesRecyclerView.adapter as? PriorityAdapter
+ val sourcesAdapter = sourcesRecyclerView.adapter as? PriorityAdapter
+
+ val qualities = qualityAdapter?.immutableCurrentList ?: emptyList()
+ val sources = sourcesAdapter?.immutableCurrentList ?: emptyList()
+
+ qualities.forEach {
+ QualityDataHelper.setQualityPriority(profile.id, it.data, it.priority)
+ }
+
+ sources.forEach {
+ QualityDataHelper.setSourcePriority(profile.id, it.name, it.priority)
+ }
+
+ qualityAdapter?.submitList(qualities.sortedBy { -it.priority })
+ sourcesAdapter?.submitList(sources.sortedBy { -it.priority })
+
+ val savedProfileName = profileText.text.toString()
+ if (savedProfileName.isBlank()) {
+ QualityDataHelper.setProfileName(profile.id, null)
+ } else {
+ QualityDataHelper.setProfileName(profile.id, savedProfileName)
+ }
+ updatedCallback.invoke()
+ }
+
+ exitBtt.setOnClickListener {
+ this.dismissSafe()
+ }
+
+ helpBtt.setOnClickListener {
+ AlertDialog.Builder(context, R.style.AlertDialogCustom).apply {
+ setMessage(R.string.quality_profile_help)
+ }.show()
+ }
+
super.show()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourceProfileSettingsDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourceProfileSettingsDialog.kt
deleted file mode 100644
index c26955b5e..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourceProfileSettingsDialog.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.lagradost.cloudstream3.ui.player.source_priority
-
-import android.app.Dialog
-import android.content.Context
-import android.view.LayoutInflater
-import androidx.annotation.StyleRes
-import com.lagradost.cloudstream3.databinding.SourceProfileSettingsDialogBinding
-import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
-import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding
-
-class SourceProfileSettingsDialog(
- val ctx: Context,
- @StyleRes themeRes: Int,
- val profile: Int
-) : Dialog(ctx, themeRes) {
- override fun show() {
- val binding =
- SourceProfileSettingsDialogBinding.inflate(LayoutInflater.from(ctx), null, false)
- setContentView(binding.root)
- fixSystemBarsPadding(binding.root)
-
- binding.apply {
- var hideErrorSources = QualityDataHelper.getProfileSetting(profile, ProfileSettings.HideErrorSources)
- var hideNegativeSources = QualityDataHelper.getProfileSetting(profile, ProfileSettings.HideNegativeSources)
-
- profileHideErrorSources.isChecked = hideErrorSources
- profileHideErrorSources.setOnCheckedChangeListener { _, bool ->
- hideErrorSources = bool
- }
-
- profileHideNegativeSources.isChecked = hideNegativeSources
- profileHideNegativeSources.setOnCheckedChangeListener { _, bool ->
- hideNegativeSources = bool
- }
-
- applyBtt.setOnClickListener {
- QualityDataHelper.setProfileSetting(profile, ProfileSettings.HideErrorSources, hideErrorSources)
- QualityDataHelper.setProfileSetting(profile, ProfileSettings.HideNegativeSources, hideNegativeSources)
- dismissSafe()
- }
-
- cancelBtt.setOnClickListener {
- dismissSafe()
- }
- }
- super.show()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt
index c316fae17..ce67763d0 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt
@@ -874,6 +874,7 @@ class ResultFragmentTv : BaseFragment(
resultMetaRating.setText(d.ratingText)
resultMetaStatus.setText(d.onGoingText)
resultMetaContentRating.setText(d.contentRatingText)
+ resultCastText.setText(d.actorsText)
resultNextAiring.setText(d.nextAiringEpisode)
resultNextAiringTime.setText(d.nextAiringDate)
resultPoster.loadImage(d.posterImage, headers = d.posterHeaders)
@@ -931,7 +932,6 @@ class ResultFragmentTv : BaseFragment(
true
)
- resultCastText.setText(if (showCast) d.actorsText else null)
resultCastItems.isGone = !showCast || d.actors.isNullOrEmpty()
(resultCastItems.adapter as? ActorAdaptor)?.submitList(if (showCast) d.actors else emptyList())
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt
index dd5b97711..5f5b064b5 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt
@@ -149,7 +149,6 @@ class SearchFragment : BaseFragment(
override fun onResume() {
super.onResume()
- searchViewModel.clearSuggestions()
afterPluginsLoadedEvent += ::reloadRepos
}
@@ -168,7 +167,6 @@ class SearchFragment : BaseFragment(
**/
fun search(query: String?) {
if (query == null) return
- searchViewModel.clearSuggestions()
// don't resume state from prev search
(binding?.searchMasterRecycler?.adapter as? BaseAdapter<*, *>)?.clearState()
context?.let { ctx ->
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/ApkUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/ApkUpdater.kt
deleted file mode 100644
index 08e279b49..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/ApkUpdater.kt
+++ /dev/null
@@ -1,220 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import android.annotation.SuppressLint
-import android.app.Activity
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.content.pm.PackageInstaller
-import android.os.Build
-import androidx.core.content.FileProvider
-import com.lagradost.cloudstream3.BuildConfig
-import com.lagradost.cloudstream3.CommonActivity
-import com.lagradost.cloudstream3.ErrorLoadingException
-import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit
-import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.mvvm.safe
-import com.lagradost.cloudstream3.receivers.PackageInstallerStatusReceiver
-import com.lagradost.cloudstream4.AppSettings
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.currentCoroutineContext
-import kotlinx.coroutines.ensureActive
-import kotlinx.coroutines.withContext
-import java.io.File
-import java.io.InputStream
-import java.io.OutputStream
-import java.security.DigestException
-import java.security.MessageDigest
-
-object ApkUpdater : AppUpdater {
- private const val APP_UPDATE_NAME = "CloudStream"
- private const val APP_UPDATE_SUFFIX = "apk"
-
- @Throws
- override suspend fun update(
- settings: AppSettings,
- url: String,
- digest: DigestPair?,
- downloadProgress: (Long, Long?) -> Unit
- ) {
- val activity = CommonActivity.activity ?: throw ErrorLoadingException("No activity found")
- clearOldFiles(activity)
-
- val request = app.get(url)
- val length = request.size
- val body = request.body
- body.use { body ->
- val length = length ?: body.contentLength()
- val readStream = body.byteStream()
-
- when (settings.updates.apkInstaller.get()) {
- 0 -> {
- packageInstallerDownloader(
- activity,
- readStream,
- length,
- digest,
- downloadProgress
- )
- }
-
- else -> {
- legacyDownloader(activity, readStream, length, digest, downloadProgress)
- }
- }
- }
- }
-
- fun clearOldFiles(activity: Activity) {
- // Delete old files
- activity.cacheDir.listFiles()?.filter {
- it.name.startsWith(APP_UPDATE_NAME) && it.extension == APP_UPDATE_SUFFIX
- }?.forEach {
- deleteFileOnExit(it)
- }
- }
-
- /** https://medium.com/@solrudev/painless-building-of-an-android-package-installer-app-d5a09b5df432 */
- @SuppressLint("RequestInstallPackagesPolicy")
- @Throws
- suspend fun packageInstallerDownloader(
- activity: Activity,
- readStream: InputStream,
- length: Long?,
- digest: DigestPair?,
- downloadProgress: (Long, Long?) -> Unit,
- ) = withContext(Dispatchers.IO) {
- var sessionId: Int? = null
- val packageInstaller = activity.packageManager.packageInstaller
- try {
- val installParams =
- PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- installParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
- }
- if (length != null) {
- installParams.setSize(length)
- }
-
- sessionId = packageInstaller.createSession(installParams)
- val session = packageInstaller.openSession(sessionId)
-
- // We do not need to buffer this because transfer has large writes
- session.openWrite(activity.packageName, 0, length ?: -1L)
- .use { writeStream ->
- transfer(writeStream, readStream, length, downloadProgress, digest)
- session.fsync(writeStream)
- }
-
- val receiverIntent = Intent(activity, PackageInstallerStatusReceiver::class.java)
- val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
- } else {
- PendingIntent.FLAG_UPDATE_CURRENT
- }
- val receiverPendingIntent =
- PendingIntent.getBroadcast(activity, 0, receiverIntent, flags)
-
- // Avoid delayed updates, and just commit instantly
- session.commit(receiverPendingIntent.intentSender)
- session.close()
- } catch (t: Throwable) {
- sessionId?.let { sessionId ->
- packageInstaller.abandonSession(sessionId)
- }
- throw t
- }
- }
-
- /**
- * Write the "readStream" to the "writeStream", while notifying the "downloadProgress" and
- * calculating the digest
- *
- * ------------------------------------------------------------------------------------------
- *
- * throws a DigestException if the Digest can be calculated (non-null + correct algorithm),
- * and is mismatched
- *
- * throws a CancellationException if canceled, but may not be "instant" if the read is blocking
- *
- * throws IOException and similar for reading
- *
- * ------------------------------------------------------------------------------------------
- *
- * In case of crashes from the digest, we ignore it as we do not want to "block" someone from
- * updating if they got a broken OS without the desired algorithm or implementation.
- *
- * This is because a malicious CDN should not be able to "crash" the algorithm, but a broken
- * OS will. And recovering from a broken OS by ignoring it is better than refusing it.
- * */
- @Throws
- suspend fun transfer(
- writeStream: OutputStream,
- readStream: InputStream,
- length: Long?,
- downloadProgress: (Long, Long?) -> Unit,
- digest: DigestPair?,
- ) = withContext(Dispatchers.IO) {
- val md = digest?.algorithm?.let { digestAlgorithm ->
- safe {
- MessageDigest.getInstance(digestAlgorithm)
- }
- }
-
- val context = currentCoroutineContext()
- val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
- var read: Int
- var transferred: Long = 0
- while ((readStream.read(buffer, 0, DEFAULT_BUFFER_SIZE)
- .also { read = it }) >= 0
- ) {
- writeStream.write(buffer, 0, read)
-
- safe {
- md?.update(buffer, 0, read)
- }
-
- transferred += read.toLong()
- downloadProgress(transferred, length)
- context.ensureActive()
- }
- writeStream.flush()
-
- val check = safe { md?.digest() }
- if (check != null && !check.contentEquals(digest?.digest)) {
- throw DigestException("Mismatched digest on transfer ${digest?.algorithm} : ${check.toHexString()} / ${digest?.digest?.toHexString()}")
- }
- }
-
- @Throws
- suspend fun legacyDownloader(
- activity: Activity,
- readStream: InputStream,
- length: Long?,
- digest: DigestPair?,
- downloadProgress: (Long, Long?) -> Unit
- ) = withContext(Dispatchers.IO) {
- val downloadedFile = File.createTempFile(APP_UPDATE_NAME, ".$APP_UPDATE_SUFFIX")
-
- // We do not need to buffer this because transfer has large writes
- downloadedFile.outputStream().use { writeStream ->
- transfer(writeStream, readStream, length, downloadProgress, digest)
- }
-
- openApk(activity, downloadedFile)
- }
-
- fun openApk(context: Context, file: File) {
- val contentUri = FileProvider.getUriForFile(
- context, BuildConfig.APPLICATION_ID + ".provider", file
- )
- val installIntent = Intent(Intent.ACTION_VIEW).apply {
- addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
- putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
- data = contentUri
- }
- context.startActivity(installIntent)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/GithubReleases.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/GithubReleases.kt
deleted file mode 100644
index 94e0ebf4a..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/GithubReleases.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import androidx.compose.runtime.Immutable
-import com.fasterxml.jackson.annotation.JsonProperty
-import com.lagradost.cloudstream3.app
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-import kotlin.jvm.Throws
-
-object GithubReleases {
- @Serializable
- private data class GithubAsset(
- @JsonProperty("name") @SerialName("name") val name: String,
- @JsonProperty("size") @SerialName("size") val size: Int, // Size in bytes
- @JsonProperty("browser_download_url") @SerialName("browser_download_url") val browserDownloadUrl: String,
- @JsonProperty("content_type") @SerialName("content_type") val contentType: String, // application/vnd.android.package-archive
- @JsonProperty("digest") @SerialName("digest") val digest: String? = null, // sha256:..., may be null
- )
-
- @Serializable
- private data class GithubRelease(
- @JsonProperty("tag_name") @SerialName("tag_name") val tagName: String, // Version code
- @JsonProperty("body") @SerialName("body") val body: String, // Description
- @JsonProperty("assets") @SerialName("assets") val assets: List,
- @JsonProperty("target_commitish") @SerialName("target_commitish") val targetCommitish: String, // Branch
- @JsonProperty("prerelease") @SerialName("prerelease") val prerelease: Boolean,
- @JsonProperty("node_id") @SerialName("node_id") val nodeId: String,
- @JsonProperty("created_at") @SerialName("created_at") val createdAt: String, // YYYY-MM-DDTHH:MM:SSZ
- )
-
- @Serializable
- private data class GithubObject(
- @JsonProperty("sha") @SerialName("sha") val sha: String, // SHA-256 hash
- //@JsonProperty("type") @SerialName("type") val type: String,
- ///@JsonProperty("url") @SerialName("url") val url: String,
- )
-
- @Serializable
- private data class GithubTag(
- //@JsonProperty("node_id") @SerialName("node_id") val nodeId: String,
- @JsonProperty("object") @SerialName("object") val githubObject: GithubObject,
- )
-
- /** GitHub file update package */
- @Immutable
- data class GithubFile(
- /** File digest, sha:xxx */
- val digest: String?,
- /** File url for download */
- val downloadUrl: String,
- /** Filename without the extension */
- val displayName: String,
- /** Changelog, aka the commit message */
- val changeLog: String,
- /** Name of the tag, aka unique release name like vX.X.X or pre-release */
- val tagName: String,
- /** Unique node id */
- val nodeId: String,
- )
-
- private val defaultHeaders = mapOf("Accept" to "application/vnd.github.v3+json")
-
- @Throws
- suspend fun getShaFromTag(
- userName: String,
- repository: String,
- tag: String,
- ): String {
- return app.get(
- url = "https://api.github.com/repos/$userName/$repository/git/ref/tags/$tag",
- headers = defaultHeaders
- ).parsed().githubObject.sha
- }
-
- @Throws
- suspend fun getLatestReleaseFile(
- userName: String,
- repository: String,
- prerelease: Boolean,
- prereleaseTag: String,
- contentType: String,
- ): GithubFile? {
- val latestReleaseUrl = if (prerelease) {
- // Find the release object
- // https://docs.github.com/en/rest/releases/releases?apiVersion=2026-03-10#get-a-release-by-tag-name
- "https://api.github.com/repos/$userName/$repository/releases/tags/$prereleaseTag"
- } else {
- // Just get the latest release
- // https://docs.github.com/en/rest/releases/releases?apiVersion=2026-03-10#get-the-latest-release
- // The latest release is the most recent non-prerelease, non-draft release, sorted by the created_at attribute
- "https://api.github.com/repos/$userName/$repository/releases/latest"
- }
-
- val latestRelease = app.get(
- url = latestReleaseUrl,
- headers = defaultHeaders
- ).parsed()
-
- // Find the first correct asset, given that we might have other binaries we release in the same version
- val foundAsset = latestRelease.assets.firstOrNull { asset ->
- asset.contentType == contentType
- }
-
- if (foundAsset == null) {
- return null
- }
-
- return GithubFile(
- digest = foundAsset.digest,
- downloadUrl = foundAsset.browserDownloadUrl,
- displayName = foundAsset.name.substringBeforeLast("."),
- changeLog = latestRelease.body,
- tagName = latestRelease.tagName,
- nodeId = latestRelease.nodeId
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/GithubViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/GithubViewModel.kt
deleted file mode 100644
index 99236ab71..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/GithubViewModel.kt
+++ /dev/null
@@ -1,299 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import androidx.compose.runtime.Immutable
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.lagradost.cloudstream3.mvvm.safe
-import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
-import com.lagradost.cloudstream4.AppSettings
-import com.lagradost.cloudstream4.compose.ActionHandler
-import com.lagradost.cloudstream4.compose.DefaultStateContainer
-import com.lagradost.cloudstream4.compose.SingleActiveQuery
-import com.lagradost.cloudstream4.compose.StateContainer
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.isActive
-import kotlinx.coroutines.launch
-import java.io.FileNotFoundException
-
-@Immutable
-data class GithubState(
- val dialog: GithubDialog? = null,
-)
-
-@Immutable
-sealed class GithubUpdateDialogState {
- data class Error(val error: Throwable) : GithubUpdateDialogState()
- data class DownloadProgress(val progress: Long, val total: Long?) : GithubUpdateDialogState()
- object Loading : GithubUpdateDialogState()
- object NoUpdateFound : GithubUpdateDialogState()
- data class UpdateFound(
- val file: GithubReleases.GithubFile,
- val newSha: String?,
- val oldSha: String?,
- ) : GithubUpdateDialogState()
-}
-
-@Immutable
-data class GithubDialog(
- val isPrerelease: Boolean,
- val isFromUser: Boolean,
- val state: GithubUpdateDialogState,
-)
-
-@Immutable
-sealed class GithubAction {
- object AutoSearchForUpdate : GithubAction()
- object SearchForUpdate : GithubAction()
- object Dismiss : GithubAction()
- data class SkipThisUpdate(val file: GithubReleases.GithubFile) : GithubAction()
- data class Update(val file: GithubReleases.GithubFile) : GithubAction()
- data class SkipUpdate(val file: GithubReleases.GithubFile) : GithubAction()
-}
-
-const val APK_USERNAME = "recloudstream"
-const val APK_REPOSITORY = "cloudstream"
-const val APK_PRERELEASE = "pre-release"
-const val APK_CONTENT_TYPE = "application/vnd.android.package-archive"
-
-interface AppUpdater {
- @Throws
- suspend fun update(
- settings: AppSettings,
- url: String,
- digest: DigestPair?,
- downloadProgress: (Long, Long?) -> Unit,
- )
-}
-
-/** The digest pair to verify that a file is correctly downloaded */
-data class DigestPair(
- val algorithm : String,
- val digest : ByteArray,
-) {
- companion object {
- // "sha256:XXXX" or "sha256-XXXX" -> "sha256", bytearray(XXXX)
- fun parse(digestPair: String?) : DigestPair? {
- if(digestPair == null) return null
-
- var split = digestPair.split(":", limit = 2)
- if(split.size != 2) {
- split = digestPair.split("-", limit = 2)
- }
- if(split.size != 2) {
- return null
- }
-
- val digestAlgorithm = split.getOrNull(0) ?: return null
- val digestByteArray = safe { split.getOrNull(1)?.hexToByteArray() } ?: return null
-
- return DigestPair(digestAlgorithm, digestByteArray)
- }
- }
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- val other = other as? DigestPair ?: return false
-
- if (algorithm != other.algorithm) return false
- if (!digest.contentEquals(other.digest)) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = algorithm.hashCode()
- result = 31 * result + digest.contentHashCode()
- return result
- }
-}
-
-/**
- * Cross-platform downloader for updates served by GitHub.
- *
- * To allow this Cross-platform behavior work we split up the UI from the Viewmodel,
- * and the Viewmodel from the installer.
- * ```
- * UI: Renders the current viewmodel state
- * Viewmodel: Searches for updates on GitHub
- * AppUpdater: Installs the update from the raw GitHub url
- *
- * UI -(onAction)-> Viewmodel -(invokes)-> AppUpdater
- * <---(state)---/ <--(callback)--/
- * ```
- * */
-class GithubViewModel(
- val remoteUserName: String,
- val remoteRepository: String,
- val remotePrereleaseTag: String,
- val remoteContentType: String,
- val versionName: String,
- val isPrerelease: Boolean,
- val isDebug: Boolean,
- val buildSha: String,
- val settings: AppSettings,
- val updater: AppUpdater,
-) : ViewModel(), StateContainer by DefaultStateContainer(GithubState()),
- ActionHandler {
- private val updateDispatcher = SingleActiveQuery(Dispatchers.IO)
-
- override fun onAction(action: GithubAction) {
- when (action) {
- GithubAction.SearchForUpdate -> {
- ioSafe {
- searchForUpdate(prerelease = isPrerelease, fromUser = true)
- }
- }
-
- GithubAction.Dismiss -> {
- viewModelScope.launch {
- updateDispatcher.cancel()
- updateState { copy(dialog = null) }
- }
- }
-
- is GithubAction.SkipThisUpdate -> {
- settings.updates.skipUpdate.set(action.file.nodeId)
- }
-
- GithubAction.AutoSearchForUpdate -> {
- if (!isDebug && settings.updates.showAppUpdates.get()) {
- ioSafe {
- searchForUpdate(prerelease = isPrerelease, fromUser = false)
- }
- }
- }
-
- is GithubAction.SkipUpdate -> {
- settings.updates.skipUpdate.set(action.file.nodeId)
- }
-
- is GithubAction.Update -> {
- ioSafe {
- installUpdate(action.file.downloadUrl, action.file.digest)
- }
- }
- }
- }
-
- /** Cancel the old update, and catch possible errors from the block and show as a new state */
- private suspend fun dispatchUpdate(block: /* @Throws */ suspend () -> Unit) {
- updateDispatcher.launch {
- try {
- block()
- } catch (t: Throwable) {
- // If it was canceled ignore it as we probably launched another update check
- if (!isActive) {
- return@launch
- }
- // Otherwise we display the error
- updateState {
- copy(dialog = dialog?.copy(state = GithubUpdateDialogState.Error(t)))
- }
- }
- }
- }
-
- private suspend fun installUpdate(url: String, digestPair: String?) = dispatchUpdate {
- updater.update(
- settings = settings,
- url = url,
- digest = DigestPair.parse(digestPair),
- ) { progress, total ->
- updateState {
- copy(
- dialog = dialog?.copy(
- state = GithubUpdateDialogState.DownloadProgress(
- progress = progress,
- total = total
- )
- )
- )
- }
- }
- updateState {
- copy(
- dialog = null
- )
- }
- }
-
- private suspend fun searchForUpdate(
- prerelease: Boolean,
- fromUser: Boolean,
- ) = dispatchUpdate {
- val baseDialog = GithubDialog(
- isPrerelease = prerelease,
- isFromUser = fromUser,
- state = GithubUpdateDialogState.Loading
- )
-
- updateState {
- copy(dialog = baseDialog)
- }
-
- // If on pre-release check if the sha matches, as we do not look at the version
- var oldSha: String? = null
- var newSha: String? = null
- if (prerelease) {
- val sha = getSha(remotePrereleaseTag)
- oldSha = buildSha.take(7)
- newSha = sha.take(7)
-
- // Only match the first 7 chars, as that is what is saved
- if (oldSha == newSha) {
- updateState {
- copy(dialog = baseDialog.copy(state = GithubUpdateDialogState.NoUpdateFound))
- }
- return@dispatchUpdate
- }
- }
-
- val release = getRelease(prerelease)
-
- // If on stable, only check that the display name matches
- if (!prerelease && release.displayName == versionName) {
- updateState {
- copy(dialog = baseDialog.copy(state = GithubUpdateDialogState.NoUpdateFound))
- }
- return@dispatchUpdate
- }
-
- // If this was automated, and we have pressed "skip this update" then check the node-id
- if (!fromUser && release.nodeId == settings.updates.skipUpdate.get()) {
- updateState {
- copy(dialog = baseDialog.copy(state = GithubUpdateDialogState.NoUpdateFound))
- }
- return@dispatchUpdate
- }
-
- updateState {
- copy(
- dialog = baseDialog.copy(
- state = GithubUpdateDialogState.UpdateFound(
- file = release,
- newSha = newSha,
- oldSha = oldSha
- )
- )
- )
- }
- }
-
- @Throws
- private suspend fun getRelease(prerelease: Boolean) =
- GithubReleases.getLatestReleaseFile(
- prerelease = prerelease,
- userName = remoteUserName,
- repository = remoteRepository,
- prereleaseTag = remotePrereleaseTag,
- contentType = remoteContentType
- ) ?: throw FileNotFoundException()
-
- @Throws
- private suspend fun getSha(tag: String) =
- GithubReleases.getShaFromTag(
- tag = tag,
- userName = remoteUserName,
- repository = remoteRepository,
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/Globals.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/Globals.kt
index 70fd2a19d..93e469a4d 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/Globals.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/Globals.kt
@@ -1,18 +1,19 @@
package com.lagradost.cloudstream3.ui.settings
+import android.app.UiModeManager
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
+import android.os.Build
import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream4.compose.DeviceLayout.Companion.isAutoTv
object Globals {
var beneneCount = 0
- const val PHONE: Int = 0b00001
- const val TV: Int = 0b00010
- const val EMULATOR: Int = 0b00100
+ const val PHONE : Int = 0b001
+ const val TV : Int = 0b010
+ const val EMULATOR : Int = 0b100
private const val INVALID = -1
private var layoutId = INVALID
@@ -21,9 +22,18 @@ object Globals {
return settingsManager.getInt(this.getString(R.string.app_layout_key), -1)
}
- fun Context.updateTv() {
- layoutId = when (getLayoutInt()) {
- -1 -> if (isAutoTv(this)) TV else PHONE
+ 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
@@ -31,10 +41,14 @@ object Globals {
}
}
+ fun Context.updateTv() {
+ layoutId = layoutIntCorrected()
+ }
+
/** Returns true if the current orientation is landscape. */
fun isLandscape(): Boolean =
isLayout(TV or EMULATOR) ||
- Resources.getSystem().configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
+ Resources.getSystem().configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
/** 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
@@ -42,7 +56,7 @@ object Globals {
*
* Valid flags are: PHONE, TV, EMULATOR
* */
- fun isLayout(flags: Int): Boolean {
+ fun isLayout(flags: Int) : Boolean {
return (layoutId and flags) != 0
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt
index 6f67ab826..75ba55674 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt
@@ -1,7 +1,6 @@
package com.lagradost.cloudstream3.ui.settings
import android.annotation.SuppressLint
-import android.app.Activity
import android.graphics.Bitmap
import android.os.Bundle
import android.os.CountDownTimer
@@ -14,6 +13,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.content.edit
import androidx.core.view.isGone
import androidx.core.view.isVisible
+import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceManager
import androidx.preference.SwitchPreference
import androidx.recyclerview.widget.RecyclerView
@@ -29,8 +29,8 @@ import com.lagradost.cloudstream3.databinding.DeviceAuthBinding
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.animeSkipApi
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.kitsuApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.kitsuApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.openSubtitlesApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.simklApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subDlApi
@@ -42,8 +42,8 @@ import com.lagradost.cloudstream3.syncproviders.SubtitleRepo
import com.lagradost.cloudstream3.syncproviders.SyncRepo
import com.lagradost.cloudstream3.ui.BasePreferenceFragmentCompat
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.PHONE
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.hideOn
@@ -76,7 +76,7 @@ class SettingsAccount : BasePreferenceFragmentCompat(), BiometricCallback {
/** Used by nginx plugin too */
@SuppressLint("StringFormatInvalid")
fun showLoginInfo(
- activity: Activity?,
+ activity: FragmentActivity?,
api: AuthRepo,
info: AuthUser?,
index: Int,
@@ -121,7 +121,7 @@ class SettingsAccount : BasePreferenceFragmentCompat(), BiometricCallback {
}
}
- private fun showAccountSwitch(activity: Activity, api: AuthRepo) {
+ private fun showAccountSwitch(activity: FragmentActivity, api: AuthRepo) {
val accounts = api.accounts
val binding: AccountSwitchBinding =
AccountSwitchBinding.inflate(activity.layoutInflater, null, false)
@@ -153,7 +153,7 @@ class SettingsAccount : BasePreferenceFragmentCompat(), BiometricCallback {
@UiThread
- fun showPin(activity: Activity, api: AuthRepo) {
+ fun showPin(activity: FragmentActivity, api: AuthRepo) {
val binding: DeviceAuthBinding =
DeviceAuthBinding.inflate(activity.layoutInflater, null, false)
@@ -273,7 +273,8 @@ class SettingsAccount : BasePreferenceFragmentCompat(), BiometricCallback {
}
- fun showAppLogin(activity: Activity, api: AuthRepo) {
+ fun showAppLogin(activity: FragmentActivity, api: AuthRepo) {
+
val binding: AddAccountInputBinding =
AddAccountInputBinding.inflate(activity.layoutInflater, null, false)
val builder =
@@ -314,7 +315,7 @@ class SettingsAccount : BasePreferenceFragmentCompat(), BiometricCallback {
binding.createAccount.setOnClickListener {
openBrowser(
api.createAccountUrl ?: return@setOnClickListener,
- activity,
+ activity
)
dialog.dismissSafe()
}
@@ -390,7 +391,7 @@ class SettingsAccount : BasePreferenceFragmentCompat(), BiometricCallback {
}
@UiThread
- fun addAccount(activity: Activity, api: AuthRepo) {
+ fun addAccount(activity: FragmentActivity, api: AuthRepo) {
try {
if (api.hasPin && !isLayout(PHONE)) {
showPin(activity, api)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount2.kt
deleted file mode 100644
index 0185370ce..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount2.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import com.lagradost.cloudstream3.utils.BaseComposeFragment
-import com.mihon.presentation.settings.SearchableSettings
-
-/** Empty glue code to connect the old navigation graph to Compose */
-class SettingsAccount2 : BaseComposeFragment(), SearchableSettings by SettingsAccountScreen
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccountScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccountScreen.kt
deleted file mode 100644
index 97890bbfa..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccountScreen.kt
+++ /dev/null
@@ -1,148 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import android.content.Context
-import androidx.activity.compose.LocalActivity
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.fragment.app.FragmentActivity
-import com.lagradost.cloudstream3.CommonActivity.activity
-import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.animeSkipApi
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.kitsuApi
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.openSubtitlesApi
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.simklApi
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subDlApi
-import com.lagradost.cloudstream3.syncproviders.PlainAuthRepo
-import com.lagradost.cloudstream3.syncproviders.SubtitleRepo
-import com.lagradost.cloudstream3.syncproviders.SyncRepo
-import com.lagradost.cloudstream3.ui.settings.SettingsAccount.Companion.addAccount
-import com.lagradost.cloudstream3.ui.settings.SettingsAccount.Companion.showLoginInfo
-import com.lagradost.cloudstream3.utils.AppContextUtils.html
-import com.lagradost.cloudstream3.utils.BackupUtils
-import com.lagradost.cloudstream3.utils.BiometricAuthenticator
-import com.lagradost.cloudstream3.utils.BiometricAuthenticator.authCallback
-import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt
-import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
-import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isAuthEnabled
-import com.lagradost.cloudstream3.utils.BiometricAuthenticator.promptInfo
-import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
-import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogText
-import com.lagradost.cloudstream4.AppSettings
-import com.lagradost.cloudstream4.compose.PHONE
-import com.lagradost.cloudstream4.compose.isLayout
-import com.lagradost.cloudstream4.rememberAppSettings
-import com.mihon.presentation.settings.Preference
-import com.mihon.presentation.settings.SearchableSettings
-import kotlinx.collections.immutable.persistentListOf
-
-object SettingsAccountScreen : SearchableSettings, BiometricAuthenticator.BiometricCallback {
- val syncApis = persistentListOf(
- SyncRepo(malApi),
- SyncRepo(kitsuApi),
- SyncRepo(aniListApi),
- SyncRepo(simklApi),
- SubtitleRepo(openSubtitlesApi),
- SubtitleRepo(subDlApi),
- PlainAuthRepo(animeSkipApi),
- )
- private fun updateAuthPreference(context: Context, enabled: Boolean) {
- val settings = AppSettings(context)
- settings.security.biometrics.set(enabled)
- }
-
- override fun onAuthenticationError() {
- val context = activity ?: return
- updateAuthPreference(context, !isAuthEnabled(context))
- }
-
- override fun onAuthenticationSuccess() {
- val context = activity ?: return
-
- if (isAuthEnabled(context)) {
- updateAuthPreference(context, true)
- BackupUtils.backup(context)
- context.showBottomDialogText(
- context.getString(R.string.biometric_setting),
- context.getString(R.string.biometric_warning).html()
- ) { onDialogDismissedEvent }
- } else {
- updateAuthPreference(context, false)
- }
- }
-
- @Composable
- override fun getTitleRes(): String = stringResource(R.string.category_account)
-
- @Composable
- override fun getPreferences(): List {
- val settings = rememberAppSettings()
- val activity = LocalActivity.current
- val context = LocalContext.current
- val hasSecurity = remember(context) {
- try {
- deviceHasPasswordPinLock(context)
- } catch (_ : Throwable) {
- // e.g preview
- false
- }
- }
-
- return persistentListOf(
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_accounts),
- preferenceItems = syncApis.map { api ->
- Preference.PreferenceItem.TextPreference(
- title = api.name,
- icon = api.icon?.let { painterResource(it) },
- onClick = {
- val activity = activity ?: return@TextPreference
- val info = api.authUser()
- val index =
- api.accounts.indexOfFirst { account -> account.user.id == info?.id }
- if (api.accounts.isNotEmpty()) {
- showLoginInfo(activity, api, info, index)
- } else {
- addAccount(activity, api)
- }
- })
- } + Preference.PreferenceItem.SwitchPreference(
- preference = settings.security.skipAccountSelection,
- title = stringResource(R.string.skip_startup_account_select_pref),
- icon = painterResource(R.drawable.ic_outline_account_circle_24)
- ),
- ), Preference.PreferenceGroup(
- enabled = hasSecurity && isLayout(PHONE),
- title = stringResource(R.string.pref_category_security),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.security.biometrics,
- title = stringResource(R.string.biometric_setting),
- subtitle = stringResource(R.string.biometric_setting_summary),
- icon = painterResource(R.drawable.ic_fingerprint),
- onValueChanged = { _ ->
- val activity =
- activity as? FragmentActivity ?: return@SwitchPreference false
-
- if (deviceHasPasswordPinLock(activity)) {
- startBiometricAuthentication(
- activity, R.string.biometric_authentication_title, false
- )
- promptInfo?.let {
- authCallback = this
- biometricPrompt?.authenticate(it)
- }
- }
-
- return@SwitchPreference true
- })
- )
- )
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment2.kt
deleted file mode 100644
index 24867b898..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment2.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import com.lagradost.cloudstream3.utils.BaseComposeFragment
-import com.lagradost.cloudstream4.compose.Screen
-
-
-/** Empty glue code to connect the old navigation graph to Compose */
-class SettingsFragment2 : BaseComposeFragment(), Screen by SettingsFragmentScreen
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragmentScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragmentScreen.kt
deleted file mode 100644
index c906b0982..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragmentScreen.kt
+++ /dev/null
@@ -1,444 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import android.annotation.SuppressLint
-import android.os.Bundle
-import androidx.compose.animation.Crossfade
-import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.foundation.text.input.TextFieldState
-import androidx.compose.foundation.text.input.rememberTextFieldState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.ProvideTextStyle
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Text
-import androidx.compose.material3.TextField
-import androidx.compose.material3.TextFieldDefaults
-import androidx.compose.material3.rememberSearchBarState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.NonRestartableComposable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
-import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.platform.LocalSoftwareKeyboardController
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.tooling.preview.PreviewLightDark
-import androidx.compose.ui.unit.dp
-import coil3.compose.AsyncImage
-import com.lagradost.cloudstream3.BuildConfig
-import com.lagradost.cloudstream3.CommonActivity.activity
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.utils.DataStoreHelper
-import com.lagradost.cloudstream3.utils.DataStoreHelper.profileImages
-import com.lagradost.cloudstream3.utils.GitInfo.currentCommitHash
-import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper
-import com.lagradost.cloudstream3.utils.UIHelper.navigate
-import com.lagradost.cloudstream3.utils.txt
-import com.lagradost.cloudstream4.compose.Screen
-import com.lagradost.cloudstream4.compose.TV
-import com.lagradost.cloudstream4.compose.circleBorder
-import com.lagradost.cloudstream4.compose.focusOutline
-import com.lagradost.cloudstream4.compose.isLayout
-import com.lagradost.cloudstream4.theme.CloudStreamPreviewTheme
-import com.mihon.material.padding
-import com.mihon.presentation.settings.SearchableSettings
-import com.mihon.presentation.settings.SettingSearchResults
-import com.mihon.presentation.settings.SettingsData
-import com.mihon.presentation.settings.widget.TextPreferenceWidget
-import kotlinx.collections.immutable.PersistentList
-import kotlinx.collections.immutable.persistentListOf
-import kotlinx.collections.immutable.toPersistentList
-import java.text.DateFormat
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
-import java.util.TimeZone
-
-object SettingsFragmentScreen : Screen {
- val screens = persistentListOf(
- SettingsNavigation(
- title = R.string.category_general,
- navigation = R.id.action_navigation_global_to_navigation_settings_general,
- screen = SettingsGeneralScreen,
- icon = R.drawable.build_24px,
- subtitle = persistentListOf(
- R.string.extension_language,
- R.string.title_downloads,
- R.string.pref_category_bypass,
- R.string.pref_category_links
- )
- ),
- SettingsNavigation(
- title = R.string.category_player,
- navigation = R.id.action_navigation_global_to_navigation_settings_player,
- screen = SettingsPlayerScreen,
- icon = R.drawable.play_arrow_24px,
- subtitle = persistentListOf(
- R.string.pref_category_subtitles,
- R.string.pref_category_player_features,
- R.string.pref_category_gestures,
- R.string.pref_category_player_layout,
- R.string.pref_category_cache
- )
- ),
- /*SettingsNavigation(
- title = R.string.category_providers,
- navigation = R.id.action_navigation_global_to_navigation_settings_providers,
- screen = SettingsProvidersScreen,
- icon = R.drawable.build_24px,
- ),*/
- SettingsNavigation(
- title = R.string.category_ui,
- navigation = R.id.action_navigation_global_to_navigation_settings_ui,
- screen = SettingsUIScreen,
- icon = R.drawable.format_paint_24px,
- subtitle = persistentListOf(
- R.string.pref_category_looks,
- R.string.pref_category_ui_features,
- R.string.search_poster_img_des,
- R.string.poster_ui_settings
- )
- ),
- SettingsNavigation(
- title = R.string.category_updates,
- navigation = R.id.action_navigation_global_to_navigation_settings_updates,
- screen = SettingsUpdatesScreen,
- icon = R.drawable.mobile_arrow_down_24px,
- subtitle = persistentListOf(
- R.string.pref_category_app_updates,
- R.string.pref_category_backup,
- R.string.pref_category_extensions,
- R.string.pref_category_actions
- )
- ),
- SettingsNavigation(
- title = R.string.category_account,
- navigation = R.id.action_navigation_global_to_navigation_settings_account,
- screen = SettingsAccountScreen,
- icon = R.drawable.encrypted_24px,
- subtitle = persistentListOf(
- R.string.pref_category_accounts,
- R.string.pref_category_security
- )
- ),
- SettingsNavigation(
- title = R.string.pref_category_extensions,
- navigation = R.id.action_navigation_global_to_navigation_settings_extensions,
- screen = null,
- icon = R.drawable.extension_24px,
- subtitle = persistentListOf(R.string.add_repository)
- ),
- )
-
- @Immutable
- data class SettingsNavigation(
- val title: Int,
- val navigation: Int,
- val screen: SearchableSettings?,
- val icon: Int,
- val subtitle: PersistentList,
- )
-
-
- @OptIn(ExperimentalMaterial3Api::class)
- @Composable
- @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
- override fun Content() {
- val textFieldState = rememberTextFieldState()
-
- val outerListState = rememberScrollState()
-
- val parentFirstScrollConnection = remember {
- object : NestedScrollConnection {
- override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
- val delta = available.y
- return if (delta < 0 && outerListState.canScrollForward) {
- val consumed = outerListState.dispatchRawDelta(-delta)
- Offset(0f, -consumed)
- } else {
- Offset.Zero
- }
- }
- }
- }
-
- Scaffold { _ ->
- Column(modifier = Modifier.verticalScroll(outerListState)) {
- Spacer(modifier = Modifier.height(MaterialTheme.padding.small))
-
- val default = DataStoreHelper.getDefaultAccount(
- LocalContext.current
- )
- val flow by DataStoreHelper.selectedAccountNumberFlow.collectAsState()
- val account = remember(flow) {
- DataStoreHelper.getCurrentAccount() ?: default
- }
-
- Row(
- modifier = Modifier
- .fillMaxSize()
- .focusOutline()
- .clickable {
- activity.navigate(
- R.id.accountSelectActivity,
- Bundle().apply { putBoolean("isFromMainActivity", true) }
- )
- }
- .padding(
- vertical = MaterialTheme.padding.large,
- horizontal = MaterialTheme.padding.medium
- )
- ) {
- val image =
- account.customImage ?: profileImages.getOrNull(account.defaultImageIndex)
- ?: profileImages.first()
-
- AsyncImage(
- contentScale = ContentScale.Crop,
- model = image,
- modifier = Modifier.circleBorder(50.dp),
- contentDescription = null,
- )
- Spacer(modifier = Modifier.width(MaterialTheme.padding.medium))
- Column {
- Text(
- text = account.name,
- style = MaterialTheme.typography.titleLarge,
- )
- Text(
- text = stringResource(R.string.title_settings),
- style = MaterialTheme.typography.bodyMedium,
- )
- }
- }
-
- Spacer(modifier = Modifier.height(MaterialTheme.padding.small))
- SettingsSearch(textFieldState = textFieldState)
- Spacer(modifier = Modifier.height(MaterialTheme.padding.small))
-
- SettingSearchResults(
- nestedScrollConnection = parentFirstScrollConnection,
- searchKey = textFieldState.text.toString(),
- deferredItems = ::generateSearchItems,
- onItemClick = { item ->
- SearchableSettings.highlightKey = item.highlightKey
- activity?.navigate(item.navigation)
- }, empty = {
- Column(
- modifier = Modifier
- .nestedScroll(parentFirstScrollConnection)
- ) {
- screens.forEach { settingsTab ->
- SettingsTab(settingsTab)
- }
- BuildStamp()
- }
- })
-
- }
- }
- }
-
- @Composable
- @NonRestartableComposable
- fun generateSearchItems() =
- screens.mapNotNull { item ->
- val contents = item.screen?.getPreferences() ?: return@mapNotNull null
- SettingsData(
- title = stringResource(item.title),
- navigation = item.navigation,
- contents = contents
- )
- }.toPersistentList()
-
- @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
- @Composable
- fun SettingsSearch(textFieldState : TextFieldState) {
- var hasFocus by remember { mutableStateOf(false) }
- val focusProgress by animateFloatAsState(targetValue = if (hasFocus) 1.0f else 0.0f)
- val focusManager = LocalFocusManager.current
- val focusRequester = remember { FocusRequester() }
- TextField(
- state = textFieldState,
- keyboardOptions = KeyboardOptions.Default.copy(
- imeAction = ImeAction.Search,
- // This option is bugged on compose, making it unable to open at all
- // showKeyboardOnFocus = false
- ),
- modifier = Modifier
- .fillMaxWidth()
- .padding(horizontal = 24.dp - 12.dp * focusProgress)
- .focusOutline(enabled = isLayout(TV), CircleShape)
- .onFocusChanged { newFocus ->
- hasFocus = newFocus.hasFocus
- }.focusRequester(focusRequester),
- placeholder = {
- Text(text = stringResource(R.string.search_hint))
- },
- shape = CircleShape,
- colors = TextFieldDefaults.colors(
- focusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
- unfocusedContainerColor = MaterialTheme.colorScheme.surfaceVariant,
- unfocusedLeadingIconColor = MaterialTheme.colorScheme.onBackground,
- unfocusedTrailingIconColor = MaterialTheme.colorScheme.onBackground,
- focusedTrailingIconColor = MaterialTheme.colorScheme.onBackground,
- focusedLeadingIconColor = MaterialTheme.colorScheme.onBackground,
- focusedIndicatorColor = Color.Transparent,
- unfocusedIndicatorColor = Color.Transparent,
- focusedPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant,
- unfocusedPlaceholderColor = MaterialTheme.colorScheme.onSurfaceVariant,
- cursorColor = MaterialTheme.colorScheme.onBackground,
- ),
- leadingIcon = {
- Crossfade(
- targetState = hasFocus,
- label = "leftsearch",
- ) { value ->
- if (value) {
- IconButton(onClick = {
- textFieldState.edit { replace(0, length, "") }
- focusManager.clearFocus()
- }) {
- Icon(
- painter = painterResource(R.drawable.keyboard_arrow_left_24px),
- contentDescription = null
- )
- }
- } else {
- IconButton(onClick = {
- focusRequester.requestFocus()
- }) {
- Icon(
- painter = painterResource(R.drawable.search_icon),
- contentDescription = null
- )
- }
- }
- }
- },
- trailingIcon = {
- Crossfade(
- targetState = hasFocus,
- label = "rightsearch",
- ) { value ->
- if (value) {
- IconButton(onClick = {
- textFieldState.edit { replace(0, length, "") }
- }) {
- Icon(
- painter = painterResource(R.drawable.close_24px),
- contentDescription = null
- )
- }
- }
- }
- },
- )
-
- val keyboardController = LocalSoftwareKeyboardController.current
- DisposableEffect(Unit) {
- onDispose {
- keyboardController?.hide()
- }
- }
- }
-
- @Composable
- fun SettingsTab(settingsTab: SettingsNavigation) {
- TextPreferenceWidget(
- title = stringResource(settingsTab.title),
- icon = painterResource(settingsTab.icon),
- // This can not be converted to joinToString due to stringResource being composable
- subtitle =
- @Suppress("SimplifiableCallChain")
- settingsTab.subtitle.map { stringResource(it) }.joinToString(),
- // Clear it if we have already set it but navigated back instantly
- onPreferenceClick = {
- SearchableSettings.highlightKey = null
- activity?.navigate(settingsTab.navigation)
- })
- }
-
- @Composable
- fun BuildStamp() {
- val (commitHash, buildTimestamp) = remember {
- val commitHash = activity?.currentCommitHash() ?: ""
- val buildTimestamp = SimpleDateFormat.getDateTimeInstance(
- DateFormat.LONG, DateFormat.MEDIUM,
- Locale.getDefault()
- ).apply {
- timeZone = TimeZone.getTimeZone("UTC")
- }.format(Date(BuildConfig.BUILD_DATE)).replace("UTC", "")
- commitHash to buildTimestamp
- }
-
- Row(
- horizontalArrangement = Arrangement.SpaceEvenly,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier
- .fillMaxWidth()
- .height(50.dp)
- .focusOutline()
- .clickable {
- clipboardHelper(
- txt(R.string.extension_version),
- "${BuildConfig.VERSION_NAME} $commitHash $buildTimestamp"
- )
- },
- ) {
- ProvideTextStyle(MaterialTheme.typography.bodyMedium) {
- Text(text = BuildConfig.VERSION_NAME)
- if (commitHash != "") {
- Text("•")
- Text(text = commitHash)
- }
- Text("•")
- Text(text = buildTimestamp)
- }
- }
- }
-}
-
-
-@PreviewLightDark
-@Composable
-fun Preview() {
- CloudStreamPreviewTheme {
- SettingsFragmentScreen.Content()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt
index 32f60752e..354424978 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt
@@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.ui.settings
import android.content.Context
import android.net.Uri
import android.os.Bundle
-import android.view.LayoutInflater
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
@@ -18,7 +17,6 @@ import com.lagradost.cloudstream3.CloudStreamApp
import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey
import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity
-import com.lagradost.cloudstream3.CommonActivity.activity
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
@@ -79,7 +77,6 @@ val appLanguages = arrayListOf(
Pair("Azərbaycan dili", "az"),
Pair("Bahasa Indonesia", "in"),
Pair("Bahasa Melayu", "ms"),
- Pair("català", "ca"),
Pair("Deutsch", "de"),
Pair("English", "en"),
Pair("Español", "es"),
@@ -100,7 +97,6 @@ val appLanguages = arrayListOf(
Pair("Português", "pt"),
Pair("Português (Brasil)", "pt-BR"),
Pair("Română", "ro"),
- Pair("Shqip мова", "sq"),
Pair("Slovenčina", "sk"),
Pair("Soomaaliga", "so"),
Pair("Svenska", "sv"),
@@ -110,7 +106,6 @@ val appLanguages = arrayListOf(
Pair("Wikang Filipino", "fil"),
Pair("Čeština", "cs"),
Pair("Ελληνικά", "el"),
- Pair("беларуская мова", "be"),
Pair("български", "bg"),
Pair("македонски", "mk"),
Pair("русский", "ru"),
@@ -130,7 +125,6 @@ val appLanguages = arrayListOf(
Pair("தமிழ்", "ta"),
Pair("ಕನ್ನಡ", "kn"),
Pair("മലയാളം", "ml"),
- Pair("ພາສາລາວ", "lo"),
Pair("ဗမာစာ", "my"),
Pair("ትግርኛ", "ti"),
Pair("አማርኛ", "am"),
@@ -178,96 +172,6 @@ class SettingsGeneral : BasePreferenceFragmentCompat() {
putString(context.getString(R.string.download_path_key_visual), visual)
}
}
-
- fun getCurrent(): MutableList {
- return getKey>(USER_PROVIDER_API)?.toMutableList()
- ?: mutableListOf()
- }
- fun showAdd() {
- val providers = allProviders.distinctBy { it::class }.sortedBy { it.name }
- val context = activity
- context?.showDialog(
- providers.map { "${it.name} (${it.mainUrl})" },
- -1,
- context.getString(R.string.add_site_pref),
- true,
- {}) { selection ->
- val provider = providers.getOrNull(selection) ?: return@showDialog
-
- val binding : AddSiteInputBinding = AddSiteInputBinding.inflate(LayoutInflater.from(
- context
- ),null,false)
-
- val builder =
- AlertDialog.Builder(context, R.style.AlertDialogCustom)
- .setView(binding.root)
-
- val dialog = builder.create()
- dialog.show()
-
- binding.text2.text = provider.name
- binding.applyBtt.setOnClickListener {
- val name = binding.siteNameInput.text?.toString()
- val url = binding.siteUrlInput.text?.toString()
- val lang = binding.siteLangInput.text?.toString()
- val realLang = if (lang.isNullOrBlank()) provider.lang else lang
- val simpleName = provider::class.simpleName
- if (url.isNullOrBlank() || name.isNullOrBlank() || simpleName == null) {
- showToast(R.string.error_invalid_data, Toast.LENGTH_SHORT)
- return@setOnClickListener
- }
-
- val current = getCurrent()
- val newSite = CustomSite(simpleName, name, url, realLang)
- current.add(newSite)
- setKey(USER_PROVIDER_API, current.toTypedArray())
- // reload apis
- MainActivity.afterPluginsLoadedEvent.invoke(false)
-
- dialog.dismissSafe(activity)
- }
- binding.cancelBtt.setOnClickListener {
- dialog.dismissSafe(activity)
- }
- }
- }
-
- fun showDelete() {
- val current = getCurrent()
- val context = activity
- context?.showMultiDialog(
- current.map { it.name },
- listOf(),
- context.getString(R.string.remove_site_pref),
- {}) { indexes ->
- current.removeAll(indexes.map { current[it] })
- setKey(USER_PROVIDER_API, current.toTypedArray())
- }
- }
-
- fun showAddOrDelete() {
- val context = activity
-
- val binding : AddRemoveSitesBinding = AddRemoveSitesBinding.inflate(
- LayoutInflater.from(
- context
- ),null,false)
- val builder =
- AlertDialog.Builder(context ?: return, R.style.AlertDialogCustom)
- .setView(binding.root)
-
- val dialog = builder.create()
- dialog.show()
-
- binding.addSite.setOnClickListener {
- showAdd()
- dialog.dismissSafe(activity)
- }
- binding.removeSite.setOnClickListener {
- showDelete()
- dialog.dismissSafe(activity)
- }
- }
}
private val pathPicker = getChooseFolderLauncher { uri, path ->
@@ -279,6 +183,10 @@ class SettingsGeneral : BasePreferenceFragmentCompat() {
setPreferencesFromResource(R.xml.settings_general, rootKey)
val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext())
+ fun getCurrent(): MutableList {
+ return getKey>(USER_PROVIDER_API)?.toMutableList()
+ ?: mutableListOf()
+ }
getPref(R.string.locale_key)?.setOnPreferenceClickListener { pref ->
val current = getCurrentLocale(pref.context)
@@ -315,7 +223,83 @@ class SettingsGeneral : BasePreferenceFragmentCompat() {
true
}
+ fun showAdd() {
+ val providers = allProviders.distinctBy { it::class }.sortedBy { it.name }
+ activity?.showDialog(
+ providers.map { "${it.name} (${it.mainUrl})" },
+ -1,
+ context?.getString(R.string.add_site_pref) ?: return,
+ true,
+ {}) { selection ->
+ val provider = providers.getOrNull(selection) ?: return@showDialog
+ val binding : AddSiteInputBinding = AddSiteInputBinding.inflate(layoutInflater,null,false)
+
+ val builder =
+ AlertDialog.Builder(context ?: return@showDialog, R.style.AlertDialogCustom)
+ .setView(binding.root)
+
+ val dialog = builder.create()
+ dialog.show()
+
+ binding.text2.text = provider.name
+ binding.applyBtt.setOnClickListener {
+ val name = binding.siteNameInput.text?.toString()
+ val url = binding.siteUrlInput.text?.toString()
+ val lang = binding.siteLangInput.text?.toString()
+ val realLang = if (lang.isNullOrBlank()) provider.lang else lang
+ val simpleName = provider::class.simpleName
+ if (url.isNullOrBlank() || name.isNullOrBlank() || simpleName == null) {
+ showToast(R.string.error_invalid_data, Toast.LENGTH_SHORT)
+ return@setOnClickListener
+ }
+
+ val current = getCurrent()
+ val newSite = CustomSite(simpleName, name, url, realLang)
+ current.add(newSite)
+ setKey(USER_PROVIDER_API, current.toTypedArray())
+ // reload apis
+ MainActivity.afterPluginsLoadedEvent.invoke(false)
+
+ dialog.dismissSafe(activity)
+ }
+ binding.cancelBtt.setOnClickListener {
+ dialog.dismissSafe(activity)
+ }
+ }
+ }
+
+ fun showDelete() {
+ val current = getCurrent()
+
+ activity?.showMultiDialog(
+ current.map { it.name },
+ listOf(),
+ context?.getString(R.string.remove_site_pref) ?: return,
+ {}) { indexes ->
+ current.removeAll(indexes.map { current[it] })
+ setKey(USER_PROVIDER_API, current.toTypedArray())
+ }
+ }
+
+ fun showAddOrDelete() {
+ val binding : AddRemoveSitesBinding = AddRemoveSitesBinding.inflate(layoutInflater,null,false)
+ val builder =
+ AlertDialog.Builder(context ?: return, R.style.AlertDialogCustom)
+ .setView(binding.root)
+
+ val dialog = builder.create()
+ dialog.show()
+
+ binding.addSite.setOnClickListener {
+ showAdd()
+ dialog.dismissSafe(activity)
+ }
+ binding.removeSite.setOnClickListener {
+ showDelete()
+ dialog.dismissSafe(activity)
+ }
+ }
getPref(R.string.override_site_key)?.setOnPreferenceClickListener { _ ->
@@ -342,7 +326,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() {
val prefValues = resources.getIntArray(R.array.dns_pref_values)
val currentDns =
- settingsManager.getInt(getString(R.string.dns_key), 0)
+ settingsManager.getInt(getString(R.string.dns_pref), 0)
activity?.showBottomDialog(
prefNames.toList(),
@@ -350,7 +334,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() {
getString(R.string.dns_pref),
true,
{}) {
- settingsManager.edit { putInt(getString(R.string.dns_key), prefValues[it]) }
+ settingsManager.edit { putInt(getString(R.string.dns_pref), prefValues[it]) }
(context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) }
}
return@setOnPreferenceClickListener true
@@ -368,7 +352,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() {
(first +
ctx.getExternalFilesDirs("").mapNotNull { it.path } +
currentDir)
- } catch (_: Exception) {
+ } catch (e: Exception) {
first
}).filterNotNull().distinct()
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral2.kt
deleted file mode 100644
index 37b1812b9..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral2.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import com.lagradost.cloudstream3.utils.BaseComposeFragment
-import com.mihon.presentation.settings.SearchableSettings
-
-/** Empty glue code to connect the old navigation graph to Compose */
-class SettingsGeneral2 : BaseComposeFragment(), SearchableSettings by SettingsGeneralScreen
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt
deleted file mode 100644
index 4c4bff7ae..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt
+++ /dev/null
@@ -1,304 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import android.content.Intent
-import android.net.Uri
-import androidx.activity.compose.rememberLauncherForActivityResult
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.integerArrayResource
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringArrayResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.tooling.preview.PreviewLightDark
-import com.lagradost.cloudstream3.APIHolder
-import com.lagradost.cloudstream3.AllLanguagesName
-import com.lagradost.cloudstream3.CloudStreamApp
-import com.lagradost.cloudstream3.CommonActivity.activity
-import com.lagradost.cloudstream3.CommonActivity.showToast
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.TvType
-import com.lagradost.cloudstream3.UnsafeSSL
-import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.insecureApp
-import com.lagradost.cloudstream3.mvvm.logError
-import com.lagradost.cloudstream3.network.initClient
-import com.lagradost.cloudstream3.ui.settings.SettingsProvidersScreen.toStringRes
-import com.lagradost.cloudstream3.utils.BatteryOptimizationChecker.isAppRestricted
-import com.lagradost.cloudstream3.utils.BatteryOptimizationChecker.showRequestIgnoreBatteryOptDialog
-import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTagToLanguageName
-import com.lagradost.cloudstream3.utils.SubtitleHelper.getNameNextToFlagEmoji
-import com.lagradost.cloudstream4.AppSettings
-import com.lagradost.cloudstream4.compose.ActionDialog
-import com.lagradost.cloudstream4.compose.PHONE
-import com.lagradost.cloudstream4.compose.isLayout
-import com.lagradost.cloudstream4.rememberAppSettings
-import com.lagradost.cloudstream4.theme.CloudStreamPreviewTheme
-import com.lagradost.safefile.SafeFile
-import com.mihon.presentation.settings.Preference
-import com.mihon.presentation.settings.SearchableSettings
-import com.mihon.presentation.settings.collectAsState
-import kotlinx.collections.immutable.persistentListOf
-import kotlinx.collections.immutable.toPersistentMap
-
-object SettingsGeneralScreen : SearchableSettings {
- @Composable
- override fun getTitleRes(): String = stringResource(R.string.category_general)
-
- @Composable
- override fun getPreferences(): List {
- val settings = rememberAppSettings()
-
- // TODO Refactor entirely to use a different file path selector ect like QuickNovel
- val selectFileSelector =
- rememberLauncherForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
- // It lies, it can be null if file manager quits.
- if (uri == null) return@rememberLauncherForActivityResult
- val context = CloudStreamApp.context ?: return@rememberLauncherForActivityResult
-
- try {
- val settings = AppSettings(context)
- // RW perms for the path
- val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-
- context.contentResolver.takePersistableUriPermission(uri, flags)
-
- val filePath = SafeFile.fromUri(context, uri)?.filePath()
- println("Selected URI path: $uri - Full path: $filePath")
-
- // store the actual URI instead of the path due to permissions.
- // filePath should only be used for cosmetic purposes.
- val visual = filePath ?: uri.toString()
- settings.general.downloadPath.set(uri.toString())
- settings.general.downloadPathVisual.set(visual)
- } catch (t: Throwable) {
- logError(t)
- }
- }
-
- val bananas by settings.general.bananas.collectAsState()
- val downloadPathVisual by settings.general.downloadPathVisual.collectAsState()
- //val downloadPath by settings.general.downloadPath.collectAsState()
-
- var isBatteryShown by remember { mutableStateOf(false) }
- val context = LocalContext.current
-
- if (isBatteryShown) {
- ActionDialog(
- icon = painterResource(R.drawable.battery_alert_24px),
- title = stringResource(R.string.battery_dialog_title),
- text = stringResource(R.string.battery_dialog_message),
- confirmText = stringResource(R.string.ok),
- dismissText = stringResource(R.string.cancel),
- dismiss = {
- isBatteryShown = false
- settings.general.batterOptimization.set(false)
- },
- confirm = {
- isBatteryShown = false
- // The og impl never modified it to true?
- // settings.general.batterOptimization.set(true)
- context.showRequestIgnoreBatteryOptDialog()
- }
- )
- }
-
- val default = AllLanguagesName to stringResource(R.string.all_languages_preference)
- val languages = APIHolder.apis.withLock {
- APIHolder.apis.map { api -> api.lang }.distinct()
- }.sortedBy { fromTagToLanguageName(it) ?: it }
-
- return persistentListOf(
- Preference.PreferenceGroup(title = stringResource(R.string.extension_language), preferenceItems = persistentListOf(
- Preference.PreferenceItem.ListPreference(
- preference = settings.general.locale,
- entries = appLanguages.associate { (name, code) -> (code to (name to code).nameNextToFlagEmoji()) },
- title = stringResource(R.string.app_language),
- icon = painterResource(R.drawable.language_korean_latin_24px),
- onValueChanged = { value ->
- settings.general.locale.set(value)
- activity?.recreate()
- return@ListPreference false
- },
- subtitleProvider = { v, e -> e[v] ?: getCurrentLocale(LocalContext.current) }
- ),
- Preference.PreferenceItem.MultiSelectListPreference(
- title = stringResource(R.string.provider_lang_settings),
- icon = painterResource(R.drawable.plugin_lang),
- entries = mapOf(default) + languages.associateWith { lang ->
- (getNameNextToFlagEmoji(
- lang
- ) ?: lang)
- },
- preference = settings.provider.extensionLanguages
- ),
- Preference.PreferenceItem.MultiSelectListPreference(
- title = stringResource(R.string.preferred_media_settings),
- icon = painterResource(R.drawable.movie_edit_24px),
- preference = settings.provider.preferredMedia,
- entries = TvType.entries.associate {
- it.ordinal.toString() to stringResource(it.toStringRes())
- }
- // Ok this looks strange af, but we do this to avoid double movie
- .toPersistentMap().remove(TvType.AnimeMovie.ordinal.toString()), onValueChanged = { diff ->
- if(diff.contains(TvType.Movie.ordinal.toString())) {
- settings.provider.preferredMedia.set(diff + TvType.AnimeMovie.ordinal.toString())
- } else {
- settings.provider.preferredMedia.set(diff - TvType.AnimeMovie.ordinal.toString())
- }
- return@MultiSelectListPreference false
- }),
- )),
-
- Preference.PreferenceGroup(
- title = stringResource(R.string.title_downloads),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.TextPreference(
- icon = painterResource(R.drawable.netflix_download),
- subtitle = downloadPathVisual,
- title = stringResource(R.string.download_path_pref),
- onClick = {
- // This is not a ListPreference because the old selection system is
- // broken af. This needs to be refactored to QuickNovels download path
- // system.
- selectFileSelector.launch(Uri.EMPTY)
- },
- ),
- Preference.PreferenceItem.SliderPreference(
- icon = painterResource(R.drawable.arrow_or_edge_24px),
- preference = settings.general.parallelDownloads,
- valueRange = 1..10,
- title = stringResource(R.string.parallel_downloads),
- subtitle = stringResource(R.string.download_parallel_settings_des),
- ),
- Preference.PreferenceItem.SliderPreference(
- icon = painterResource(R.drawable.arrow_and_edge_24px),
- preference = settings.general.concurrentConnections,
- valueRange = 1..10,
- title = stringResource(R.string.concurrent_connections),
- subtitle = stringResource(R.string.concurrent_connections_settings_des),
- ),
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.battery_dialog_title),
- icon = painterResource(R.drawable.battery_alert_24px),
- enabled = isLayout(PHONE),
- onClick = {
- if (isAppRestricted(context)) {
- isBatteryShown = true
- } else {
- showToast(R.string.app_unrestricted_toast)
- }
- }
- ),
- )
- ),
-
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_bypass),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.add_site_pref),
- subtitle = stringResource(R.string.add_site_summary),
- icon = painterResource(R.drawable.copy_all_24px),
- onClick = {
- // TODO refactor into compose
- if (SettingsGeneral.getCurrent().isEmpty()) {
- SettingsGeneral.showAdd()
- } else {
- SettingsGeneral.showAddOrDelete()
- }
- }
- ),
- Preference.PreferenceItem.ListPreference(
- title = stringResource(R.string.dns_pref),
- subtitle = stringResource(R.string.dns_pref_summary),
- icon = painterResource(R.drawable.dns_24px),
- preference = settings.general.dns,
- entries = integerArrayResource(R.array.dns_pref_values).zip(
- stringArrayResource(R.array.dns_pref)
- ).toMap(),
- onValueChanged = {
- (CloudStreamApp.context)?.let { ctx ->
- app.initClient(ctx, ignoreSSL = false)
- @OptIn(UnsafeSSL::class)
- insecureApp.initClient(ctx, ignoreSSL = true)
- }
- return@ListPreference true
- },
- ),
- Preference.PreferenceItem.SwitchPreference(
- title = stringResource(R.string.jsdelivr_proxy),
- subtitle = stringResource(R.string.jsdelivr_proxy_summary),
- icon = painterResource(R.drawable.wifi_proxy_24px),
- preference = settings.general.jsdelivrProxy
- )
- )
- ),
-
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_links),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.github),
- subtitle = "https://github.com/recloudstream/cloudstream",
- icon = painterResource(R.drawable.ic_github_logo),
- onClick = {
- CloudStreamApp.openBrowser("https://github.com/recloudstream/cloudstream")
- }
- ),
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.lightnovel),
- subtitle = "https://github.com/LagradOst/QuickNovel",
- icon = painterResource(R.drawable.quick_novel_icon),
- onClick = {
- CloudStreamApp.openBrowser("https://github.com/LagradOst/QuickNovel")
- }
- ),
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.discord),
- subtitle = "https://discord.gg/5Hus6fM",
- icon = painterResource(R.drawable.ic_baseline_discord_24),
- onClick = {
- CloudStreamApp.openBrowser("https://discord.gg/5Hus6fM")
- }
- ),
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.cs3wiki),
- subtitle = "https://cloudstream.miraheze.org/",
- icon = painterResource(R.drawable.description_24px),
- onClick = {
- CloudStreamApp.openBrowser("https://cloudstream.miraheze.org/")
- }
- ),
- )
- ),
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.benene),
- subtitle = if (bananas == 0) {
- stringResource(R.string.benene_count_text_none)
- } else {
- stringResource(R.string.benene_count_text, bananas)
- },
- onClick = {
- settings.general.bananas.set(bananas + 1)
- },
- icon = painterResource(R.drawable.benene),
- ),
- Preference.PreferenceItem.InfoPreference(title = stringResource(R.string.legal_notice_text)),
- )
- }
-}
-
-
-@PreviewLightDark
-@Composable
-private fun SettingGeneralPreview() {
- CloudStreamPreviewTheme {
- SettingsGeneralScreen.Content()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer2.kt
deleted file mode 100644
index 3c6a272d7..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer2.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import com.lagradost.cloudstream3.utils.BaseComposeFragment
-import com.mihon.presentation.settings.SearchableSettings
-
-/** Empty glue code to connect the old navigation graph to Compose */
-class SettingsPlayer2 : BaseComposeFragment(), SearchableSettings by SettingsPlayerScreen
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayerScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayerScreen.kt
deleted file mode 100644
index 176eb1202..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayerScreen.kt
+++ /dev/null
@@ -1,351 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import android.text.format.Formatter.formatShortFileSize
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
-import androidx.compose.runtime.mutableLongStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.integerArrayResource
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringArrayResource
-import androidx.compose.ui.res.stringResource
-import com.lagradost.cloudstream3.CommonActivity.activity
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.actions.VideoClickActionHolder
-import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
-import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getFolderSize
-import com.lagradost.cloudstream3.ui.subtitles.ChromecastSubtitlesFragment
-import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
-import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
-import com.lagradost.cloudstream4.compose.EMULATOR
-import com.lagradost.cloudstream4.compose.PHONE
-import com.lagradost.cloudstream4.compose.TV
-import com.lagradost.cloudstream4.compose.isLayout
-import com.lagradost.cloudstream4.rememberAppSettings
-import com.mihon.presentation.settings.Preference
-import com.mihon.presentation.settings.SearchableSettings
-import kotlinx.collections.immutable.persistentListOf
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import kotlin.reflect.jvm.jvmName
-
-object SettingsPlayerScreen : SearchableSettings {
- @Composable
- override fun getTitleRes(): String = stringResource(R.string.category_player)
-
- @Composable
- override fun getPreferences(): List {
- val settings = rememberAppSettings()
-
- val context = LocalContext.current
- val defaultPlayerName = stringResource(R.string.player_settings_play_in_app)
- val players = remember(context) {
- mapOf("" to defaultPlayerName) + VideoClickActionHolder.getPlayers()
- .associate { player ->
- player.uniqueId() to (player.name.asStringNull(context)
- ?: player::class.simpleName ?: player::class.jvmName)
- }
- }
-
- var cacheSize by remember { mutableLongStateOf(0L) }
- var cacheCleared by remember { mutableIntStateOf(0) }
- val cacheDir = LocalContext.current.cacheDir
- LaunchedEffect(cacheCleared) {
- withContext(Dispatchers.IO) {
- cacheSize = getFolderSize(cacheDir)
- }
- }
-
- return persistentListOf(
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_subtitles),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.player_subtitles_settings),
- subtitle = stringResource(R.string.player_subtitles_settings_des),
- icon = painterResource(R.drawable.subtitles_gear_24px),
- onClick = {
- SubtitlesFragment.push(activity, false)
- }),
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.chromecast_subtitles_settings),
- subtitle = stringResource(R.string.chromecast_subtitles_settings_des),
- icon = painterResource(R.drawable.cast),
- onClick = {
- ChromecastSubtitlesFragment.push(activity, false)
- }),
- ),
- ),
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_player_features),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.ListPreference(
- title = stringResource(R.string.player_pref),
- icon = painterResource(R.drawable.play_arrow_24px),
- preference = settings.player.defaultPlayer,
- entries = players,
- ),
-
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.source_priority),
- subtitle = stringResource(R.string.source_priority_help),
- icon = painterResource(R.drawable.ic_baseline_people_24),
- onClick = {
- ioSafe {
- val defaultSources = QualityProfileDialog.getAllDefaultSources()
- val activity = activity ?: return@ioSafe
- activity.runOnUiThread {
- QualityProfileDialog(
- activity,
- R.style.DialogFullscreenPlayer,
- defaultSources,
- ).show()
- }
- }
- }),
-
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.pipEnabled,
- title = stringResource(R.string.picture_in_picture),
- subtitle = stringResource(R.string.picture_in_picture_des),
- icon = painterResource(R.drawable.pip_24px),
- ),
-
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.resizeEnabled,
- title = stringResource(R.string.player_size_settings),
- subtitle = stringResource(R.string.player_size_settings_des),
- icon = painterResource(R.drawable.aspect_ratio_24px),
- ),
-
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.speedEnabled,
- title = stringResource(R.string.eigengraumode_settings),
- subtitle = stringResource(R.string.speed_setting_summary),
- icon = painterResource(R.drawable.ic_baseline_speed_24),
- ),
-
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.tiktokEnabled,
- title = stringResource(R.string.speedup_title),
- subtitle = stringResource(R.string.speedup_summary),
- icon = painterResource(R.drawable.speedup),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.autoPlayEnabled,
- title = stringResource(R.string.autoplay_next_settings),
- subtitle = stringResource(R.string.autoplay_next_settings_des),
- icon = painterResource(R.drawable.skip_next_24px),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.startPaused,
- title = stringResource(R.string.start_paused_settings),
- subtitle = stringResource(R.string.start_paused_settings_des),
- icon = painterResource(R.drawable.pause_24px),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.skipOpEnabled,
- title = stringResource(R.string.video_skip_op),
- subtitle = stringResource(R.string.enable_skip_op_from_database_des),
- icon = painterResource(R.drawable.keyboard_double_arrow_right_24px),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.autoRotateEnabled,
- title = stringResource(R.string.auto_rotate_video),
- subtitle = stringResource(R.string.auto_rotate_video_desc),
- icon = painterResource(R.drawable.screen_rotation_alt_24px),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.rotateButtonEnabled,
- title = stringResource(R.string.rotate_video),
- subtitle = stringResource(R.string.rotate_video_desc),
- icon = painterResource(R.drawable.screen_rotation),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.previewBarEnabled,
- enabled = isLayout(PHONE or EMULATOR),
- title = stringResource(R.string.preview_seekbar),
- subtitle = stringResource(R.string.preview_seekbar_desc),
- icon = painterResource(R.drawable.picture_in_picture_center_24px),
- ),
- Preference.PreferenceItem.ListPreference(
- preference = settings.player.softwareDecoding,
- title = stringResource(R.string.software_decoding),
- subtitle = stringResource(R.string.software_decoding_desc),
- icon = painterResource(R.drawable.memory_24px),
- entries = integerArrayResource(R.array.software_decoding_switch_values).zip(
- stringArrayResource(R.array.software_decoding_switch)
- ).toMap()
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.extraBrightnessEnabled,
- title = stringResource(R.string.extra_brightness_settings),
- subtitle = stringResource(R.string.extra_brightness_settings_des),
- icon = painterResource(R.drawable.light_mode_24px),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.episodeSync,
- title = stringResource(R.string.episode_sync_settings),
- subtitle = stringResource(R.string.episode_sync_settings_des),
- icon = painterResource(R.drawable.autorenew_24px)
- ),
- ),
- ),
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_gestures),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.swipeHorizontalEnabled,
- title = stringResource(R.string.swipe_to_seek_settings),
- subtitle = stringResource(R.string.swipe_to_seek_settings_des),
- icon = painterResource(R.drawable.swipe_24px),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.swipeVerticalEnabled,
- title = stringResource(R.string.swipe_to_change_settings),
- subtitle = stringResource(R.string.swipe_to_change_settings_des),
- icon = painterResource(R.drawable.swipe_vertical_24px),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.doubleTapToSeekEnabled,
- title = stringResource(R.string.double_tap_to_seek_settings),
- subtitle = stringResource(R.string.double_tap_to_seek_settings_des),
- icon = painterResource(R.drawable.touch_double_24px),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.doubleTapToPauseEnabled,
- title = stringResource(R.string.double_tap_to_pause_settings),
- subtitle = stringResource(R.string.double_tap_to_pause_settings_des),
- icon = painterResource(R.drawable.touch_double_24px),
- ),
-
- Preference.PreferenceItem.SliderPreference(
- preference = settings.player.doubleTapTime,
- title = stringResource(R.string.double_tap_to_seek_amount_settings),
- icon = painterResource(R.drawable.go_forward_30),
- valueRange = 5..60,
- //steps = 10,
- ),
- )
- ),
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_android_tv),
- enabled = isLayout(TV or EMULATOR),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.SliderPreference(
- preference = settings.player.tvSeekOnTime,
- title = stringResource(R.string.android_tv_interface_on_seek_settings),
- subtitle = stringResource(R.string.android_tv_interface_on_seek_settings_summary),
- icon = painterResource(R.drawable.go_forward_30),
- valueRange = 5..60,
- //steps = 10,
- ),
- Preference.PreferenceItem.SliderPreference(
- preference = settings.player.tvSeekOffTime,
- title = stringResource(R.string.android_tv_interface_off_seek_settings),
- subtitle = stringResource(R.string.android_tv_interface_off_seek_settings_summary),
- icon = painterResource(R.drawable.go_forward_30),
- valueRange = 5..60,
- //steps = 10,
- ),
- )
- ),
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_player_layout),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.ListPreference(
- preference = settings.player.limitPlayerTitle,
- title = stringResource(R.string.limit_title),
- icon = painterResource(R.drawable.match_word_24px),
- entries = integerArrayResource(R.array.limit_title_pref_values).zip(
- stringArrayResource(R.array.limit_title_pref_names)
- ).toMap()
- ),
- Preference.PreferenceItem.SwitchPreference(
- enabled = isLayout(PHONE or EMULATOR),
- preference = settings.player.hidePlayerControlNames,
- icon = painterResource(R.drawable.visibility_off_24px),
- title = stringResource(R.string.hide_player_control_names),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.showName,
- icon = painterResource(R.drawable.label_24px),
- title = stringResource(R.string.source_name),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.showMediaInfo,
- icon = painterResource(R.drawable.movie_info_24px),
- title = stringResource(R.string.video_info),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.player.showResolution,
- title = stringResource(R.string.resolution),
- icon = painterResource(R.drawable.high_res_24px),
- ),
-
- // Unsure if we want to have MultiSelectListPreference or boolean
- /*Preference.PreferenceItem.MultiSelectListPreference(
- preference = settings.player.showPlayerInfo,
- title = stringResource(R.string.limit_title_rez),
- icon = painterResource(R.drawable.ic_baseline_text_format_24),
- entries = persistentMapOf(
- ShowPlayerInfo.Name to stringResource(R.string.source_name),
- ShowPlayerInfo.Resolution to stringResource(R.string.resolution),
- ShowPlayerInfo.VideoInfo to stringResource(R.string.video_info),
- )
- ), */
- )
- ),
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_cache),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.ListPreference(
- preference = settings.player.bufferDiskMB,
- title = stringResource(R.string.video_buffer_disk_settings),
- subtitle = "%s\n" + stringResource(R.string.video_disk_description),
- icon = painterResource(R.drawable.hard_drive_24px),
- entries = integerArrayResource(R.array.video_buffer_size_values).zip(
- stringArrayResource(R.array.video_buffer_size_names)
- ).toMap()
- ),
-
- Preference.PreferenceItem.ListPreference(
- preference = settings.player.bufferRamMB,
- title = stringResource(R.string.video_buffer_size_settings),
- subtitle = "%s\n" + stringResource(R.string.video_ram_description),
- icon = painterResource(R.drawable.memory_alt_24px),
- entries = integerArrayResource(R.array.video_buffer_size_values).zip(
- stringArrayResource(R.array.video_buffer_size_names)
- ).toMap()
- ),
-
- Preference.PreferenceItem.ListPreference(
- preference = settings.player.bufferTimeSec,
- title = stringResource(R.string.video_buffer_length_settings),
- subtitle = "%s\n" + stringResource(R.string.video_ram_description),
- icon = painterResource(R.drawable.history_toggle_off_24px),
- entries = integerArrayResource(R.array.video_buffer_length_values).zip(
- stringArrayResource(R.array.video_buffer_length_names)
- ).toMap()
- ),
-
- Preference.PreferenceItem.TextPreference(
- icon = painterResource(R.drawable.ic_baseline_delete_outline_24),
- title = stringResource(R.string.video_buffer_clear_settings),
- subtitle = formatShortFileSize(LocalContext.current, cacheSize),
- onClick = {
- ioSafe {
- cacheDir.deleteRecursively()
- cacheCleared += 1
- }
- })
- )
- ),
-
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt
index 08b147f52..c8478a840 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt
@@ -109,8 +109,7 @@ class SettingsProviders : BasePreferenceFragmentCompat() {
return@setOnPreferenceClickListener true
}
- getPref(R.string.
- provider_lang_key)?.setOnPreferenceClickListener {
+ getPref(R.string.provider_lang_key)?.setOnPreferenceClickListener {
activity?.getApiProviderLangSettings()?.let { currentLangTags ->
val languagesTagName = APIHolder.apis.withLock {
listOf(Pair(AllLanguagesName, getString(R.string.all_languages_preference))) +
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders2.kt
deleted file mode 100644
index a0faeca93..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders2.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-import com.lagradost.cloudstream3.utils.BaseComposeFragment
-import com.mihon.presentation.settings.SearchableSettings
-
-/** Empty glue code to connect the old navigation graph to Compose */
-@Deprecated("This setting was merged into other places")
-class SettingsProviders2 : BaseComposeFragment(), SearchableSettings by SettingsProvidersScreen
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProvidersScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProvidersScreen.kt
deleted file mode 100644
index 4186994c7..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProvidersScreen.kt
+++ /dev/null
@@ -1,89 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import com.lagradost.cloudstream3.APIHolder
-import com.lagradost.cloudstream3.AllLanguagesName
-import com.lagradost.cloudstream3.CommonActivity.activity
-import com.lagradost.cloudstream3.DubStatus
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.TvType
-import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTagToLanguageName
-import com.lagradost.cloudstream3.utils.SubtitleHelper.getNameNextToFlagEmoji
-import com.lagradost.cloudstream3.utils.UIHelper.navigate
-import com.lagradost.cloudstream4.rememberAppSettings
-import com.mihon.presentation.settings.Preference
-import com.mihon.presentation.settings.SearchableSettings
-import kotlinx.collections.immutable.persistentListOf
-
-@Deprecated("This setting was merged into other places")
-object SettingsProvidersScreen : SearchableSettings {
- @Composable
- override fun getTitleRes(): String = stringResource(R.string.category_providers)
-
- fun TvType.toStringRes() = when (this) {
- TvType.TvSeries -> R.string.tv_series_singular
- TvType.Anime -> R.string.anime_singular
- TvType.OVA -> R.string.ova_singular
- TvType.AnimeMovie -> R.string.movies_singular
- TvType.Cartoon -> R.string.cartoons_singular
- TvType.Documentary -> R.string.documentaries_singular
- TvType.Movie -> R.string.movies_singular
- TvType.Torrent -> R.string.torrent_singular
- TvType.AsianDrama -> R.string.asian_drama_singular
- TvType.Live -> R.string.live_singular
- TvType.Others -> R.string.other_singular
- TvType.NSFW -> R.string.nsfw_singular
- TvType.Music -> R.string.music_singular
- TvType.AudioBook -> R.string.audio_book_singular
- TvType.CustomMedia -> R.string.custom_media_singular
- TvType.Audio -> R.string.audio_singular
- TvType.Podcast -> R.string.podcast_singular
- TvType.Video -> R.string.video_singular
- }
-
- @Composable
- override fun getPreferences(): List {
- val settings = rememberAppSettings()
-
- val default = AllLanguagesName to stringResource(R.string.all_languages_preference)
- val languages = APIHolder.apis.withLock {
- APIHolder.apis.map { api -> api.lang }.distinct()
- }.sortedBy { fromTagToLanguageName(it) ?: it }
-
- return persistentListOf(
- Preference.PreferenceItem.MultiSelectListPreference(
- title = stringResource(R.string.provider_lang_settings),
- icon = painterResource(R.drawable.plugin_lang),
- entries = mapOf(default) + languages.associateWith { lang ->
- (getNameNextToFlagEmoji(
- lang
- ) ?: lang)
- },
- preference = settings.provider.extensionLanguages
- ), Preference.PreferenceItem.MultiSelectListPreference(
- title = stringResource(R.string.preferred_media_settings),
- icon = painterResource(R.drawable.movie_edit_24px),
- preference = settings.provider.preferredMedia,
- entries = TvType.entries.associate {
- it.ordinal.toString() to stringResource(it.toStringRes())
- }),
- Preference.PreferenceItem.MultiSelectListPreference(
- title = stringResource(R.string.display_subbed_dubbed_settings),
- icon = painterResource(R.drawable.audio_capture_24px),
- preference = settings.provider.displayDubSub,
- entries = mapOf(
- DubStatus.None.name to stringResource(R.string.none),
- DubStatus.Dubbed.name to stringResource(R.string.app_dubbed_text),
- DubStatus.Subbed.name to stringResource(R.string.app_subbed_text),
- )
- ), Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.test_extensions),
- subtitle = stringResource(R.string.test_extensions_summary),
- icon = painterResource(R.drawable.baseline_network_ping_24),
- onClick = {
- activity?.navigate(R.id.navigation_test_providers)
- }))
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt
index 5f86a3a5a..f4c522bf9 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt
@@ -228,8 +228,6 @@ class SettingsUI : BasePreferenceFragmentCompat() {
return@setOnPreferenceClickListener true
}
- getPref(R.string.tv_layout_clock_key)?.hideOn(PHONE or EMULATOR)
-
getPref(R.string.confirm_exit_key)?.setOnPreferenceClickListener {
val prefNames = resources.getStringArray(R.array.confirm_exit)
val prefValues = resources.getIntArray(R.array.confirm_exit_values)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI2.kt
deleted file mode 100644
index 86f3371fa..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI2.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import com.lagradost.cloudstream3.utils.BaseComposeFragment
-import com.mihon.presentation.settings.SearchableSettings
-
-/** Empty glue code to connect the old navigation graph to Compose */
-class SettingsUI2 : BaseComposeFragment(), SearchableSettings by SettingsUIScreen
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt
deleted file mode 100644
index 1092a9be6..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt
+++ /dev/null
@@ -1,303 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import android.os.Build
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.integerArrayResource
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringArrayResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import com.lagradost.cloudstream3.CommonActivity.activity
-import com.lagradost.cloudstream3.DubStatus
-import com.lagradost.cloudstream3.MainActivity
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.SearchQuality
-import com.lagradost.cloudstream3.mvvm.safe
-import com.lagradost.cloudstream3.ui.clear
-import com.lagradost.cloudstream3.ui.home.HomeChildItemAdapter
-import com.lagradost.cloudstream3.ui.home.ParentItemAdapter
-import com.lagradost.cloudstream3.ui.search.SearchAdapter
-import com.lagradost.cloudstream3.ui.settings.Globals.updateTv
-import com.lagradost.cloudstream3.utils.UIHelper.toPx
-import com.lagradost.cloudstream4.compose.TV
-import com.lagradost.cloudstream4.compose.isLayout
-import com.lagradost.cloudstream4.rememberAppSettings
-import com.lagradost.cloudstream4.theme.CloudStreamPrimaryColor
-import com.lagradost.cloudstream4.theme.modeToTheme
-import com.lagradost.cloudstream4.theme.perfToColor
-import com.lagradost.cloudstream4.theme.perfToMode
-import com.mihon.presentation.settings.Preference
-import com.mihon.presentation.settings.SearchableSettings
-import kotlinx.collections.immutable.mutate
-import kotlinx.collections.immutable.persistentListOf
-import kotlinx.collections.immutable.toPersistentMap
-
-object SettingsUIScreen : SearchableSettings {
- @Composable
- override fun getTitleRes(): String = stringResource(R.string.category_ui)
-
- fun SearchQuality.toStringRes() = when (this) {
- SearchQuality.BlueRay -> R.string.quality_blueray
- SearchQuality.Cam -> R.string.quality_cam
- SearchQuality.CamRip -> R.string.quality_cam_rip
- SearchQuality.DVD -> R.string.quality_dvd
- SearchQuality.HD -> R.string.quality_hd
- SearchQuality.HQ -> R.string.quality_hq
- SearchQuality.HdCam -> R.string.quality_cam_hd
- SearchQuality.Telecine -> R.string.quality_tc
- SearchQuality.Telesync -> R.string.quality_ts
- SearchQuality.WorkPrint -> R.string.quality_workprint
- SearchQuality.SD -> R.string.quality_sd
- SearchQuality.FourK -> R.string.quality_4k
- SearchQuality.UHD -> R.string.quality_uhd
- SearchQuality.SDR -> R.string.quality_sdr
- SearchQuality.HDR -> R.string.quality_hdr
- SearchQuality.WebRip -> R.string.quality_webrip
- }
-
- @Composable
- fun RoundColor(color: Color) {
- Box(
- modifier = Modifier
- .padding(start = 15.dp)
- .size(20.dp)
- .border(width = 1.5.dp, shape = CircleShape, color = MaterialTheme.colorScheme.onBackground)
- .background(color, CircleShape)
- )
- }
-
- @Composable
- override fun getPreferences(): List {
- val settings = rememberAppSettings()
-
- return persistentListOf(
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_looks),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.ListPreference(
- preference = settings.ui.primaryColor,
- icon = painterResource(R.drawable.colors_24px),
- title = stringResource(R.string.primary_color_settings),
- entries = stringArrayResource(R.array.themes_overlay_names_values).zip(
- stringArrayResource(R.array.themes_overlay_names)
- ).toMap().toPersistentMap().mutate { map ->
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // remove monet on android 11 and less
- map.remove("Monet")
- map.remove("Monet2")
- }
- },
- iconProvider = { k, _ ->
- val color = perfToColor(k)
- RoundColor(color.color)
- },
- onValueChanged = { newValue ->
- settings.ui.primaryColor.set(newValue) // We need to set before we recreate
- safe {
- activity?.recreate()
- }
- return@ListPreference false
- }),
- Preference.PreferenceItem.ListPreference(
- preference = settings.ui.theme,
- icon = painterResource(R.drawable.palette_24px),
- title = stringResource(R.string.app_theme_settings),
- entries = stringArrayResource(R.array.themes_names_values).zip(
- stringArrayResource(R.array.themes_names)
- ).toMap().toPersistentMap().mutate { map ->
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // remove monet on android 11 and less
- map.remove("Monet")
- }
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { // Remove system on android 9 and less
- map.remove("System")
- }
- },
- iconProvider = { k, _ ->
- val theme = modeToTheme(perfToMode(k), CloudStreamPrimaryColor.NORMAL)
- RoundColor(theme.background)
- },
- onValueChanged = { newValue ->
- settings.ui.theme.set(newValue) // We need to set before we recreate
- safe {
- activity?.recreate()
- }
- return@ListPreference false
- }),
- Preference.PreferenceItem.ListPreference(
- preference = settings.ui.layout,
- icon = painterResource(R.drawable.responsive_layout_24px),
- title = stringResource(R.string.app_layout),
- entries = integerArrayResource(R.array.app_layout_values).zip(
- stringArrayResource(R.array.app_layout)
- ).toMap().toPersistentMap(),
- onValueChanged = { newValue ->
- settings.ui.layout.set(newValue) // We need to set before we recreate
- safe {
- activity?.updateTv()
- activity?.recreate()
- }
- return@ListPreference false
- }),
- )
- ),
-
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_ui_features),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.SliderPreference(
- preference = settings.ui.overscanDp,
- title = stringResource(R.string.overscan_settings),
- subtitle = stringResource(R.string.overscan_settings_des),
- valueRange = 0..100,
- icon = painterResource(R.drawable.arrows_input_24px),
- enabled = isLayout(TV),
- onValueChanged = { newValue ->
- settings.ui.overscanDp.set(newValue)
- val padding = newValue.toPx
- (activity as? MainActivity)?.binding?.homeRoot?.setPadding(
- padding, padding, padding, padding
- )
- return@SliderPreference false
- }),
-
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.advancedSearch,
- title = stringResource(R.string.advanced_search),
- subtitle = stringResource(R.string.advanced_search_des),
- icon = painterResource(R.drawable.search_icon)
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.searchSuggestions,
- title = stringResource(R.string.search_suggestions),
- subtitle = stringResource(R.string.search_suggestions_des),
- icon = painterResource(R.drawable.tooltip_24px)
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.trailersEnabled,
- title = stringResource(R.string.show_trailers_settings),
- icon = painterResource(R.drawable.baseline_theaters_24)
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.kitsuPostersEnabled,
- title = stringResource(R.string.kitsu_settings),
- icon = painterResource(R.drawable.kitsu_icon)
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.castEnabled,
- title = stringResource(R.string.show_cast_in_details),
- icon = painterResource(R.drawable.face_24px)
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.fillersEnabled,
- title = stringResource(R.string.show_fillers_settings),
- icon = painterResource(R.drawable.skip_next_24px)
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.showMetadataOverlay,
- title = stringResource(R.string.show_player_metadata_overlay),
- icon = painterResource(R.drawable.metadata_overlay_icon)
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.showClock,
- title = stringResource(R.string.tv_layout_clock_settings),
- subtitle = stringResource(R.string.tv_layout_clock_settings_des),
- icon = painterResource(R.drawable.ic_baseline_clock_24),
- enabled = isLayout(TV),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.randomButtonEnabled,
- title = stringResource(R.string.random_button_settings),
- subtitle = stringResource(R.string.random_button_settings_desc),
- icon = painterResource(R.drawable.shuffle_24px)
- ),
- Preference.PreferenceItem.ListPreference(
- preference = settings.ui.confirmExit,
- title = stringResource(R.string.confirm_before_exiting_title),
- subtitle = stringResource(R.string.confirm_before_exiting_desc),
- icon = painterResource(R.drawable.ic_baseline_exit_24),
- entries = integerArrayResource(R.array.confirm_exit_values).zip(
- stringArrayResource(R.array.confirm_exit)
- ).toMap()
- ),
- )
- ),
-
- Preference.PreferenceGroup(
- title = stringResource(R.string.search_poster_img_des),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.MultiSelectListPreference(
- preference = settings.ui.filterQuality,
- title = stringResource(R.string.pref_filter_search_quality),
- icon = painterResource(R.drawable.filter_alt_24px),
- entries = SearchQuality.entries.associateWith { stringResource(it.toStringRes()) }),
- Preference.PreferenceItem.MultiSelectListPreference(
- title = stringResource(R.string.display_subbed_dubbed_settings),
- icon = painterResource(R.drawable.audio_capture_24px),
- preference = settings.provider.displayDubSub,
- entries = mapOf(
- DubStatus.None.name to stringResource(R.string.none),
- DubStatus.Dubbed.name to stringResource(R.string.app_dubbed_text),
- DubStatus.Subbed.name to stringResource(R.string.app_subbed_text),
- )),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.bottomTitle,
- title = stringResource(R.string.bottom_title_settings),
- subtitle = stringResource(R.string.bottom_title_settings_des),
- icon = painterResource(R.drawable.title_24px)
- ),
- Preference.PreferenceItem.SliderPreference(
- preference = settings.ui.posterSize,
- title = stringResource(R.string.poster_size_settings),
- subtitle = stringResource(R.string.poster_size_settings_des),
- valueRange = 0..15,
- icon = painterResource(R.drawable.baseline_grid_view_24),
- onValueChanged = { newValue ->
- HomeChildItemAdapter.sharedPool.clear()
- ParentItemAdapter.sharedPool.clear()
- SearchAdapter.sharedPool.clear()
- activity?.let { HomeChildItemAdapter.updatePosterSize(it, newValue) }
- settings.ui.posterSize.set(newValue)
- return@SliderPreference false
- }),
- ),
- ),
- Preference.PreferenceGroup(
- title = stringResource(R.string.poster_ui_settings),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.posterShowRating,
- title = stringResource(R.string.show_rating),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.posterShowEpisode,
- title = stringResource(R.string.show_episode_text),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.posterShowTitle,
- title = stringResource(R.string.show_title),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.posterShowHd,
- title = stringResource(R.string.show_hd),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.posterShowDub,
- title = stringResource(R.string.show_dub),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = settings.ui.posterShowSub,
- title = stringResource(R.string.show_sub),
- ),
- )
- )
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates2.kt
deleted file mode 100644
index 50070a384..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates2.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import com.lagradost.cloudstream3.utils.BaseComposeFragment
-import com.mihon.presentation.settings.SearchableSettings
-
-/** Empty glue code to connect the old navigation graph to Compose */
-class SettingsUpdates2 : BaseComposeFragment(), SearchableSettings by SettingsUpdatesScreen
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdatesScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdatesScreen.kt
deleted file mode 100644
index 091a645d6..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdatesScreen.kt
+++ /dev/null
@@ -1,230 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings
-
-import android.content.Intent
-import android.net.Uri
-import androidx.activity.compose.rememberLauncherForActivityResult
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.res.integerArrayResource
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringArrayResource
-import androidx.compose.ui.res.stringResource
-import com.lagradost.cloudstream3.AutoDownloadMode
-import com.lagradost.cloudstream3.BuildConfig
-import com.lagradost.cloudstream3.CloudStreamApp
-import com.lagradost.cloudstream3.CommonActivity.activity
-import com.lagradost.cloudstream3.MainActivityScreen
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.mvvm.logError
-import com.lagradost.cloudstream3.plugins.PluginManager
-import com.lagradost.cloudstream3.utils.BackupUtils
-import com.lagradost.cloudstream3.utils.BackupUtils.restorePrompt
-import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
-import com.lagradost.cloudstream3.utils.InAppUpdater.installPreReleaseIfNeeded
-import com.lagradost.cloudstream3.utils.UIHelper.navigate
-import com.lagradost.cloudstream4.AppSettings
-import com.lagradost.cloudstream4.rememberAppSettings
-import com.lagradost.safefile.SafeFile
-import com.mihon.presentation.settings.Preference
-import com.mihon.presentation.settings.SearchableSettings
-import com.mihon.presentation.settings.collectAsState
-import kotlinx.collections.immutable.persistentListOf
-
-object SettingsUpdatesScreen : SearchableSettings {
- @Composable
- override fun getTitleRes(): String = stringResource(R.string.category_updates)
-
- @Composable
- override fun getPreferences(): List {
- val settings = rememberAppSettings()
-
- // TODO Refactor entirely to use a different file path selector ect like QuickNovel
- val selectFileSelector =
- rememberLauncherForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
- // It lies, it can be null if file manager quits.
- if (uri == null) return@rememberLauncherForActivityResult
- val context = CloudStreamApp.context ?: return@rememberLauncherForActivityResult
-
- try {
- val settings = AppSettings(context)
- // RW perms for the path
- val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-
- context.contentResolver.takePersistableUriPermission(uri, flags)
-
- val filePath = SafeFile.fromUri(context, uri)?.filePath()
- println("Selected URI path: $uri - Full path: $filePath")
-
- // store the actual URI instead of the path due to permissions.
- // filePath should only be used for cosmetic purposes.
- val visual = filePath ?: uri.toString()
- settings.backup.path.set(uri.toString())
- settings.backup.visualPath.set(visual)
- } catch (t: Throwable) {
- logError(t)
- }
- }
-
- val visualBackupPath by settings.backup.visualPath.collectAsState()
- var showDialog by remember { mutableStateOf(false) }
-
- if (showDialog) {
- com.lagradost.cloudstream3.ui.settings.logcat.LogcatDialog {
- showDialog = false
- }
- }
-
- val githubViewModel = MainActivityScreen.githubViewModel()
-
- return persistentListOf(
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_app_updates),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.check_for_update),
- subtitle = BuildConfig.VERSION_NAME,
- icon = painterResource(R.drawable.mobile_arrow_down_24px),
- onClick = {
- githubViewModel?.onAction(GithubAction.SearchForUpdate)
- /*ioSafe {
- if (activity?.runAutoUpdate(false) == false) {
- activity?.runOnUiThread {
- showToast(
- R.string.no_update_found,
- Toast.LENGTH_SHORT
- )
- }
- }
- }*/
- }
- ),
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.install_prerelease),
- icon = painterResource(R.drawable.mobile_code_24px),
- enabled = BuildConfig.FLAVOR == "stable",
- onClick = {
- activity?.installPreReleaseIfNeeded()
- }
- ),
-
- Preference.PreferenceItem.ListPreference(
- title = stringResource(R.string.apk_installer_settings),
- subtitle = stringResource(R.string.apk_installer_settings_des),
- icon = painterResource(R.drawable.mobile_wrench_24px),
- entries = integerArrayResource(R.array.apk_installer_values).zip(
- stringArrayResource(R.array.apk_installer_pref)
- ).toMap(),
- preference = settings.updates.apkInstaller
- ),
-
- Preference.PreferenceItem.SwitchPreference(
- title = stringResource(R.string.updates_settings),
- subtitle = stringResource(R.string.updates_settings_des),
- icon = painterResource(R.drawable.notifications_active_24px),
- preference = settings.updates.showAppUpdates
- )
- )
- ),
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_backup),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.backup_settings),
- icon = painterResource(R.drawable.save_as_24px),
- onClick = {
- BackupUtils.backup(activity)
- }
- ),
- Preference.PreferenceItem.ListPreference(
- title = stringResource(R.string.backup_frequency),
- icon = painterResource(R.drawable.save_clock_24px),
- entries = integerArrayResource(R.array.periodic_work_values).zip(
- stringArrayResource(R.array.periodic_work_names)
- ).toMap(),
- preference = settings.backup.frequency
- ),
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.backup_path_title),
- icon = painterResource(R.drawable.folder_24px),
- subtitle = visualBackupPath,
- onClick = {
- // This is not a ListPreference because the old selection system is
- // broken af. This needs to be refactored to QuickNovels download path
- // system.
- selectFileSelector.launch(Uri.EMPTY)
- }
- ),
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.restore_settings),
- icon = painterResource(R.drawable.restore_page_24px),
- onClick = {
- activity?.restorePrompt()
- }
- ),
- )
- ),
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_extensions),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.SwitchPreference(
- title = stringResource(R.string.automatic_plugin_updates),
- icon = painterResource(R.drawable.extension_24px),
- preference = settings.plugins.autoUpdate,
- ),
-
- Preference.PreferenceItem.ListPreference(
- title = stringResource(R.string.automatic_plugin_download),
- subtitle = "%s\n" + stringResource(R.string.automatic_plugin_download_summary),
- icon = painterResource(R.drawable.extention_renew2),
- entries = AutoDownloadMode.entries.map { it.value }.sorted()
- .zip(stringArrayResource((R.array.auto_download_plugin))).toMap(),
- preference = settings.plugins.autoDownload
- ),
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.update_plugins),
- subtitle = stringResource(R.string.update_plugins_manually),
- icon = painterResource(R.drawable.extention_download),
- onClick = {
- ioSafe {
- PluginManager.___DO_NOT_CALL_FROM_A_PLUGIN_manuallyReloadAndUpdatePlugins(
- activity ?: return@ioSafe
- )
- }
- }
- ),
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.test_extensions),
- subtitle = stringResource(R.string.test_extensions_summary),
- icon = painterResource(R.drawable.baseline_network_ping_24),
- onClick = {
- activity?.navigate(R.id.navigation_test_providers)
- })
- )
- ),
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_actions),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.show_log_cat),
- icon = painterResource(R.drawable.article_24px),
- onClick = {
- showDialog = true
- }
- ),
- Preference.PreferenceItem.TextPreference(
- title = stringResource(R.string.redo_setup_process),
- icon = painterResource(R.drawable.construction_24px),
- onClick = {
- activity?.navigate(R.id.navigation_setup_language)
- }
- ),
- )
- )
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt
deleted file mode 100644
index 7d5ce2313..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatDialog.kt
+++ /dev/null
@@ -1,282 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings.logcat
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.IntrinsicSize
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.LinearProgressIndicator
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory.component1
-import androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory.component2
-import androidx.compose.ui.focus.focusProperties
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.compose.ui.window.DialogProperties
-import com.lagradost.cloudstream3.CommonActivity.showToast
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.mvvm.logError
-import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper
-import com.lagradost.cloudstream3.utils.downloader.VideoDownloadManager
-import com.lagradost.cloudstream3.utils.txt
-import com.lagradost.cloudstream4.compose.BlackButton
-import com.lagradost.cloudstream4.compose.WhiteButton
-import com.lagradost.cloudstream4.compose.circle
-import com.lagradost.cloudstream4.compose.rounded
-import kotlinx.collections.immutable.persistentListOf
-import kotlinx.collections.immutable.toPersistentList
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-import java.io.OutputStream
-import java.lang.System.currentTimeMillis
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
-
-
-@Composable
-fun LogcatDialog(dismiss: () -> Unit) {
- val list = remember { mutableStateOf(persistentListOf()) }
- var isLoading by remember { mutableStateOf(true) }
- LaunchedEffect(dismiss) {
- try {
- isLoading = true
-
- // https://developer.android.com/studio/command-line/logcat
- val process = Runtime.getRuntime().exec("logcat --binary -d")
- val items = arrayListOf()
- LogcatBinaryParser(process.inputStream).use { parser ->
- while (true) {
- val item = parser.parseItem() ?: break
- items.add(item)
- }
- }
-
- list.value = items.toPersistentList()
- } catch (e: Exception) {
- logError(e) // kinda ironic
- } finally {
- isLoading = false
- }
- }
- val (dismissFocus, confirmFocus) = remember { FocusRequester.createRefs() }
-
- val context = LocalContext.current
- val scope = rememberCoroutineScope()
- AlertDialog(
- containerColor = MaterialTheme.colorScheme.background,
- onDismissRequest = dismiss,
- title = {
- Text(text = stringResource(R.string.log_cat))
- },
- text = {
- if (isLoading) {
- LinearProgressIndicator(
- modifier = Modifier.fillMaxWidth(),
- color = MaterialTheme.colorScheme.onBackground,
- trackColor = MaterialTheme.colorScheme.surfaceVariant,
- )
- }
- LazyColumn(
- modifier = Modifier.focusProperties {
- start = dismissFocus
- end = confirmFocus
- }
- ) {
- items(items = list.value) { item ->
- LogcatItem(item, modifier = Modifier.focusProperties {
- start = dismissFocus
- end = confirmFocus
- })
- }
- }
- },
- confirmButton = {
- WhiteButton(
- text = stringResource(R.string.sort_save),
- modifier = Modifier.focusRequester(confirmFocus)
- ) {
- scope.launch {
- withContext(Dispatchers.IO) {
- val date = SimpleDateFormat("yyyy_MM_dd_HH_mm", Locale.getDefault()).format(
- Date(currentTimeMillis())
- )
- var fileStream: OutputStream?
- try {
- fileStream = VideoDownloadManager.setupStream(
- context,
- "logcat_${date}",
- null,
- "txt",
- false
- ).openNew()
- fileStream.bufferedWriter()
- .use { writer ->
- list.value.forEach {
- writer.write(it.toString())
- writer.write("\n\n")
- }
- }
- dismiss()
- } catch (t: Throwable) {
- logError(t)
- showToast(t.message)
- }
- /*try {
- val date = SimpleDateFormat(
- "yyyy_MM_dd_HH_mm",
- Locale.getDefault()
- ).format(
- Date(System.currentTimeMillis())
- )
-
- val file = FileHelper.logcat.createFile(context, "logcat_${date}")
- ?: throw ErrorLoadingException("Unable to create file")
- val stream = file.openOutputStream(append = false)
- ?: throw ErrorLoadingException("Unable to create stream")
-
- stream.bufferedWriter()
- .use { writer ->
- list.value.forEach {
- writer.write(it.toString())
- writer.write("\n\n")
- }
- }
- dismiss()
- showToast(
- txt(
- R.string.logcat_success,
- file.absolutePath ?: file.uri.toString()
- ),
- Toast.LENGTH_LONG
- )
- } catch (t: Throwable) {
- logError(t)
- showToast(t.message)
- }*/
- }
- }
- }
-
- WhiteButton(text = stringResource(R.string.sort_copy)) {
- clipboardHelper(
- txt("Logcat"),
- list.value.joinToString(separator = "\n\n") { it.toString() }
- )
- }
- WhiteButton(text = stringResource(R.string.sort_clear)) {
- try {
- Runtime.getRuntime().exec("logcat -c")
- } catch (t: Throwable) {
- logError(t)
- }
- dismiss()
- }
- },
- dismissButton = {
- BlackButton(
- text = stringResource(R.string.sort_close),
- onClick = dismiss,
- modifier = Modifier.focusRequester(dismissFocus)
- )
- },
- properties = DialogProperties(usePlatformDefaultWidth = false)
- )
-}
-
-
-@Composable
-fun LogcatItem(item: LogcatItem, modifier: Modifier = Modifier) {
- val color = when (item.level) {
- LogcatLevel.Fatal -> Color.Magenta
- LogcatLevel.Error -> Color.Red
- LogcatLevel.Warning -> Color.Yellow
- LogcatLevel.Info -> Color.White
- LogcatLevel.Debug -> Color.Green
- LogcatLevel.Verbose -> Color.Gray
- null -> Color.Transparent
- }
-
- Row(modifier = Modifier.fillMaxWidth()) {
- item.level?.identifier?.let { value ->
- Text(
- value,
- modifier = Modifier
- .padding(2.dp)
- .rounded()
- .background(MaterialTheme.colorScheme.onBackground)
- .padding(4.dp),
- color = MaterialTheme.colorScheme.surfaceVariant
- )
- }
- Text(
- item.date.toHumanReadable(),
- modifier = Modifier
- .padding(2.dp)
- .rounded()
- .background(MaterialTheme.colorScheme.surfaceVariant)
- .padding(4.dp),
- color = MaterialTheme.colorScheme.onBackground
- )
- Text(
- item.tag,
- modifier = Modifier
- .padding(2.dp)
- .rounded()
- .background(MaterialTheme.colorScheme.surfaceVariant)
- .padding(4.dp),
- color = MaterialTheme.colorScheme.onBackground
- )
- }
- Row(
- modifier = modifier
- .height(IntrinsicSize.Min)
- .fillMaxWidth()
- .rounded()
- .clickable(
- onClick = {
- clipboardHelper(txt("Logcat"), item.toString())
- })
- .padding(5.dp)
- ) {
- Box(
- modifier = Modifier
- .fillMaxHeight()
- .width(4.dp)
- .circle()
- .background(color)
- )
- Spacer(modifier = Modifier.width(5.dp))
- Text(
- item.message,
- color = MaterialTheme.colorScheme.onBackground,
- fontSize = 14.sp,
- lineHeight = 15.sp,
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatParser.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatParser.kt
deleted file mode 100644
index df1dc7077..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/logcat/LogcatParser.kt
+++ /dev/null
@@ -1,140 +0,0 @@
-package com.lagradost.cloudstream3.ui.settings.logcat
-
-import androidx.compose.runtime.Immutable
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-import java.io.Closeable
-import java.io.InputStream
-import kotlin.time.Instant
-import kotlinx.io.Buffer
-import kotlinx.io.asSource
-import kotlinx.io.readByteArray
-import kotlinx.io.readIntLe
-import kotlinx.io.readString
-import kotlinx.io.readUShortLe
-import java.text.SimpleDateFormat
-import java.util.Date
-import java.util.Locale
-import java.util.TimeZone
-
-fun Instant.toHumanReadable(): String {
- val formatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).apply {
- timeZone = TimeZone.getDefault()
- }
- return formatter.format(Date(this.toEpochMilliseconds()))
-}
-@Immutable
-data class LogcatItem(
- val date: Instant,
- val pid: Int,
- val tid: Int,
- val level: LogcatLevel?,
- val tag: String,
- val message: String,
-) {
- override fun toString(): String {
- return "${date.toHumanReadable()} $pid-$tid $tag ${level?.identifier ?: "?"} $message"
- }
-}
-
-enum class LogcatLevel(val identifier: String) {
- Fatal("WTF"),
- Error("E"),
- Warning("W"),
- Info("I"),
- Debug("D"),
- Verbose("V"),
-}
-
-/**https://github.com/brudaswen/android-logcat/blob/main/library/logcat-core/src/main/kotlin/de/brudaswen/android/logcat/core/parser/LogcatBinaryParser.kt */
-class LogcatBinaryParser(
- private val input: InputStream,
-) : Closeable by input {
- val source = input.asSource()
- private val buffer = Buffer()
-
- /**
- * Parse one [LogcatItem] from the current [input] stream.
- *
- * @return The parsed [LogcatItem] or `null` if stream reached EOF.
- */
- suspend fun parseItem(): LogcatItem? = withContext(Dispatchers.IO) {
- val firstByte = input.read()
- if (firstByte == -1) return@withContext null
-
- // Read v1 header
- buffer.writeByte(firstByte.toByte())
- buffer.write(source = source, byteCount = 19)
-
- val len = buffer.readUShortLe().toInt()
- val headerSize = buffer.readUShortLe().toInt()
- val pid = buffer.readIntLe()
- val tid = buffer.readIntLe()
- val sec = buffer.readIntLe()
- val nsec = buffer.readIntLe()
-
- // Read additional header fields
- buffer.write(source = source, byteCount = headerSize - 20L)
-
- val additionalHeaderBytes = (headerSize - 20).coerceAtLeast(0)
- buffer.readByteArray(byteCount = additionalHeaderBytes)
-
- // Read payload
- buffer.write(source = source, byteCount = len.toLong())
-
- val priority = buffer.readByte()
-
- val payload = buffer.readString()
- val texts = payload.split('\u0000', limit = 2)
- val tag = texts.getOrNull(0).orEmpty()
- val message = texts.getOrNull(1).orEmpty().removeSuffix("\u0000").trim()
-
- // Clear buffer
- buffer.clear()
-
- // Convert raw values to item
- LogcatItem(
- sec = sec,
- nsec = nsec,
- priority = priority,
- pid = pid,
- tid = tid,
- tag = tag,
- message = message,
- )
- }
-
- private fun LogcatItem(
- sec: Int,
- nsec: Int,
- priority: Byte,
- pid: Int,
- tid: Int,
- tag: String,
- message: String,
- ): LogcatItem {
- val date = Instant.fromEpochSeconds(
- epochSeconds = sec.toLong(),
- nanosecondAdjustment = nsec,
- )
-
- val level = when (priority) {
- 2.toByte() -> LogcatLevel.Verbose
- 3.toByte() -> LogcatLevel.Debug
- 4.toByte() -> LogcatLevel.Info
- 5.toByte() -> LogcatLevel.Warning
- 6.toByte() -> LogcatLevel.Error
- 7.toByte() -> LogcatLevel.Fatal
- else -> null
- }
-
- return LogcatItem(
- date = date,
- pid = pid,
- tid = tid,
- level = level,
- tag = tag,
- message = message,
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt
index 42a62ed52..e96a662c3 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt
@@ -50,18 +50,18 @@ class SetupFragmentLanguage : BaseFragment(
appIconImage.setImageDrawable(ContextCompat.getDrawable(ctx, drawable))
}
- val tags = appLanguages.map { it.second }
- val current = getCurrentLocale(ctx).replace('_', '-')
- val currentIndex = tags.indexOf(current).takeIf { it >= 0 }
- ?: tags.indexOf(current.substringBefore('-'))
+ val current = getCurrentLocale(ctx)
+ val languageTagsIETF = appLanguages.map { it.second }
+ val languageNames = appLanguages.map { it.nameNextToFlagEmoji() }
+ val currentIndex = languageTagsIETF.indexOf(current)
- arrayAdapter.addAll(appLanguages.map { it.nameNextToFlagEmoji() })
+ arrayAdapter.addAll(languageNames)
listview1.adapter = arrayAdapter
listview1.choiceMode = AbsListView.CHOICE_MODE_SINGLE
- if (currentIndex >= 0) listview1.setItemChecked(currentIndex, true)
+ listview1.setItemChecked(currentIndex, true)
listview1.setOnItemClickListener { _, _, selectedLangIndex, _ ->
- val langTagIETF = tags[selectedLangIndex]
+ val langTagIETF = languageTagsIETF[selectedLangIndex]
CommonActivity.setLocale(activity, langTagIETF)
settingsManager.edit {
putString(getString(R.string.locale_key), langTagIETF)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt
index 45755b947..8da121daa 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt
@@ -4,6 +4,7 @@ import android.view.View
import android.widget.AbsListView
import android.widget.ArrayAdapter
import androidx.core.content.edit
+import androidx.core.util.forEach
import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.R
@@ -26,41 +27,40 @@ class SetupFragmentMedia : BaseFragment(
safe {
val ctx = context ?: return@safe
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
- val prefKey = getString(R.string.prefer_media_type_key)
val arrayAdapter =
ArrayAdapter(ctx, R.layout.sort_bottom_single_choice)
- val sortedTypes = enumValues().sorted()
+ val names = enumValues().sorted().map { it.name }
+ val selected = mutableListOf()
- arrayAdapter.addAll(sortedTypes.map { it.name })
+ arrayAdapter.addAll(names)
binding.apply {
listview1.let {
it.adapter = arrayAdapter
it.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
- val stored = settingsManager.getStringSet(prefKey, null)
- sortedTypes.forEachIndexed { index, type ->
- // All media types except NSFW are checked by default
- val isChecked = stored?.contains(type.ordinal.toString())
- ?: (type != TvType.NSFW)
- it.setItemChecked(index, isChecked)
- }
-
- val saveSelection = {
- val values = sortedTypes
- .filterIndexed { index, _ -> it.isItemChecked(index) }
- .map { type -> type.ordinal.toString() }
- .toSet()
- settingsManager.edit { putStringSet(prefKey, values) }
+ it.setOnItemClickListener { _, _, _, _ ->
+ it.checkedItemPositions?.forEach { key, value ->
+ if (value) {
+ selected.add(key)
+ } else {
+ selected.remove(key)
+ }
+ }
+ val prefValues = selected.mapNotNull { pos ->
+ val item =
+ it.getItemAtPosition(pos)?.toString() ?: return@mapNotNull null
+ val itemVal = TvType.valueOf(item)
+ itemVal.ordinal.toString()
+ }.toSet()
+ settingsManager.edit {
+ putStringSet(getString(R.string.prefer_media_type_key), prefValues)
+ }
// Regenerate set homepage
DataStoreHelper.currentHomePage = null
}
-
- if (stored == null) saveSelection()
-
- it.setOnItemClickListener { _, _, _, _ -> saveSelection() }
}
nextBtt.setOnClickListener {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt
index 0e203989f..ab64e9993 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt
@@ -11,8 +11,6 @@ import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.OptIn
-import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.mutableStateOf
import androidx.media3.common.text.Cue
import androidx.media3.common.util.UnstableApi
import com.fasterxml.jackson.annotation.JsonProperty
@@ -46,16 +44,15 @@ import kotlinx.serialization.Serializable
const val CHROME_SUBTITLE_KEY = "chome_subtitle_settings"
@Serializable
-@Immutable
data class SaveChromeCaptionStyle(
- @JsonProperty("fontFamily") @SerialName("fontFamily") val fontFamily: String? = null,
- @JsonProperty("fontGenericFamily") @SerialName("fontGenericFamily") val fontGenericFamily: Int? = null,
- @JsonProperty("backgroundColor") @SerialName("backgroundColor") val backgroundColor: Int = 0x00FFFFFF, // transparent
- @JsonProperty("edgeColor") @SerialName("edgeColor") val edgeColor: Int = Color.BLACK, // BLACK
- @JsonProperty("edgeType") @SerialName("edgeType") val edgeType: Int = EDGE_TYPE_OUTLINE,
- @JsonProperty("foregroundColor") @SerialName("foregroundColor") val foregroundColor: Int = Color.WHITE,
- @JsonProperty("fontScale") @SerialName("fontScale") val fontScale: Float = 1.05f,
- @JsonProperty("windowColor") @SerialName("windowColor") val windowColor: Int = Color.TRANSPARENT,
+ @JsonProperty("fontFamily") @SerialName("fontFamily") var fontFamily: String? = null,
+ @JsonProperty("fontGenericFamily") @SerialName("fontGenericFamily") var fontGenericFamily: Int? = null,
+ @JsonProperty("backgroundColor") @SerialName("backgroundColor") var backgroundColor: Int = 0x00FFFFFF, // transparent
+ @JsonProperty("edgeColor") @SerialName("edgeColor") var edgeColor: Int = Color.BLACK, // BLACK
+ @JsonProperty("edgeType") @SerialName("edgeType") var edgeType: Int = EDGE_TYPE_OUTLINE,
+ @JsonProperty("foregroundColor") @SerialName("foregroundColor") var foregroundColor: Int = Color.WHITE,
+ @JsonProperty("fontScale") @SerialName("fontScale") var fontScale: Float = 1.05f,
+ @JsonProperty("windowColor") @SerialName("windowColor") var windowColor: Int = Color.TRANSPARENT,
)
class ChromecastSubtitlesFragment : BaseFragment(
@@ -95,9 +92,6 @@ class ChromecastSubtitlesFragment : BaseFragment(CHROME_SUBTITLE_KEY) ?: defaultState
}
- val defaultChromeCastSubtitleState = SaveChromeCaptionStyle()
- val chromeCastSubtitleState = mutableStateOf(
- getKey(CHROME_SUBTITLE_KEY) ?: defaultChromeCastSubtitleState
- )
+ private val defaultState = SaveChromeCaptionStyle()
}
private fun onColorSelected(stuff: Pair) {
@@ -143,10 +134,10 @@ class ChromecastSubtitlesFragment : BaseFragment state = state.copy(foregroundColor = realColor)
- 1 -> state = state.copy(edgeColor = realColor)
- 2 -> state = state.copy(backgroundColor = realColor)
- 3 -> state = state.copy(windowColor = realColor)
+ 0 -> state.foregroundColor = realColor
+ 1 -> state.edgeColor = realColor
+ 2 -> state.backgroundColor = realColor
+ 3 -> state.windowColor = realColor
else -> Unit
}
@@ -249,13 +240,13 @@ class ChromecastSubtitlesFragment : BaseFragment
- state = state.copy(edgeType = edgeTypes.map { it.first }[index])
+ state.edgeType = edgeTypes.map { it.first }[index]
updateState()
}
}
binding.subsEdgeType.setOnLongClickListener {
- state = state.copy(edgeType = defaultChromeCastSubtitleState.edgeType)
+ state.edgeType = defaultState.edgeType
updateState()
showToast(R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
return@setOnLongClickListener true
@@ -290,13 +281,13 @@ class ChromecastSubtitlesFragment : BaseFragment
- state = state.copy(fontScale = fontSizes.map { it.first }[index])
+ state.fontScale = fontSizes.map { it.first }[index]
//textView.context.updateState() // font size not changed
}
}
binding.subsFontSize.setOnLongClickListener { _ ->
- state = state.copy(fontScale = defaultChromeCastSubtitleState.fontScale)
+ state.fontScale = defaultState.fontScale
//textView.context.updateState() // font size not changed
showToast(R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
return@setOnLongClickListener true
@@ -323,12 +314,12 @@ class ChromecastSubtitlesFragment : BaseFragment
- state = state.copy(fontFamily = fontTypes.map { it.first }[index])
+ state.fontFamily = fontTypes.map { it.first }[index]
updateState()
}
}
binding.subsFont.setOnLongClickListener { _ ->
- state = state.copy(fontFamily = defaultChromeCastSubtitleState.fontFamily)
+ state.fontFamily = defaultState.fontFamily
updateState()
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
return@setOnLongClickListener true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment2.kt
deleted file mode 100644
index a25a8aa88..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment2.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.lagradost.cloudstream3.ui.subtitles
-
-import com.lagradost.cloudstream3.utils.BaseComposeFragment
-import com.lagradost.cloudstream4.compose.Screen
-
-class ChromecastSubtitlesFragment2 : BaseComposeFragment(), Screen by ChromecastSubtitlesScreen
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesScreen.kt
deleted file mode 100644
index 63c3209b6..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesScreen.kt
+++ /dev/null
@@ -1,159 +0,0 @@
-package com.lagradost.cloudstream3.ui.subtitles
-
-import androidx.compose.foundation.layout.RowScope
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_DEPRESSED
-import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_DROP_SHADOW
-import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_NONE
-import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_OUTLINE
-import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_RAISED
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.ui.subtitles.ChromecastSubtitlesFragment.Companion.applyStyleEvent
-import com.lagradost.cloudstream3.ui.subtitles.ChromecastSubtitlesFragment.Companion.chromeCastSubtitleState
-import com.lagradost.cloudstream3.ui.subtitles.ChromecastSubtitlesFragment.Companion.defaultChromeCastSubtitleState
-import com.lagradost.cloudstream3.ui.subtitles.ChromecastSubtitlesFragment.Companion.saveStyle
-import com.lagradost.cloudstream3.ui.subtitles.SubtitlesScreen.SetUpUndoBar
-import com.mihon.common.preference.StatePreferenceStore
-import com.mihon.presentation.settings.Preference
-import com.mihon.presentation.settings.SearchableSettings
-import kotlinx.collections.immutable.persistentListOf
-import kotlinx.collections.immutable.persistentMapOf
-import kotlin.math.roundToInt
-
-object ChromecastSubtitlesScreen : SearchableSettings {
- @Composable
- override fun getTitleRes(): String = stringResource(R.string.chromecast_subtitles_settings)
-
- // For ux purposes we also want to show a diff of what will be reset
- private val diff = arrayOf Any?>>(
- R.string.subs_font to { style -> style.fontFamily },
- R.string.subs_text_color to { state -> state.foregroundColor },
- R.string.subs_font_size to { state -> state.fontScale },
- R.string.subs_edge_type to { state -> state.edgeType },
- R.string.subs_outline_color to { state -> state.edgeColor },
- R.string.subs_window_color to { state -> state.windowColor },
- R.string.subs_background_color to { state -> state.backgroundColor },
- )
-
- @Composable
- override fun RowScope.AppBarAction() {
- val initialState = remember { chromeCastSubtitleState.value }
- var state by chromeCastSubtitleState
- val context = LocalContext.current
-
- SideEffect(state) {
- // When we modify the state, we modify the stored data
- context.saveStyle(state)
- }
-
- DisposableEffect(Unit) {
- onDispose {
- if (initialState != state) {
- applyStyleEvent.invoke(state)
- }
- }
- }
- SetUpUndoBar(
- diff = diff,
- initialState = initialState,
- currentState = chromeCastSubtitleState,
- default = defaultChromeCastSubtitleState
- )
- }
-
- @Composable
- override fun getPreferences(): List {
- val store = StatePreferenceStore(chromeCastSubtitleState)
-
- return persistentListOf(
- Preference.PreferenceGroup(
- title = stringResource(R.string.subs_font),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.ListPreference(
- preference = store.field(SaveChromeCaptionStyle::fontFamily) { newValue ->
- copy(fontFamily = newValue)
- },
- icon = painterResource(R.drawable.font_download_24px),
- title = stringResource(R.string.subs_font),
- entries = persistentMapOf(
- null to stringResource(R.string.normal),
- "Droid Sans" to "Droid Sans",
- "Droid Sans Mono" to "Droid Sans Mono",
- "Droid Serif Regular" to "Droid Serif Regular",
- "Cutive Mono" to "Cutive Mono",
- "Short Stack" to "Short Stack",
- "Quintessential" to "Quintessential",
- "Alegreya Sans SC" to "Alegreya Sans SC",
- )
- ),
- Preference.PreferenceItem.ColorPreference(
- preference = store.field(SaveChromeCaptionStyle::foregroundColor) { newValue ->
- copy(foregroundColor = newValue)
- },
- title = stringResource(R.string.subs_text_color),
- icon = painterResource(R.drawable.format_color_text_24px),
- ),
- Preference.PreferenceItem.SliderPreference(
- preference = store.field(
- get = { fontScale.times(100.0f).roundToInt() },
- set = { newValue -> copy(fontScale = newValue.toFloat() * 0.01f) }),
- icon = painterResource(R.drawable.text_fields_24px),
- title = stringResource(R.string.subs_font_size),
- valueRange = 75..150,
- steps = 75 / 5 - 1,
- )
- )
- ), Preference.PreferenceGroup(
- title = stringResource(R.string.subs_edge), preferenceItems = persistentListOf(
- Preference.PreferenceItem.ListPreference(
- preference = store.field(SaveChromeCaptionStyle::edgeType) { newValue ->
- copy(edgeType = newValue)
- },
- icon = painterResource(R.drawable.shadow_24px),
- title = stringResource(R.string.subs_edge_type),
- entries = persistentMapOf(
- EDGE_TYPE_NONE to stringResource(R.string.subtitles_none),
- EDGE_TYPE_OUTLINE to stringResource(R.string.subtitles_outline),
- EDGE_TYPE_DEPRESSED to stringResource(R.string.subtitles_depressed),
- EDGE_TYPE_DROP_SHADOW to stringResource(R.string.subtitles_shadow),
- EDGE_TYPE_RAISED to stringResource(R.string.subtitles_raised),
- )
- ),
- Preference.PreferenceItem.ColorPreference(
- preference = store.field(SaveChromeCaptionStyle::edgeColor) { newValue ->
- copy(edgeColor = newValue)
- },
- //enabled = state.edgeType != EDGE_TYPE_NONE,
- title = stringResource(R.string.subs_outline_color),
- icon = painterResource(R.drawable.border_color_24px),
- ),
- )
- ), Preference.PreferenceGroup(
- title = stringResource(R.string.background), preferenceItems = persistentListOf(
- Preference.PreferenceItem.ColorPreference(
- preference = store.field(SaveChromeCaptionStyle::windowColor) { newValue ->
- copy(windowColor = newValue)
- },
- title = stringResource(R.string.subs_window_color),
- icon = painterResource(R.drawable.imagesearch_roller_24px),
- ),
- Preference.PreferenceItem.ColorPreference(
- preference = store.field(SaveChromeCaptionStyle::backgroundColor) { newValue ->
- copy(backgroundColor = newValue)
- },
- title = stringResource(R.string.subs_background_color),
- icon = painterResource(R.drawable.format_color_fill_24px),
- ),
- )
- )
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt
index dd363fc10..bf77ef247 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt
@@ -9,7 +9,6 @@ import android.os.Bundle
import android.text.Layout
import android.text.Spannable
import android.text.SpannableString
-import android.text.SpannableStringBuilder
import android.text.style.StyleSpan
import android.util.DisplayMetrics
import android.util.TypedValue
@@ -19,8 +18,6 @@ import android.widget.Toast
import androidx.annotation.FontRes
import androidx.annotation.OptIn
import androidx.annotation.Px
-import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.mutableStateOf
import androidx.core.content.edit
import androidx.core.content.res.ResourcesCompat
import androidx.media3.common.text.Cue
@@ -66,83 +63,30 @@ const val SUBTITLE_AUTO_SELECT_KEY = "subs_auto_select"
const val SUBTITLE_DOWNLOAD_KEY = "subs_auto_download"
@Serializable
-enum class SubtitleFont(
- @FontRes val resource: Int,
- val label: String
-) {
- @JsonProperty("Trebuchet")
- @SerialName("Trebuchet")
- Trebuchet(R.font.trebuchet_ms, "Trebuchet MS"),
- @JsonProperty("Netflix")
- @SerialName("Netflix")
- Netflix(R.font.netflix_sans, "Netflix Sans"),
- @JsonProperty("Google")
- @SerialName("Google")
- Google(R.font.google_sans, "Google Sans"),
- @JsonProperty("Open")
- @SerialName("Open")
- Open(R.font.open_sans, "Open Sans"),
- @JsonProperty("Futura")
- @SerialName("Futura")
- Futura(R.font.futura, "Futura"),
- @JsonProperty("Consola")
- @SerialName("Consola")
- Consola(R.font.consola, "Consola"),
- @JsonProperty("Gotham")
- @SerialName("Gotham")
- Gotham(R.font.gotham, "Gotham"),
- @JsonProperty("Lucida")
- @SerialName("Lucida")
- Lucida(R.font.lucida_grande, "Lucida Grande"),
- @JsonProperty("STIX")
- @SerialName("STIX")
- STIX(R.font.stix_general, "STIX General"),
- @JsonProperty("TimesNewRoman")
- @SerialName("TimesNewRoman")
- TimesNewRoman(R.font.times_new_roman, "Times New Roman"),
- @JsonProperty("Verdana")
- @SerialName("Verdana")
- Verdana(R.font.verdana, "Verdana"),
- @JsonProperty("Ubuntu")
- @SerialName("Ubuntu")
- Ubuntu(R.font.ubuntu_regular, "Ubuntu"),
- @JsonProperty("Comic")
- @SerialName("Comic")
- Comic(R.font.comic_sans, "Comic Sans"),
- @JsonProperty("Poppins")
- @SerialName("Poppins")
- Poppins(R.font.poppins_regular, "Poppins"),
- @JsonProperty("Custom")
- @SerialName("Custom")
- Custom(0, "Custom"),
-}
-
-@Serializable
-@Immutable
data class SaveCaptionStyle(
- @JsonProperty("foregroundColor") @SerialName("foregroundColor") val foregroundColor: Int,
- @JsonProperty("backgroundColor") @SerialName("backgroundColor") val backgroundColor: Int,
- @JsonProperty("windowColor") @SerialName("windowColor") val windowColor: Int,
+ @JsonProperty("foregroundColor") @SerialName("foregroundColor") var foregroundColor: Int,
+ @JsonProperty("backgroundColor") @SerialName("backgroundColor") var backgroundColor: Int,
+ @JsonProperty("windowColor") @SerialName("windowColor") var windowColor: Int,
@OptIn(UnstableApi::class)
- @JsonProperty("edgeType") @SerialName("edgeType") val edgeType: @CaptionStyleCompat.EdgeType Int,
- @JsonProperty("edgeColor") @SerialName("edgeColor") val edgeColor: Int,
- @JsonProperty("font") @SerialName("font") val font: SubtitleFont? = null,
- @JsonProperty("typefaceFilePath") @SerialName("typefaceFilePath") val typefaceFilePath: String?,
- @JsonProperty("elevation") @SerialName("elevation") val elevation: Int, // in dp
- @JsonProperty("fixedTextSize") @SerialName("fixedTextSize") val fixedTextSize: Float?, // in sp
- @Px @JsonProperty("edgeSize") @SerialName("edgeSize") val edgeSize: Float? = null,
- @JsonProperty("removeCaptions") @SerialName("removeCaptions") val removeCaptions: Boolean = false,
- @JsonProperty("removeBloat") @SerialName("removeBloat") val removeBloat: Boolean = true,
+ @JsonProperty("edgeType") @SerialName("edgeType") var edgeType: @CaptionStyleCompat.EdgeType Int,
+ @JsonProperty("edgeColor") @SerialName("edgeColor") var edgeColor: Int,
+ @FontRes @JsonProperty("typeface") @SerialName("typeface") var typeface: Int?,
+ @JsonProperty("typefaceFilePath") @SerialName("typefaceFilePath") var typefaceFilePath: String?,
+ @JsonProperty("elevation") @SerialName("elevation") var elevation: Int, // in dp
+ @JsonProperty("fixedTextSize") @SerialName("fixedTextSize") var fixedTextSize: Float?, // in sp
+ @Px @JsonProperty("edgeSize") @SerialName("edgeSize") var edgeSize: Float? = null,
+ @JsonProperty("removeCaptions") @SerialName("removeCaptions") var removeCaptions: Boolean = false,
+ @JsonProperty("removeBloat") @SerialName("removeBloat") var removeBloat: Boolean = true,
/** Apply caps lock to the text */
- @JsonProperty("upperCase") @SerialName("upperCase") val upperCase: Boolean = false,
+ @JsonProperty("upperCase") @SerialName("upperCase") var upperCase: Boolean = false,
/** Apply bold to the text */
- @JsonProperty("bold") @SerialName("bold") val bold: Boolean = false,
+ @JsonProperty("bold") @SerialName("bold") var bold: Boolean = false,
/** Apply italic to the text */
- @JsonProperty("italic") @SerialName("italic") val italic: Boolean = false,
+ @JsonProperty("italic") @SerialName("italic") var italic: Boolean = false,
/** in px, background radius, aka how round the background (backgroundColor) on each row is */
- @JsonProperty("backgroundRadius") @SerialName("backgroundRadius") val backgroundRadius: Float? = null,
+ @JsonProperty("backgroundRadius") @SerialName("backgroundRadius") var backgroundRadius: Float? = null,
/** The SSA_ALIGNMENT */
- @JsonProperty("alignment") @SerialName("alignment") val alignment: Int? = null,
+ @JsonProperty("alignment") @SerialName("alignment") var alignment: Int? = null,
)
const val DEF_SUBS_ELEVATION = 20
@@ -186,7 +130,7 @@ class SubtitlesFragment : BaseDialogFragment(
}
fun Cue.Builder.applyStyle(style: SaveCaptionStyle): Cue.Builder {
- val edgeSize = style.edgeSize ?: 0.0f
+ val edgeSize = style.edgeSize
/*
This is old code for only applying on non null
@@ -204,18 +148,22 @@ class SubtitlesFragment : BaseDialogFragment(
// instead we use the inherit size of the subtitle view
setTextSize(Cue.DIMEN_UNSET, Cue.TYPE_UNSET)
+ // 2. apply edge
text?.let { text ->
- val textSpanBuilder = SpannableStringBuilder(text)
-
- // 2. apply edge
- if (edgeSize > 0.0f) {
- textSpanBuilder.setSpan(
- OutlineSpan(edgeSize), 0, textSpanBuilder.length,
+ val customSpan = SpannableString.valueOf(text)
+ if (edgeSize != null) {
+ customSpan.setSpan(
+ OutlineSpan(edgeSize), 0, customSpan.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
+ setText(customSpan)
+ }
+
+ // 3. apply bold + italic
+ text?.let { text ->
+ val customSpan = SpannableString.valueOf(text)
- // 3. apply bold + italic
val typeface = when (style.bold to style.italic) {
(true to true) -> Typeface.BOLD_ITALIC
(true to false) -> Typeface.BOLD
@@ -227,36 +175,39 @@ class SubtitlesFragment : BaseDialogFragment(
}
if (typeface != Typeface.NORMAL) {
val styleSpan = StyleSpan(typeface)
- textSpanBuilder.setSpan(
- styleSpan, 0, textSpanBuilder.length,
+ customSpan.setSpan(
+ styleSpan, 0, customSpan.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
+ setText(customSpan)
+ }
- // 4. apply radius
- val radius = style.backgroundRadius ?: 0.0f
- if (radius > 0.0f && style.backgroundColor != Color.TRANSPARENT) {
+ // 4. apply radius
+ text?.let { text ->
+ val customSpan = SpannableString.valueOf(text)
+ val radius = style.backgroundRadius
+
+ if (radius != null && style.backgroundColor != Color.TRANSPARENT) {
val styleSpan = RoundedBackgroundColorSpan(
style.backgroundColor,
this.textAlignment ?: Layout.Alignment.ALIGN_CENTER,
2.0F + radius * 0.5f,
radius
)
- textSpanBuilder.setSpan(
- styleSpan, 0, textSpanBuilder.length,
+ customSpan.setSpan(
+ styleSpan, 0, customSpan.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
+ setText(customSpan)
+ }
- // 5. remove captions but ensure that styling is kept
- // Just doing .replace removes all styling!
+ // 5. remove captions
+ text?.let { text ->
if (style.removeCaptions) {
- for (match in captionRegex.findAll(text).toList().asReversed()) {
- textSpanBuilder.delete(match.range.first, match.range.last + 1)
- }
+ setText(text.replace(captionRegex, ""))
}
-
- this.setText(textSpanBuilder)
}
// 6. set alignment
@@ -266,31 +217,23 @@ class SubtitlesFragment : BaseDialogFragment(
private fun Context.fromSaveToStyle(data: SaveCaptionStyle): CaptionStyleCompat {
return CaptionStyleCompat(
data.foregroundColor,
- data.backgroundColor,
+ // we actually override with a custom span when backgroundRadius != null
+ if (data.backgroundRadius == null) data.backgroundColor else Color.TRANSPARENT,
data.windowColor,
data.edgeType,
data.edgeColor,
data.typefaceFilePath?.let {
try {
// RuntimeException: Font asset not found
- // Fuck android, they have no good way to load a font from a uri without a copy
Typeface.createFromFile(File(it))
- } catch (_: Exception) {
+ } catch (e: Exception) {
null
}
- } ?: data.font?.let { font ->
- if(font.resource != 0) {
- try {
- ResourcesCompat.getFont(
- this,
- font.resource
- )
- } catch (_ : Throwable) {
- Typeface.SANS_SERIF
- }
- } else {
- Typeface.SANS_SERIF
- }
+ } ?: data.typeface?.let {
+ ResourcesCompat.getFont(
+ this,
+ it
+ )
}
?: Typeface.SANS_SERIF
)
@@ -313,29 +256,25 @@ class SubtitlesFragment : BaseDialogFragment(
}
}
+ private var cachedSubtitleStyle: SaveCaptionStyle? = null
+
fun Context.saveStyle(style: SaveCaptionStyle) {
- if (subtitleStyleState.value !== style) {
- subtitleStyleState.value = style
- }
+ cachedSubtitleStyle = style
this.setKey(SUBTITLE_KEY, style)
}
- val defaultSubtitleStyle = SaveCaptionStyle(
- foregroundColor = getDefColor(0),
- backgroundColor = getDefColor(2),
- windowColor = getDefColor(3),
- edgeType = CaptionStyleCompat.EDGE_TYPE_OUTLINE,
- edgeColor = getDefColor(1),
- font = null,
- typefaceFilePath = null,
- elevation = DEF_SUBS_ELEVATION,
- fixedTextSize = null,
- )
- val subtitleStyleState =
- mutableStateOf((getKey(SUBTITLE_KEY) ?: defaultSubtitleStyle))
-
fun getCurrentSavedStyle(): SaveCaptionStyle {
- return subtitleStyleState.value
+ return cachedSubtitleStyle ?: (getKey(SUBTITLE_KEY) ?: SaveCaptionStyle(
+ foregroundColor = getDefColor(0),
+ backgroundColor = getDefColor(2),
+ windowColor = getDefColor(3),
+ edgeType = CaptionStyleCompat.EDGE_TYPE_OUTLINE,
+ edgeColor = getDefColor(1),
+ typeface = null,
+ typefaceFilePath = null,
+ elevation = DEF_SUBS_ELEVATION,
+ fixedTextSize = null,
+ )).also { cachedSubtitleStyle = it }
}
private fun Context.getSavedFonts(): List {
@@ -379,10 +318,10 @@ class SubtitlesFragment : BaseDialogFragment(
private fun Context.setColor(id: Int, color: Int?) {
val realColor = color ?: getDefColor(id)
when (id) {
- 0 -> state = state.copy(foregroundColor = realColor)
- 1 -> state = state.copy(edgeColor = realColor)
- 2 -> state = state.copy(backgroundColor = realColor)
- 3 -> state = state.copy(windowColor = realColor)
+ 0 -> state.foregroundColor = realColor
+ 1 -> state.edgeColor = realColor
+ 2 -> state.backgroundColor = realColor
+ 3 -> state.windowColor = realColor
else -> Unit
}
@@ -508,7 +447,7 @@ class SubtitlesFragment : BaseDialogFragment(
false,
dismissCallback
) { index ->
- state = state.copy(elevation = elevationTypes.map { it.first }[index])
+ state.elevation = elevationTypes.map { it.first }[index]
textView.context.updateState()
if (hide)
activity?.hideSystemUI()
@@ -516,7 +455,7 @@ class SubtitlesFragment : BaseDialogFragment(
}
subsSubtitleElevation.setOnLongClickListener {
- state = state.copy(elevation = DEF_SUBS_ELEVATION)
+ state.elevation = DEF_SUBS_ELEVATION
it.context.updateState()
showToast(R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
return@setOnLongClickListener true
@@ -539,14 +478,13 @@ class SubtitlesFragment : BaseDialogFragment(
false,
dismissCallback
) { index ->
- state =
- state.copy(backgroundRadius = radiusTypes.map { it.first }[index]?.toFloat())
+ state.backgroundRadius = radiusTypes.map { it.first }[index]?.toFloat()
textView.context.updateState()
}
}
subsBackgroundRadius.setOnLongClickListener {
- state = state.copy(backgroundRadius = null)
+ state.backgroundRadius = null
it.context.updateState()
showToast(R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
return@setOnLongClickListener true
@@ -574,7 +512,7 @@ class SubtitlesFragment : BaseDialogFragment(
false,
dismissCallback
) { index ->
- state = state.copy(alignment = alignmentTypes.map { it.first }[index])
+ state.alignment = alignmentTypes.map { it.first }[index]
textView.context.updateState()
}
}
@@ -602,13 +540,13 @@ class SubtitlesFragment : BaseDialogFragment(
false,
dismissCallback
) { index ->
- state = state.copy(edgeType = edgeTypes.map { it.first }[index])
+ state.edgeType = edgeTypes.map { it.first }[index]
textView.context.updateState()
}
}
subsEdgeType.setOnLongClickListener {
- state = state.copy(edgeType = CaptionStyleCompat.EDGE_TYPE_OUTLINE)
+ state.edgeType = CaptionStyleCompat.EDGE_TYPE_OUTLINE
it.context.updateState()
showToast(R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
return@setOnLongClickListener true
@@ -628,7 +566,7 @@ class SubtitlesFragment : BaseDialogFragment(
false,
dismissCallback
) { index ->
- state = state.copy(fixedTextSize = fontSizes.map { it.first }[index])
+ state.fixedTextSize = fontSizes.map { it.first }[index]
textView.context.updateState()
}
}
@@ -647,47 +585,47 @@ class SubtitlesFragment : BaseDialogFragment(
false,
dismissCallback
) { index ->
- state = state.copy(edgeSize = fontSizes.map { it.first }[index])
+ state.edgeSize = fontSizes.map { it.first }[index]
textView.context.updateState()
}
}
subtitlesRemoveBloat.isChecked = state.removeBloat
subtitlesRemoveBloat.setOnCheckedChangeListener { _, b ->
- state = state.copy(removeBloat = b)
+ state.removeBloat = b
}
subtitlesUppercase.isChecked = state.upperCase
subtitlesUppercase.setOnCheckedChangeListener { _, b ->
- state = state.copy(upperCase = b)
+ state.upperCase = b
context?.updateState()
}
subtitlesRemoveCaptions.isChecked = state.removeCaptions
subtitlesRemoveCaptions.setOnCheckedChangeListener { _, b ->
- state = state.copy(removeCaptions = b)
+ state.removeCaptions = b
}
subtitlesBold.isChecked = state.bold
subtitlesBold.setOnCheckedChangeListener { _, b ->
- state = state.copy(bold = b)
+ state.bold = b
context?.updateState()
}
subtitlesItalic.isChecked = state.italic
subtitlesItalic.setOnCheckedChangeListener { _, b ->
- state = state.copy(italic = b)
+ state.italic = b
context?.updateState()
}
subsFontSize.setOnLongClickListener { _ ->
- state = state.copy(fixedTextSize = null)
+ state.fixedTextSize = null
context?.updateState()
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
return@setOnLongClickListener true
}
subsEdgeSize.setOnLongClickListener { _ ->
- state = state.copy(edgeSize = null)
+ state.edgeSize = null
context?.updateState()
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
return@setOnLongClickListener true
@@ -733,7 +671,7 @@ class SubtitlesFragment : BaseDialogFragment(
savedFontTypes.indexOfFirst { it.absolutePath == state.typefaceFilePath }
.let { index ->
if (index == -1)
- fontTypes.indexOfFirst { it.first == state.font?.resource }
+ fontTypes.indexOfFirst { it.first == state.typeface }
else index + fontTypes.size
}
@@ -745,23 +683,20 @@ class SubtitlesFragment : BaseDialogFragment(
false,
dismissCallback
) { index ->
- state = if (index < fontTypes.size) {
- state.copy(
- font = SubtitleFont.entries.firstOrNull { it.resource == fontTypes[index].first },
- typefaceFilePath = null
- )
+ if (index < fontTypes.size) {
+ state.typeface = fontTypes[index].first
+ state.typefaceFilePath = null
} else {
- state.copy(
- typefaceFilePath = savedFontTypes[index - fontTypes.size].absolutePath,
- font = null
- )
+ state.typefaceFilePath = savedFontTypes[index - fontTypes.size].absolutePath
+ state.typeface = null
}
textView.context.updateState()
}
}
subsFont.setOnLongClickListener { textView ->
- state = state.copy(font = null, typefaceFilePath = null)
+ state.typeface = null
+ state.typefaceFilePath = null
textView.context.updateState()
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
return@setOnLongClickListener true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment2.kt
deleted file mode 100644
index b9fd40556..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment2.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.lagradost.cloudstream3.ui.subtitles
-
-import com.lagradost.cloudstream3.utils.BaseComposeFragment
-import com.lagradost.cloudstream3.utils.BaseDialogComposeFragment
-import com.lagradost.cloudstream4.compose.Screen
-
-class SubtitlesFragment2 : BaseComposeFragment(), Screen by SubtitlesScreen
-class SubtitlesFragmentDialog2 : BaseDialogComposeFragment(), Screen by SubtitlesScreen
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesScreen.kt
deleted file mode 100644
index cba651c76..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesScreen.kt
+++ /dev/null
@@ -1,453 +0,0 @@
-package com.lagradost.cloudstream3.ui.subtitles
-
-import android.text.SpannableString
-import androidx.activity.compose.rememberLauncherForActivityResult
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.annotation.OptIn
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.RowScope
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.SideEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.media3.common.text.Cue
-import androidx.media3.common.util.UnstableApi
-import androidx.media3.ui.CaptionStyleCompat
-import androidx.media3.ui.SubtitleView
-import coil3.compose.AsyncImage
-import com.lagradost.cloudstream3.CloudStreamApp
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.mvvm.logError
-import com.lagradost.cloudstream3.ui.player.CustomDecoder
-import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.applyStyle
-import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.applyStyleEvent
-import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.defaultSubtitleStyle
-import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.saveStyle
-import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.setSubtitleViewStyle
-import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.subtitleStyleState
-import com.lagradost.cloudstream3.utils.AppUtils.parseJson
-import com.lagradost.cloudstream3.utils.AppUtils.toJsonLiteral
-import com.lagradost.cloudstream3.utils.SubtitleHelper.languages
-import com.lagradost.cloudstream4.compose.ActionDialog
-import com.mihon.common.preference.AndroidPreferenceStore
-import com.mihon.common.preference.DataPreferenceStore
-import com.mihon.common.preference.StatePreferenceStore
-import com.mihon.presentation.settings.Preference
-import com.mihon.presentation.settings.SearchableSettings
-import kotlinx.collections.immutable.persistentListOf
-import kotlinx.collections.immutable.persistentMapOf
-import java.io.File
-
-object SubtitlesScreen : SearchableSettings {
- @Composable
- override fun getTitleRes(): String = stringResource(R.string.subtitles_settings)
-
- // For ux purposes we also want to show a diff of what will be reset
- private val diff = arrayOf Any?>>(
- R.string.subs_font to { style -> style.font },
- R.string.subs_text_color to { state -> state.foregroundColor },
- R.string.subs_font_size to { state -> state.fixedTextSize },
- R.string.uppercase_all_subtitles to { state -> state.upperCase },
- R.string.all_subtitles_bold to { state -> state.bold },
- R.string.all_subtitles_italic to { state -> state.italic },
- R.string.subs_edge_type to { state -> state.edgeType },
- R.string.subs_outline_color to { state -> state.edgeColor },
- R.string.subs_edge_size to { state -> state.edgeSize },
- R.string.subs_window_color to { state -> state.windowColor },
- R.string.subs_background_color to { state -> state.backgroundColor },
- R.string.background_radius to { state -> state.backgroundRadius },
- R.string.subs_subtitle_alignment to { state -> state.alignment },
- R.string.subs_subtitle_elevation to { state -> state.elevation },
- R.string.subtitles_remove_captions to { state -> state.removeCaptions },
- R.string.subtitles_remove_bloat to { state -> state.removeBloat },
- )
-
- @Composable
- fun SetUpUndoBar(
- diff: Array Any?>>,
- initialState: T,
- currentState: MutableState,
- default: T
- ) {
- var currentState by currentState
- var dialogShown by remember { mutableStateOf(false) }
- if (dialogShown) {
- ActionDialog(
- title = stringResource(R.string.subs_default_reset_toast),
- // This can not be converted to joinToString due to stringResource being composable
- text =
- @Suppress("SimplifiableCallChain")
- diff.filter { (_, f) -> f(default) != f(currentState) }
- .map { (name, _) -> stringResource(name) }.joinToString(separator = "\n"),
- confirmText = stringResource(R.string.reset_btn),
- dismissText = stringResource(R.string.dismiss),
- confirm = {
- currentState = default
- dialogShown = false
- },
- dismiss = {
- dialogShown = false
- })
- }
-
- // Undo, current -> initial last saved value
- AnimatedVisibility(currentState != initialState) {
- IconButton(onClick = {
- currentState = initialState
- }) {
- Icon(
- painter = painterResource(R.drawable.undo_24px),
- contentDescription = stringResource(R.string.reset_btn),
- tint = MaterialTheme.colorScheme.onBackground
- )
- }
- }
-
- // Reset, current -> default
- AnimatedVisibility(currentState != default) {
- IconButton(onClick = {
- dialogShown = true
- }) {
- Icon(
- painter = painterResource(R.drawable.reset_colors_24px),
- contentDescription = stringResource(R.string.subs_default_reset_toast),
- tint = MaterialTheme.colorScheme.onBackground
- )
- }
- }
- }
-
- @Composable
- override fun RowScope.AppBarAction() {
- val initialState = remember { subtitleStyleState.value }
- var state by subtitleStyleState
- val context = LocalContext.current
-
- SideEffect(state) {
- // When we modify the state, we modify the stored data
- context.saveStyle(state)
- }
-
- DisposableEffect(Unit) {
- onDispose {
- if (initialState != state) {
- applyStyleEvent.invoke(state)
- }
- }
- }
-
- SetUpUndoBar(
- diff = diff,
- initialState = initialState,
- currentState = subtitleStyleState,
- default = defaultSubtitleStyle
- )
- }
-
- private val sortedLanguages by lazy {
- languages.map { it.IETF_tag to it.nameNextToFlagEmoji() }.sortedBy {
- it.second.substringAfter("\u00a0").lowercase()
- }.toMap()
- }
-
- @OptIn(UnstableApi::class)
- @Composable
- override fun getPreferences(): List {
- val store = StatePreferenceStore(subtitleStyleState)
- val dataStore = DataPreferenceStore(LocalContext.current)
- val androidStore = AndroidPreferenceStore(LocalContext.current)
-
- val subtitlesFilterSubLang = androidStore.getBoolean("filter_sub_lang_key", false)
- val autoSelectSubtitles = dataStore.getString(SUBTITLE_AUTO_SELECT_KEY, "en")
- val downloadSubsLanguage =
- dataStore.getObjectFromString(SUBTITLE_DOWNLOAD_KEY, setOf("en"), serializer = { obj ->
- obj.toList().toJsonLiteral()
- }, deserializer = { json ->
- parseJson>(json).toSet()
- })
-
- val subtitleFileSelector =
- rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
- // It lies, it can be null if file manager quits.
- if (uri == null) return@rememberLauncherForActivityResult
- val context = CloudStreamApp.context ?: return@rememberLauncherForActivityResult
-
- try {
- val localFile = File(context.filesDir, "customfont.ttf")
- localFile.delete()
- localFile.createNewFile()
- context.contentResolver?.openInputStream(uri)?.use { stream ->
- stream.copyTo(localFile.outputStream())
- }
- store.state.value = store.state.value.copy(
- font = SubtitleFont.Custom,
- typefaceFilePath = localFile.absolutePath
- )
- } catch (t: Throwable) {
- logError(t)
- }
- }
-
- return persistentListOf(
- Preference.PreferenceItem.CustomPreference(
- title = stringResource(R.string.skip_type_preview), content = {
- val state by store.state
- val text = stringResource(R.string.subtitles_example_text)
- val fixedText =
- SpannableString.valueOf(if (state.upperCase) text.uppercase() else text)
-
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .height(75.dp)
- ) {
- AsyncImage(
- contentDescription = stringResource(R.string.preview_background_img_des),
- model = R.drawable.subtitles_preview_background,
- contentScale = ContentScale.Crop,
- modifier = Modifier.fillMaxSize()
- )
- AndroidView(modifier = Modifier.fillMaxSize(), factory = { context ->
- SubtitleView(context).apply {
- setSubtitleViewStyle(this, state, false)
- this.setCues(
- listOf(
- Cue.Builder().setText(fixedText).applyStyle(state).build()
- )
- )
- }
- }, update = { view ->
- setSubtitleViewStyle(view, state, false)
- view.setCues(
- listOf(
- Cue.Builder().setText(fixedText).applyStyle(state).build()
- )
- )
- })
- }
- }),
- Preference.PreferenceGroup(
- title = stringResource(R.string.subs_font),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.ListPreference(
- preference = store.field(SaveCaptionStyle::font) { newValue -> copy(font = newValue, typefaceFilePath = null) },
- icon = painterResource(R.drawable.font_download_24px),
- title = stringResource(R.string.subs_font),
- entries = mapOf(null to stringResource(R.string.normal)) + SubtitleFont.entries.associateWith { it.label },
- onValueChanged = { value ->
- if (value?.resource == 0) {
- subtitleFileSelector.launch(
- arrayOf(
- "font/*",
- )
- )
- false
- } else {
- true
- }
- }
- ),
- Preference.PreferenceItem.ColorPreference(
- preference = store.field(SaveCaptionStyle::foregroundColor) { newValue ->
- copy(foregroundColor = newValue)
- },
- title = stringResource(R.string.subs_text_color),
- icon = painterResource(R.drawable.format_color_text_24px),
- ),
- Preference.PreferenceItem.SliderPreference(
- preference = store.field(
- get = { fixedTextSize?.toInt() ?: 25 },
- set = { newValue -> copy(fixedTextSize = newValue.toFloat()) }
- ),
- icon = painterResource(R.drawable.text_fields_24px),
- title = stringResource(R.string.subs_font_size),
- valueRange = 5..60
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = store.field(SaveCaptionStyle::upperCase) { newValue ->
- copy(upperCase = newValue)
- },
- icon = painterResource(R.drawable.uppercase_24px),
- title = stringResource(R.string.uppercase_all_subtitles),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = store.field(SaveCaptionStyle::bold) { newValue -> copy(bold = newValue) },
- icon = painterResource(R.drawable.format_bold_24px),
- title = stringResource(R.string.all_subtitles_bold),
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = store.field(SaveCaptionStyle::italic) { newValue -> copy(italic = newValue) },
- icon = painterResource(R.drawable.format_italic_24px),
- title = stringResource(R.string.all_subtitles_italic),
- ),
- )
- ),
- Preference.PreferenceGroup(
- title = stringResource(R.string.subs_edge),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.ListPreference(
- icon = painterResource(R.drawable.shadow_24px),
- title = stringResource(R.string.subs_edge_type),
- preference = store.field(SaveCaptionStyle::edgeType) { newValue ->
- copy(edgeType = newValue)
- },
- entries = persistentMapOf(
- CaptionStyleCompat.EDGE_TYPE_NONE to stringResource(R.string.subtitles_none),
- CaptionStyleCompat.EDGE_TYPE_OUTLINE to stringResource(R.string.subtitles_outline),
- CaptionStyleCompat.EDGE_TYPE_DEPRESSED to stringResource(R.string.subtitles_depressed),
- CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW to stringResource(R.string.subtitles_shadow),
- CaptionStyleCompat.EDGE_TYPE_RAISED to stringResource(R.string.subtitles_raised),
- )
- ),
- Preference.PreferenceItem.ColorPreference(
- //enabled = state.edgeType != CaptionStyleCompat.EDGE_TYPE_NONE,
- preference = store.field(SaveCaptionStyle::edgeColor) { newValue ->
- copy(edgeColor = newValue)
- },
- title = stringResource(R.string.subs_outline_color),
- icon = painterResource(R.drawable.border_color_24px),
- ),
- Preference.PreferenceItem.SliderPreference(
- preference = store.field(
- get = { edgeSize?.toInt() ?: 0 },
- set = { newValue -> copy(edgeSize = newValue.toFloat()) }
- ),
- //enabled = state.edgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE,
- icon = painterResource(R.drawable.shadow_add_24px),
- title = stringResource(R.string.subs_edge_size),
- valueRange = 0..60,
- )
- )
- ),
-
- Preference.PreferenceGroup(
- title = stringResource(R.string.background),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.ColorPreference(
- preference = store.field(SaveCaptionStyle::windowColor) { newValue ->
- copy(windowColor = newValue)
- },
- title = stringResource(R.string.subs_window_color),
- icon = painterResource(R.drawable.imagesearch_roller_24px),
- ),
- Preference.PreferenceItem.ColorPreference(
- preference = store.field(SaveCaptionStyle::backgroundColor) { newValue ->
- copy(backgroundColor = newValue)
- },
- title = stringResource(R.string.subs_background_color),
- icon = painterResource(R.drawable.format_color_fill_24px),
- ),
- Preference.PreferenceItem.SliderPreference(
- preference = store.field(
- get = { backgroundRadius?.toInt() ?: 0 },
- set = { newValue -> copy(backgroundRadius = newValue.toFloat()) }
- ),
- icon = painterResource(R.drawable.rounded_corner_24px),
- title = stringResource(R.string.background_radius),
- valueRange = 0..50,
- steps = 9,
- )
- )
- ),
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_player_layout),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.ListPreference(
- preference = store.field(SaveCaptionStyle::alignment) { newValue ->
- copy(alignment = newValue)
- },
-
- icon = painterResource(R.drawable.format_align_center_24px),
- title = stringResource(R.string.subs_subtitle_alignment),
- entries = persistentMapOf(
- null to stringResource(R.string.automatic),
- CustomDecoder.SSA_ALIGNMENT_BOTTOM_LEFT to stringResource(R.string.bottom_left),
- CustomDecoder.SSA_ALIGNMENT_BOTTOM_CENTER to stringResource(R.string.bottom_center),
- CustomDecoder.SSA_ALIGNMENT_BOTTOM_RIGHT to stringResource(R.string.bottom_right),
- CustomDecoder.SSA_ALIGNMENT_MIDDLE_LEFT to stringResource(R.string.middle_left),
- CustomDecoder.SSA_ALIGNMENT_MIDDLE_CENTER to stringResource(R.string.middle_center),
- CustomDecoder.SSA_ALIGNMENT_MIDDLE_RIGHT to stringResource(R.string.middle_right),
- CustomDecoder.SSA_ALIGNMENT_TOP_LEFT to stringResource(R.string.top_left),
- CustomDecoder.SSA_ALIGNMENT_TOP_CENTER to stringResource(R.string.top_center),
- CustomDecoder.SSA_ALIGNMENT_TOP_RIGHT to stringResource(R.string.top_right),
- )
- ),
- Preference.PreferenceItem.SliderPreference(
- preference = store.field(SaveCaptionStyle::elevation) { newValue ->
- copy(elevation = newValue)
- },
- title = stringResource(R.string.subs_subtitle_elevation),
- icon = painterResource(R.drawable.text_select_move_up_24px),
- valueRange = 0..400,
- steps = 39
- ),
- )
- ),
-
- Preference.PreferenceGroup(
- title = stringResource(R.string.extension_language),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.ListPreference(
- icon = painterResource(R.drawable.language_korean_latin_24px),
- title = stringResource(R.string.subs_auto_select_language),
- preference = autoSelectSubtitles,
- entries = mapOf(
- "None" to stringResource(R.string.none)
- ) + sortedLanguages,
- ),
- Preference.PreferenceItem.MultiSelectListPreference(
- icon = painterResource(R.drawable.language_download2),
- title = stringResource(R.string.subs_download_languages),
- preference = downloadSubsLanguage,
- entries = mapOf(
- "None" to stringResource(R.string.none)
- ) + sortedLanguages,
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = subtitlesFilterSubLang,
- icon = painterResource(R.drawable.filter_alt_24px),
- title = stringResource(R.string.subtitles_filter_lang),
- ),
- )
- ),
- Preference.PreferenceGroup(
- title = stringResource(R.string.pref_category_ui_features),
- preferenceItems = persistentListOf(
- Preference.PreferenceItem.SwitchPreference(
- preference = store.field(SaveCaptionStyle::removeCaptions) { newValue ->
- copy(removeCaptions = newValue)
- },
- icon = painterResource(R.drawable.closed_caption_24px),
- title = stringResource(R.string.subtitles_remove_captions),
- subtitle = "[Knocking on door] Hello → Hello",
- ),
- Preference.PreferenceItem.SwitchPreference(
- preference = store.field(SaveCaptionStyle::removeBloat) { newValue ->
- copy(removeBloat = newValue)
- },
- icon = painterResource(R.drawable.text_ad_off_24px),
- title = stringResource(R.string.subtitles_remove_bloat),
- ),
- )
- ),
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt
index e0661721a..8d322fd04 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt
@@ -86,7 +86,6 @@ import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.toClassDir
import com.lagradost.cloudstream3.utils.JsUnpacker.Companion.load
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.downloader.DownloadObjects
-import com.lagradost.cloudstream4.AppSettings
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import okhttp3.Cache
@@ -482,14 +481,14 @@ object AppContextUtils {
fun Context.filterSearchResultByFilmQuality(data: List): List {
// Filter results omitting entries with certain quality
if (data.isNotEmpty()) {
- val filteredSearchQuality = AppSettings(this).ui.filterQuality.get() /*PreferenceManager.getDefaultSharedPreferences(this)
+ val filteredSearchQuality = PreferenceManager.getDefaultSharedPreferences(this)
?.getStringSet(getString(R.string.pref_filter_search_quality_key), setOf())
?.mapNotNull { entry ->
entry.toIntOrNull() ?: return@mapNotNull null
- } ?: listOf()*/
+ } ?: listOf()
if (filteredSearchQuality.isNotEmpty()) {
return data.filter { item ->
- val searchQualVal = item.quality //?.ordinal ?: -1
+ val searchQualVal = item.quality?.ordinal ?: -1
//Log.i("filterSearch", "QuickSearch item => ${item.toJson()}")
!filteredSearchQuality.contains(searchQualVal)
}
@@ -501,17 +500,17 @@ object AppContextUtils {
fun Context.filterHomePageListByFilmQuality(data: HomePageList): HomePageList {
// Filter results omitting entries with certain quality
if (data.list.isNotEmpty()) {
- val filteredSearchQuality = AppSettings(this).ui.filterQuality.get() /*PreferenceManager.getDefaultSharedPreferences(this)
+ val filteredSearchQuality = PreferenceManager.getDefaultSharedPreferences(this)
?.getStringSet(getString(R.string.pref_filter_search_quality_key), setOf())
?.mapNotNull { entry ->
entry.toIntOrNull() ?: return@mapNotNull null
- } ?: listOf()*/
+ } ?: listOf()
if (filteredSearchQuality.isNotEmpty()) {
return HomePageList(
name = data.name,
isHorizontalImages = data.isHorizontalImages,
list = data.list.filter { item ->
- val searchQualVal = item.quality //?.ordinal ?: -1
+ val searchQualVal = item.quality?.ordinal ?: -1
//Log.i("filterSearch", "QuickSearch item => ${item.toJson()}")
!filteredSearchQuality.contains(searchQualVal)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt
index d0c79c43c..0a561b471 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt
@@ -1,6 +1,5 @@
package com.lagradost.cloudstream3.utils
-import android.app.Activity
import android.content.Context
import android.net.Uri
import android.widget.Toast
@@ -283,7 +282,7 @@ object BackupUtils {
}
}
- fun Activity.restorePrompt() {
+ fun FragmentActivity.restorePrompt() {
runOnUiThread {
try {
restoreFileSelector?.launch(
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BaseComposeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BaseComposeFragment.kt
deleted file mode 100644
index b11b03754..000000000
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/BaseComposeFragment.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.lagradost.cloudstream3.utils
-
-import android.content.res.Configuration
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.DialogFragment
-import androidx.fragment.app.Fragment
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
-import com.lagradost.cloudstream3.ui.settings.Globals.TV
-import com.lagradost.cloudstream3.ui.settings.Globals.isLandscape
-import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
-import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding
-import com.lagradost.cloudstream4.compose.Screen
-import com.lagradost.cloudstream4.compose.createComposeView
-
-abstract class BaseComposeFragment : Fragment(), Screen {
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View = createComposeView(inflater, container, savedInstanceState)
-
- override fun onConfigurationChanged(newConfig: Configuration) {
- super.onConfigurationChanged(newConfig)
- this.view?.let { view ->
- fixSystemBarsPadding(
- view,
- padLeft = isLayout(TV or EMULATOR),
- padBottom = isLandscape()
- )
- }
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- fixSystemBarsPadding(
- view,
- padLeft = isLayout(TV or EMULATOR),
- padBottom = isLandscape()
- )
- }
-}
-
-abstract class BaseDialogComposeFragment : DialogFragment(), Screen {
- var systemBarsAddPadding = isLayout(TV or EMULATOR)
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View = createComposeView(inflater, container, savedInstanceState)
-
- override fun onConfigurationChanged(newConfig: Configuration) {
- super.onConfigurationChanged(newConfig)
- dialog?.window?.decorView?.let { view ->
- fixSystemBarsPadding(
- view,
- padLeft = systemBarsAddPadding,
- padBottom = systemBarsAddPadding || isLandscape()
- )
- }
- }
-
- override fun onStart() {
- super.onStart()
- dialog?.window?.setWindowAnimations(R.style.DialogFullscreenPlayer)
- }
-
- override fun getTheme(): Int {
- return R.style.DialogFullscreenPlayer
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- dialog?.window?.decorView?.let { view ->
- fixSystemBarsPadding(
- view,
- padLeft = systemBarsAddPadding,
- padBottom = systemBarsAddPadding || isLandscape()
- )
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt
index 68f262d8d..02ee69791 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt
@@ -181,11 +181,11 @@ object DataStore {
}
fun Context.getKey(path: String, valueType: Class): T? {
- return try {
+ try {
val json: String = getSharedPrefs().getString(path, null) ?: return null
- parseJson(json, valueType.kotlin)
- } catch (_: Exception) {
- null
+ return parseJson(json, valueType.kotlin)
+ } catch (e: Exception) {
+ return null
}
}
@@ -193,37 +193,21 @@ object DataStore {
setKey(getFolderName(folder, path), value)
}
- @Deprecated(
- message = "Use parseJson(this) directly instead.",
- level = DeprecationLevel.WARNING,
- replaceWith = ReplaceWith(
- expression = "parseJson(this)",
- imports = ["com.lagradost.cloudstream3.utils.AppUtils.parseJson"],
- ),
- )
inline fun String.toKotlinObject(): T {
return parseJson(this)
}
- @Deprecated(
- message = "Use parseJson(this) directly instead.",
- level = DeprecationLevel.WARNING,
- replaceWith = ReplaceWith(
- expression = "parseJson(this)",
- imports = ["com.lagradost.cloudstream3.utils.AppUtils.parseJson"],
- ),
- )
fun String.toKotlinObject(valueType: Class): T {
return parseJson(this, valueType.kotlin)
}
// GET KEY GIVEN PATH AND DEFAULT VALUE, NULL IF ERROR
inline fun Context.getKey(path: String, defVal: T?): T? {
- return try {
+ try {
val json: String = getSharedPrefs().getString(path, null) ?: return defVal
- parseJson(json)
- } catch (_: Exception) {
- null
+ return json.toKotlinObject()
+ } catch (e: Exception) {
+ return null
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt
index eabac20d3..340266f6c 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt
@@ -33,8 +33,6 @@ import com.lagradost.cloudstream3.ui.result.VideoWatchState
import com.lagradost.cloudstream3.utils.AppContextUtils.filterProviderByPreferredMedia
import com.lagradost.cloudstream3.utils.downloader.DownloadObjects
import com.lagradost.cloudstream3.utils.serializers.WriteOnlySerializer
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KeepGeneratedSerializer
import kotlinx.serialization.SerialName
@@ -182,10 +180,6 @@ object DataStoreHelper {
var selectedKeyIndex by PreferenceDelegate("$TAG/account_key_index", 0)
val currentAccount: String get() = selectedKeyIndex.toString()
- private val _selectedAccountNumberFlow = MutableStateFlow(0)
- /** What account instance we are on, this number changes whenever anything about local accounts changes */
- val selectedAccountNumberFlow : StateFlow = _selectedAccountNumberFlow
-
/**
* Get or set the current account homepage.
* Setting this does not automatically reload the homepage.
@@ -213,7 +207,6 @@ object DataStoreHelper {
// This is not a new account, and the homepage has changed, reload it
MainActivity.reloadHomeEvent(true)
}
- _selectedAccountNumberFlow.value += 1
}
fun getDefaultAccount(context: Context): Account {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt
index af3795834..e3c7d68df 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt
@@ -63,7 +63,7 @@ object BatteryOptimizationChecker {
return isRestricted && isOptimizedNotShown && isLayout(PHONE)
}
- fun Context.showRequestIgnoreBatteryOptDialog() {
+ private fun Context.showRequestIgnoreBatteryOptDialog() {
try {
val intent = Intent().apply {
action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt
index b5b367262..4c0717b3f 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt
@@ -149,12 +149,10 @@ object TvChannelUtils {
.setInputId(inputId)
.build()
- val channelUri = runCatching {
- context.contentResolver.insert(
- TvContractCompat.Channels.CONTENT_URI,
- channel.toContentValues()
- )
- }.getOrNull()
+ val channelUri = context.contentResolver.insert(
+ TvContractCompat.Channels.CONTENT_URI,
+ channel.toContentValues()
+ )
channelUri?.let {
val channelId = ContentUris.parseId(it)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/videoskip/AnimeSkip.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/videoskip/AnimeSkip.kt
index 687f746e3..df9d56217 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/videoskip/AnimeSkip.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/videoskip/AnimeSkip.kt
@@ -4,7 +4,6 @@ import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.AnimeLoadResponse
import com.lagradost.cloudstream3.ErrorLoadingException
import com.lagradost.cloudstream3.LoadResponse
-import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.TvSeriesLoadResponse
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.app
@@ -26,7 +25,6 @@ import java.security.MessageDigest
class AnimeSkipAuth : AuthAPI() {
override val name = "AnimeSkip"
- override val icon: Int = R.drawable.animeskip
override val inAppLoginRequirement: AuthLoginRequirement =
AuthLoginRequirement(password = true, username = true)
override val idPrefix = "anime-skip"
diff --git a/app/src/main/res/color/account_edit_btn_tint.xml b/app/src/main/res/color/account_edit_btn_tint.xml
deleted file mode 100644
index 7205c77fc..000000000
--- a/app/src/main/res/color/account_edit_btn_tint.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/article_24px.xml b/app/src/main/res/drawable/article_24px.xml
deleted file mode 100644
index 5b0f45801..000000000
--- a/app/src/main/res/drawable/article_24px.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/aspect_ratio_24px.xml b/app/src/main/res/drawable/aspect_ratio_24px.xml
deleted file mode 100644
index 429ed7237..000000000
--- a/app/src/main/res/drawable/aspect_ratio_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/audio_capture_24px.xml b/app/src/main/res/drawable/audio_capture_24px.xml
deleted file mode 100644
index a9ed40e02..000000000
--- a/app/src/main/res/drawable/audio_capture_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/autorenew_24px.xml b/app/src/main/res/drawable/autorenew_24px.xml
deleted file mode 100644
index 4ac69c08f..000000000
--- a/app/src/main/res/drawable/autorenew_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/battery_alert_24px.xml b/app/src/main/res/drawable/battery_alert_24px.xml
deleted file mode 100644
index 3675de44d..000000000
--- a/app/src/main/res/drawable/battery_alert_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/border_color_24px.xml b/app/src/main/res/drawable/border_color_24px.xml
deleted file mode 100644
index f4a5bbf3b..000000000
--- a/app/src/main/res/drawable/border_color_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/build_24px.xml b/app/src/main/res/drawable/build_24px.xml
deleted file mode 100644
index 0616691da..000000000
--- a/app/src/main/res/drawable/build_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/cast.xml b/app/src/main/res/drawable/cast.xml
deleted file mode 100644
index 7600cb706..000000000
--- a/app/src/main/res/drawable/cast.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/close_24px.xml b/app/src/main/res/drawable/close_24px.xml
deleted file mode 100644
index 77be1d01e..000000000
--- a/app/src/main/res/drawable/close_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/closed_caption_24px.xml b/app/src/main/res/drawable/closed_caption_24px.xml
deleted file mode 100644
index 793e1e061..000000000
--- a/app/src/main/res/drawable/closed_caption_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/colors_24px.xml b/app/src/main/res/drawable/colors_24px.xml
deleted file mode 100644
index d86304cf9..000000000
--- a/app/src/main/res/drawable/colors_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/construction_24px.xml b/app/src/main/res/drawable/construction_24px.xml
deleted file mode 100644
index 00c976c90..000000000
--- a/app/src/main/res/drawable/construction_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/copy_all_24px.xml b/app/src/main/res/drawable/copy_all_24px.xml
deleted file mode 100644
index 8e4db679b..000000000
--- a/app/src/main/res/drawable/copy_all_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/delete_24px.xml b/app/src/main/res/drawable/delete_24px.xml
deleted file mode 100644
index 237e75aa6..000000000
--- a/app/src/main/res/drawable/delete_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/description_24px.xml b/app/src/main/res/drawable/description_24px.xml
deleted file mode 100644
index f55b9ad85..000000000
--- a/app/src/main/res/drawable/description_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/dns_24px.xml b/app/src/main/res/drawable/dns_24px.xml
deleted file mode 100644
index 2f9b4c8bf..000000000
--- a/app/src/main/res/drawable/dns_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/encrypted_24px.xml b/app/src/main/res/drawable/encrypted_24px.xml
deleted file mode 100644
index 66231650a..000000000
--- a/app/src/main/res/drawable/encrypted_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/extension_24px.xml b/app/src/main/res/drawable/extension_24px.xml
deleted file mode 100644
index ce2cfc260..000000000
--- a/app/src/main/res/drawable/extension_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/extention_download.xml b/app/src/main/res/drawable/extention_download.xml
deleted file mode 100644
index 97f3dbd67..000000000
--- a/app/src/main/res/drawable/extention_download.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/extention_renew.xml b/app/src/main/res/drawable/extention_renew.xml
deleted file mode 100644
index a9e6e07f9..000000000
--- a/app/src/main/res/drawable/extention_renew.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/extention_renew2.xml b/app/src/main/res/drawable/extention_renew2.xml
deleted file mode 100644
index d34e91355..000000000
--- a/app/src/main/res/drawable/extention_renew2.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/face_24px.xml b/app/src/main/res/drawable/face_24px.xml
deleted file mode 100644
index 152c278e7..000000000
--- a/app/src/main/res/drawable/face_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/filter_alt_24px.xml b/app/src/main/res/drawable/filter_alt_24px.xml
deleted file mode 100644
index 0afa9b51d..000000000
--- a/app/src/main/res/drawable/filter_alt_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/folder_24px.xml b/app/src/main/res/drawable/folder_24px.xml
deleted file mode 100644
index a1e978b69..000000000
--- a/app/src/main/res/drawable/folder_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/font_download_24px.xml b/app/src/main/res/drawable/font_download_24px.xml
deleted file mode 100644
index 82f04c483..000000000
--- a/app/src/main/res/drawable/font_download_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/format_align_center_24px.xml b/app/src/main/res/drawable/format_align_center_24px.xml
deleted file mode 100644
index 8a425daa4..000000000
--- a/app/src/main/res/drawable/format_align_center_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/format_bold_24px.xml b/app/src/main/res/drawable/format_bold_24px.xml
deleted file mode 100644
index 61aa7f509..000000000
--- a/app/src/main/res/drawable/format_bold_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/format_color_fill_24px.xml b/app/src/main/res/drawable/format_color_fill_24px.xml
deleted file mode 100644
index 3dc2047e9..000000000
--- a/app/src/main/res/drawable/format_color_fill_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/format_color_text_24px.xml b/app/src/main/res/drawable/format_color_text_24px.xml
deleted file mode 100644
index e1d88be33..000000000
--- a/app/src/main/res/drawable/format_color_text_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/format_italic_24px.xml b/app/src/main/res/drawable/format_italic_24px.xml
deleted file mode 100644
index 0f69910e8..000000000
--- a/app/src/main/res/drawable/format_italic_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/format_paint_24px.xml b/app/src/main/res/drawable/format_paint_24px.xml
deleted file mode 100644
index b27cf246e..000000000
--- a/app/src/main/res/drawable/format_paint_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/hard_drive_24px.xml b/app/src/main/res/drawable/hard_drive_24px.xml
deleted file mode 100644
index 45fecb4d2..000000000
--- a/app/src/main/res/drawable/hard_drive_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/high_res_24px.xml b/app/src/main/res/drawable/high_res_24px.xml
deleted file mode 100644
index 183f2ae69..000000000
--- a/app/src/main/res/drawable/high_res_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/history_toggle_off_24px.xml b/app/src/main/res/drawable/history_toggle_off_24px.xml
deleted file mode 100644
index 30186016c..000000000
--- a/app/src/main/res/drawable/history_toggle_off_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/ic_baseline_clock_24.xml b/app/src/main/res/drawable/ic_baseline_clock_24.xml
deleted file mode 100644
index 3436c6208..000000000
--- a/app/src/main/res/drawable/ic_baseline_clock_24.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/imagesearch_roller_24px.xml b/app/src/main/res/drawable/imagesearch_roller_24px.xml
deleted file mode 100644
index 184f6f60a..000000000
--- a/app/src/main/res/drawable/imagesearch_roller_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/keyboard_arrow_left_24px.xml b/app/src/main/res/drawable/keyboard_arrow_left_24px.xml
deleted file mode 100644
index b4b89a922..000000000
--- a/app/src/main/res/drawable/keyboard_arrow_left_24px.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/keyboard_double_arrow_right_24px.xml b/app/src/main/res/drawable/keyboard_double_arrow_right_24px.xml
deleted file mode 100644
index 2e564cea4..000000000
--- a/app/src/main/res/drawable/keyboard_double_arrow_right_24px.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/label_24px.xml b/app/src/main/res/drawable/label_24px.xml
deleted file mode 100644
index fb1d1b506..000000000
--- a/app/src/main/res/drawable/label_24px.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/language_download.xml b/app/src/main/res/drawable/language_download.xml
deleted file mode 100644
index 129dd4eea..000000000
--- a/app/src/main/res/drawable/language_download.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/language_download2.xml b/app/src/main/res/drawable/language_download2.xml
deleted file mode 100644
index 30cb8bf13..000000000
--- a/app/src/main/res/drawable/language_download2.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/language_korean_latin_24px.xml b/app/src/main/res/drawable/language_korean_latin_24px.xml
deleted file mode 100644
index 55bf0cf21..000000000
--- a/app/src/main/res/drawable/language_korean_latin_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/light_mode_24px.xml b/app/src/main/res/drawable/light_mode_24px.xml
deleted file mode 100644
index 8d278ec46..000000000
--- a/app/src/main/res/drawable/light_mode_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/lock_icon_bg.xml b/app/src/main/res/drawable/lock_icon_bg.xml
deleted file mode 100644
index 7d5a233e7..000000000
--- a/app/src/main/res/drawable/lock_icon_bg.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/match_word_24px.xml b/app/src/main/res/drawable/match_word_24px.xml
deleted file mode 100644
index d2a7334f8..000000000
--- a/app/src/main/res/drawable/match_word_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/memory_24px.xml b/app/src/main/res/drawable/memory_24px.xml
deleted file mode 100644
index 7e4dd5e9b..000000000
--- a/app/src/main/res/drawable/memory_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/memory_alt_24px.xml b/app/src/main/res/drawable/memory_alt_24px.xml
deleted file mode 100644
index 21807a9e9..000000000
--- a/app/src/main/res/drawable/memory_alt_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/mobile_arrow_down_24px.xml b/app/src/main/res/drawable/mobile_arrow_down_24px.xml
deleted file mode 100644
index caab6850e..000000000
--- a/app/src/main/res/drawable/mobile_arrow_down_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/mobile_code_24px.xml b/app/src/main/res/drawable/mobile_code_24px.xml
deleted file mode 100644
index 9b78af094..000000000
--- a/app/src/main/res/drawable/mobile_code_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/mobile_wrench_24px.xml b/app/src/main/res/drawable/mobile_wrench_24px.xml
deleted file mode 100644
index 5cc975528..000000000
--- a/app/src/main/res/drawable/mobile_wrench_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/movie_edit_24px.xml b/app/src/main/res/drawable/movie_edit_24px.xml
deleted file mode 100644
index 41df964e6..000000000
--- a/app/src/main/res/drawable/movie_edit_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/movie_info_24px.xml b/app/src/main/res/drawable/movie_info_24px.xml
deleted file mode 100644
index 7d7dfe9e9..000000000
--- a/app/src/main/res/drawable/movie_info_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/notifications_active_24px.xml b/app/src/main/res/drawable/notifications_active_24px.xml
deleted file mode 100644
index b3982c522..000000000
--- a/app/src/main/res/drawable/notifications_active_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/outline_drawable_forced_round.xml b/app/src/main/res/drawable/outline_drawable_forced_round.xml
index e028ef10a..7736f088a 100644
--- a/app/src/main/res/drawable/outline_drawable_forced_round.xml
+++ b/app/src/main/res/drawable/outline_drawable_forced_round.xml
@@ -1,8 +1,5 @@
-
-
- -
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/oval_stroke_focus.xml b/app/src/main/res/drawable/oval_stroke_focus.xml
deleted file mode 100644
index a2af717f1..000000000
--- a/app/src/main/res/drawable/oval_stroke_focus.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/palette_24px.xml b/app/src/main/res/drawable/palette_24px.xml
deleted file mode 100644
index f701027cc..000000000
--- a/app/src/main/res/drawable/palette_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/pause_24px.xml b/app/src/main/res/drawable/pause_24px.xml
deleted file mode 100644
index e8de0339c..000000000
--- a/app/src/main/res/drawable/pause_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/picture_in_picture_center_24px.xml b/app/src/main/res/drawable/picture_in_picture_center_24px.xml
deleted file mode 100644
index 60d7e133b..000000000
--- a/app/src/main/res/drawable/picture_in_picture_center_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/pip_24px.xml b/app/src/main/res/drawable/pip_24px.xml
deleted file mode 100644
index 5fba5b85e..000000000
--- a/app/src/main/res/drawable/pip_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/play_arrow_24px.xml b/app/src/main/res/drawable/play_arrow_24px.xml
deleted file mode 100644
index b7b67fccc..000000000
--- a/app/src/main/res/drawable/play_arrow_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/plugin_lang.xml b/app/src/main/res/drawable/plugin_lang.xml
deleted file mode 100644
index ac33489ac..000000000
--- a/app/src/main/res/drawable/plugin_lang.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/quick_novel_icon.xml b/app/src/main/res/drawable/quick_novel_icon.xml
index 8f0b3cf3e..b19aaafea 100644
--- a/app/src/main/res/drawable/quick_novel_icon.xml
+++ b/app/src/main/res/drawable/quick_novel_icon.xml
@@ -1,23 +1,31 @@
-
-
-
-
-
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108"
+ android:name="vector"
+ android:tint="?attr/white"
+>
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/reset_colors_24px.xml b/app/src/main/res/drawable/reset_colors_24px.xml
deleted file mode 100644
index 6cfa81413..000000000
--- a/app/src/main/res/drawable/reset_colors_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/responsive_layout_24px.xml b/app/src/main/res/drawable/responsive_layout_24px.xml
deleted file mode 100644
index 4d3ea085f..000000000
--- a/app/src/main/res/drawable/responsive_layout_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/restore_page_24px.xml b/app/src/main/res/drawable/restore_page_24px.xml
deleted file mode 100644
index 65a7d274f..000000000
--- a/app/src/main/res/drawable/restore_page_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/rounded_corner_24px.xml b/app/src/main/res/drawable/rounded_corner_24px.xml
deleted file mode 100644
index 6d3180c42..000000000
--- a/app/src/main/res/drawable/rounded_corner_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/rounded_outline.xml b/app/src/main/res/drawable/rounded_outline.xml
index 752cd7823..b85ace8ea 100644
--- a/app/src/main/res/drawable/rounded_outline.xml
+++ b/app/src/main/res/drawable/rounded_outline.xml
@@ -1,11 +1,13 @@
-
-
-
-
+
+
-
-
+
+
-
+
diff --git a/app/src/main/res/drawable/save_as_24px.xml b/app/src/main/res/drawable/save_as_24px.xml
deleted file mode 100644
index 0c3c01414..000000000
--- a/app/src/main/res/drawable/save_as_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/save_clock_24px.xml b/app/src/main/res/drawable/save_clock_24px.xml
deleted file mode 100644
index c86924cc1..000000000
--- a/app/src/main/res/drawable/save_clock_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/screen_rotation_alt_24px.xml b/app/src/main/res/drawable/screen_rotation_alt_24px.xml
deleted file mode 100644
index a1d01a5c6..000000000
--- a/app/src/main/res/drawable/screen_rotation_alt_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/shadow_24px.xml b/app/src/main/res/drawable/shadow_24px.xml
deleted file mode 100644
index 09bf8d62e..000000000
--- a/app/src/main/res/drawable/shadow_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/shadow_add_24px.xml b/app/src/main/res/drawable/shadow_add_24px.xml
deleted file mode 100644
index 9ff30d54c..000000000
--- a/app/src/main/res/drawable/shadow_add_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/shuffle_24px.xml b/app/src/main/res/drawable/shuffle_24px.xml
deleted file mode 100644
index d25985f8b..000000000
--- a/app/src/main/res/drawable/shuffle_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/skip_next_24px.xml b/app/src/main/res/drawable/skip_next_24px.xml
deleted file mode 100644
index 3beb9c760..000000000
--- a/app/src/main/res/drawable/skip_next_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/subtitles_gear_24px.xml b/app/src/main/res/drawable/subtitles_gear_24px.xml
deleted file mode 100644
index d84e9f883..000000000
--- a/app/src/main/res/drawable/subtitles_gear_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/swipe_24px.xml b/app/src/main/res/drawable/swipe_24px.xml
deleted file mode 100644
index 9b2183713..000000000
--- a/app/src/main/res/drawable/swipe_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/swipe_vertical_24px.xml b/app/src/main/res/drawable/swipe_vertical_24px.xml
deleted file mode 100644
index 48048f334..000000000
--- a/app/src/main/res/drawable/swipe_vertical_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/text_ad_off_24px.xml b/app/src/main/res/drawable/text_ad_off_24px.xml
deleted file mode 100644
index 2a108446b..000000000
--- a/app/src/main/res/drawable/text_ad_off_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/text_fields_24px.xml b/app/src/main/res/drawable/text_fields_24px.xml
deleted file mode 100644
index 49153d403..000000000
--- a/app/src/main/res/drawable/text_fields_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/text_select_move_up_24px.xml b/app/src/main/res/drawable/text_select_move_up_24px.xml
deleted file mode 100644
index 7311798a9..000000000
--- a/app/src/main/res/drawable/text_select_move_up_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/tooltip_24px.xml b/app/src/main/res/drawable/tooltip_24px.xml
deleted file mode 100644
index 5770d07b4..000000000
--- a/app/src/main/res/drawable/tooltip_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/touch_double_24px.xml b/app/src/main/res/drawable/touch_double_24px.xml
deleted file mode 100644
index f008bd374..000000000
--- a/app/src/main/res/drawable/touch_double_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/undo_24px.xml b/app/src/main/res/drawable/undo_24px.xml
deleted file mode 100644
index 89a3f5b80..000000000
--- a/app/src/main/res/drawable/undo_24px.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/uppercase_24px.xml b/app/src/main/res/drawable/uppercase_24px.xml
deleted file mode 100644
index 68b9166ae..000000000
--- a/app/src/main/res/drawable/uppercase_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/visibility_off_24px.xml b/app/src/main/res/drawable/visibility_off_24px.xml
deleted file mode 100644
index 50ba19a57..000000000
--- a/app/src/main/res/drawable/visibility_off_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/drawable/wifi_proxy_24px.xml b/app/src/main/res/drawable/wifi_proxy_24px.xml
deleted file mode 100644
index 92798c29c..000000000
--- a/app/src/main/res/drawable/wifi_proxy_24px.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/app/src/main/res/layout-port/player_select_source_priority.xml b/app/src/main/res/layout-port/player_select_source_priority.xml
index 926227c58..2cba9c869 100644
--- a/app/src/main/res/layout-port/player_select_source_priority.xml
+++ b/app/src/main/res/layout-port/player_select_source_priority.xml
@@ -1,5 +1,6 @@
-
-
+
@@ -32,21 +33,14 @@
android:textSize="20sp"
android:textStyle="bold" />
-
-
+ android:src="@drawable/baseline_help_outline_24"
+ android:contentDescription="@string/help" />
-
+
+ tools:text="@string/profile_number"
+ tools:ignore="LabelFor" />
+ android:text="@string/sort_save"
+ style="@style/WhiteButton" />
+ android:text="@string/sort_close"
+ style="@style/BlackButton" />
diff --git a/app/src/main/res/layout/account_edit_dialog.xml b/app/src/main/res/layout/account_edit_dialog.xml
index ae91c16a5..f52c8ea51 100644
--- a/app/src/main/res/layout/account_edit_dialog.xml
+++ b/app/src/main/res/layout/account_edit_dialog.xml
@@ -37,58 +37,32 @@
android:layout_marginBottom="60dp"
android:orientation="vertical">
-
+
-
+ android:scaleType="centerCrop"
+ android:src="@drawable/profile_bg_blue" />
-
-
-
-
-
-
-
-
-
-
+ android:background="@color/skipOpTransparent"
+ android:padding="5dp"
+ android:src="@drawable/ic_baseline_edit_24" />
+
@@ -108,11 +83,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:buttonTint="?attr/textColor"
- android:nextFocusUp="@id/account_name"
- android:nextFocusDown="@id/apply_btt"
android:text="@string/lock_profile"
android:textColor="?attr/grayTextColor" />
+
@@ -140,8 +113,6 @@
android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end"
android:layout_toStartOf="@+id/cancel_btt"
- android:nextFocusUp="@id/lockProfileCheckbox"
- android:nextFocusDown="@id/cancel_btt"
android:nextFocusLeft="@id/delete_btt"
android:nextFocusRight="@id/cancel_btt"
android:text="@string/sort_apply" />
@@ -152,8 +123,6 @@
android:layout_width="wrap_content"
android:layout_alignParentEnd="true"
android:layout_gravity="center_vertical|end"
- android:nextFocusUp="@id/apply_btt"
- android:nextFocusDown="@id/apply_btt"
android:nextFocusLeft="@id/apply_btt"
android:text="@string/sort_cancel" />
diff --git a/app/src/main/res/layout/account_list_item.xml b/app/src/main/res/layout/account_list_item.xml
index 9b3cef349..3cbfc72fb 100644
--- a/app/src/main/res/layout/account_list_item.xml
+++ b/app/src/main/res/layout/account_list_item.xml
@@ -1,70 +1,59 @@
-
+ android:foreground="?attr/selectableItemBackground"
+ app:cardCornerRadius="@dimen/rounded_image_radius"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintDimensionRatio="1"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHeight_percent="0.4"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
-
+
+
+ android:background="@drawable/outline_card"
+ android:visibility="gone"
+ tools:visibility="visible" />
-
-
-
-
-
-
-
-
-
+
+ android:padding="10dp"
+ android:textColor="@color/textColor"
+ android:textSize="16sp"
+ tools:text="Hello World!" />
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/account_list_item_add.xml b/app/src/main/res/layout/account_list_item_add.xml
index 7f91f6580..dea64484f 100644
--- a/app/src/main/res/layout/account_list_item_add.xml
+++ b/app/src/main/res/layout/account_list_item_add.xml
@@ -1,51 +1,29 @@
-
+ app:cardCornerRadius="@dimen/rounded_image_radius"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintDimensionRatio="1"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHeight_percent="0.4"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
-
+
-
-
-
-
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/account_list_item_edit.xml b/app/src/main/res/layout/account_list_item_edit.xml
index d1dbef2ef..3f41a23c2 100644
--- a/app/src/main/res/layout/account_list_item_edit.xml
+++ b/app/src/main/res/layout/account_list_item_edit.xml
@@ -1,79 +1,67 @@
-
+ android:foreground="?attr/selectableItemBackground"
+ app:cardCornerRadius="@dimen/rounded_image_radius"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintDimensionRatio="1"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHeight_percent="0.4"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
-
+
+
+ android:background="@drawable/outline_card"
+ android:visibility="gone"
+ tools:visibility="visible" />
-
+
-
-
-
-
-
-
-
-
-
+
+ android:padding="10dp"
+ android:textColor="@color/textColor"
+ android:textSize="16sp"
+ tools:text="Hello World!" />
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/account_select_linear.xml b/app/src/main/res/layout/account_select_linear.xml
index 65c2ffba9..b78c0d44c 100644
--- a/app/src/main/res/layout/account_select_linear.xml
+++ b/app/src/main/res/layout/account_select_linear.xml
@@ -26,8 +26,6 @@
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:clipChildren="false"
- android:clipToPadding="false"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="4"
tools:listitem="@layout/account_list_item" />
diff --git a/app/src/main/res/layout/activity_account_select.xml b/app/src/main/res/layout/activity_account_select.xml
index 444a04a18..9f62d5601 100644
--- a/app/src/main/res/layout/activity_account_select.xml
+++ b/app/src/main/res/layout/activity_account_select.xml
@@ -18,7 +18,7 @@
android:nextFocusDown="@id/account_recycler_view"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/manage_accounts"
- app:tint="@color/account_edit_btn_tint" />
+ app:tint="@color/player_on_button_tv_attr" />
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index eb7c67145..2483a3714 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -82,9 +82,4 @@
tools:ignore="FragmentTagUsage" />
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main_tv.xml b/app/src/main/res/layout/activity_main_tv.xml
index 0d81285a6..c51d77d16 100644
--- a/app/src/main/res/layout/activity_main_tv.xml
+++ b/app/src/main/res/layout/activity_main_tv.xml
@@ -93,11 +93,6 @@
-
-
diff --git a/app/src/main/res/layout/bottom_input_dialog.xml b/app/src/main/res/layout/bottom_input_dialog.xml
index 63f5e876d..bfb728052 100644
--- a/app/src/main/res/layout/bottom_input_dialog.xml
+++ b/app/src/main/res/layout/bottom_input_dialog.xml
@@ -4,7 +4,7 @@
android:nextFocusDown="@id/nginx_text_input"
android:orientation="vertical"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="match_parent">
@@ -51,8 +50,6 @@
android:layout_gravity="center_vertical|end"
android:text="@string/sort_apply"
android:id="@+id/apply_btt"
- android:nextFocusUp="@id/nginx_text_input"
- android:nextFocusRight="@id/cancel_btt"
style="@style/WhiteButton" />
diff --git a/app/src/main/res/layout/bottom_selection_dialog.xml b/app/src/main/res/layout/bottom_selection_dialog.xml
index 78916970b..55ca6562e 100644
--- a/app/src/main/res/layout/bottom_selection_dialog.xml
+++ b/app/src/main/res/layout/bottom_selection_dialog.xml
@@ -37,7 +37,6 @@
android:paddingTop="10dp"
android:dividerHeight="1dp"
android:requiresFadingEdge="vertical"
- android:paddingBottom="32dp"
android:nextFocusRight="@id/cancel_btt"
android:nextFocusLeft="@id/apply_btt"
tools:listitem="@layout/sort_bottom_single_choice" />
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index dc1635392..99a764dee 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -227,12 +227,6 @@
tools:listitem="@layout/homepage_parent"
tools:visibility="gone" />
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home_tv.xml b/app/src/main/res/layout/fragment_home_tv.xml
index f8559ab14..d1d5c9e3b 100644
--- a/app/src/main/res/layout/fragment_home_tv.xml
+++ b/app/src/main/res/layout/fragment_home_tv.xml
@@ -93,7 +93,6 @@
-
-
-
-
-
-
-
+
+
diff --git a/app/src/main/res/layout/fragment_setup_language.xml b/app/src/main/res/layout/fragment_setup_language.xml
index bdba3f209..93afe823e 100644
--- a/app/src/main/res/layout/fragment_setup_language.xml
+++ b/app/src/main/res/layout/fragment_setup_language.xml
@@ -31,8 +31,6 @@
android:layout_height="match_parent"
android:layout_rowWeight="1"
android:layout_marginBottom="60dp"
- android:nextFocusLeft="@id/skip_btt"
- android:nextFocusRight="@id/next_btt"
android:paddingTop="10dp"
android:requiresFadingEdge="vertical"
tools:listitem="@layout/sort_bottom_single_choice" />
diff --git a/app/src/main/res/layout/fragment_setup_layout.xml b/app/src/main/res/layout/fragment_setup_layout.xml
index 9e891e1aa..18815ced3 100644
--- a/app/src/main/res/layout/fragment_setup_layout.xml
+++ b/app/src/main/res/layout/fragment_setup_layout.xml
@@ -29,8 +29,8 @@
android:layout_height="match_parent"
android:layout_rowWeight="1"
android:layout_marginBottom="60dp"
- android:nextFocusLeft="@id/prev_btt"
- android:nextFocusRight="@id/next_btt"
+ android:nextFocusLeft="@id/apply_btt"
+ android:nextFocusRight="@id/cancel_btt"
android:paddingTop="10dp"
android:requiresFadingEdge="vertical"
tools:listitem="@layout/sort_bottom_single_choice" />
diff --git a/app/src/main/res/layout/fragment_setup_media.xml b/app/src/main/res/layout/fragment_setup_media.xml
index c1154405e..154db3386 100644
--- a/app/src/main/res/layout/fragment_setup_media.xml
+++ b/app/src/main/res/layout/fragment_setup_media.xml
@@ -26,8 +26,8 @@
android:layout_height="match_parent"
android:layout_rowWeight="1"
android:layout_marginBottom="60dp"
- android:nextFocusLeft="@id/prev_btt"
- android:nextFocusRight="@id/next_btt"
+ android:nextFocusLeft="@id/apply_btt"
+ android:nextFocusRight="@id/cancel_btt"
android:paddingTop="10dp"
android:requiresFadingEdge="vertical"
tools:listitem="@layout/sort_bottom_single_choice" />
diff --git a/app/src/main/res/layout/fragment_setup_provider_languages.xml b/app/src/main/res/layout/fragment_setup_provider_languages.xml
index e92f1135b..8dc889178 100644
--- a/app/src/main/res/layout/fragment_setup_provider_languages.xml
+++ b/app/src/main/res/layout/fragment_setup_provider_languages.xml
@@ -29,8 +29,8 @@
android:layout_height="match_parent"
android:layout_rowWeight="1"
android:layout_marginBottom="60dp"
- android:nextFocusLeft="@id/prev_btt"
- android:nextFocusRight="@id/next_btt"
+ android:nextFocusLeft="@id/apply_btt"
+ android:nextFocusRight="@id/cancel_btt"
android:paddingTop="10dp"
android:requiresFadingEdge="vertical"
tools:listitem="@layout/sort_bottom_single_choice" />
diff --git a/app/src/main/res/layout/player_custom_layout.xml b/app/src/main/res/layout/player_custom_layout.xml
index 5ccc3ff09..c4aa4c473 100644
--- a/app/src/main/res/layout/player_custom_layout.xml
+++ b/app/src/main/res/layout/player_custom_layout.xml
@@ -264,15 +264,6 @@
tools:visibility="visible"
android:layout_gravity="center"/>
-
-
diff --git a/app/src/main/res/layout/player_custom_layout_tv.xml b/app/src/main/res/layout/player_custom_layout_tv.xml
index c6445d80f..88e4cd984 100644
--- a/app/src/main/res/layout/player_custom_layout_tv.xml
+++ b/app/src/main/res/layout/player_custom_layout_tv.xml
@@ -352,22 +352,6 @@
android:layout_marginEnd="32dp"
android:orientation="vertical">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/trailer_custom_layout.xml b/app/src/main/res/layout/trailer_custom_layout.xml
index b1dc96479..ddaa53464 100644
--- a/app/src/main/res/layout/trailer_custom_layout.xml
+++ b/app/src/main/res/layout/trailer_custom_layout.xml
@@ -171,12 +171,6 @@
android:layout_marginEnd="32dp"
android:orientation="vertical">
-
-
مقطع
استعراض
البث قائم
- أظهر ساعة الوقت الحقيقي
- أظهر شاشة الوقت الحقيقي في قمة الشاشة. ينطبق على الصفحة الرئيسية و المشغل
- اعدادات الملف الشخصي
- أخفي المصادر صاحبة الأولوية السلبية
- أخفي المصادر التي تحتوي أخطاء
diff --git a/app/src/main/res/values-b+bn/strings.xml b/app/src/main/res/values-b+bn/strings.xml
index 39ca9396e..e90608793 100644
--- a/app/src/main/res/values-b+bn/strings.xml
+++ b/app/src/main/res/values-b+bn/strings.xml
@@ -3,7 +3,7 @@
পোস্টার
ক্লাউডস্ট্রিম দিয়ে চালান
হোম
- অভিনয়ে: %s
+ অভিনয়েঃ %s
%1$dদিন %2$dঘন্টা %3$dমিনিট
%1$dঘন্টা %2$dমিনিট
%d মিনিট
@@ -22,7 +22,7 @@
খুঁজুন
ডাউনলোডসমূহ
সেটিংস
- %1$s পর্ব %2$d
+ %1$s এপি %2$d
পর্ব %d মুক্তির তারিখ
শেয়ার
ব্রাউজারে খুলুন
@@ -131,8 +131,8 @@
স্টোরেজ এর অনুমতি অনুপস্থিত। দয়া করে আবার চেষ্টা করুন।
তথ্য
ট্রেইলার প্রদর্শন করুন
- Kitsu থেকে পোস্টারসমূহ প্রদর্শন করুন
- স্বয়ংক্রিয় প্লাগইন আপডেট
+ কিটসু হতে পোস্টারসমূহ প্রদর্শন করুন
+ স্বয়ংক্রিয়ভাবে প্লাগিন এর হালনাগাদ
স্বয়ংক্রিয়ভাবে প্লাগিনসমুহের ডাউনলোড
হালনাগাদ ও ব্যাকআপ
অ্যাপ এর হালনাগাদ দেখান
@@ -148,9 +148,9 @@
সামনে বা পিছনের দিকে যেতে ডান বা বাম দিকে দুবার আলতো চাপুন
ফাইল ডিলিট
মান ডিফল্ট এ রিসেট করুন
- স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন যোগ।
+ স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন
সিনেমা
- অ্যানিমে ফিলার পর্ব প্রদর্শন
+ এনিমে এর ফিলার পর্ব দেখায়
টিভি সিরিজ
লিংক পাওয়া যায়নি
চা খাওয়ানো হয়েছে
@@ -174,7 +174,7 @@
ডিলিট
শুরু
কার্টুন
- Some devices do not support the new package installer. Try the legacy option if updates do not install।
+ কিছু ফোন নতুন প্যাকেজ ইনস্টলার সাপোর্ট করে না। যদি আপডেটগুলি ইনস্টল না হয় তবে পুরোনো পদ্ধতি ব্যবহার করে দেখুন।
সাবটাইটেল নেই
এই প্রোভাইডার ক্রোমকাস্ট সাপোর্ট করে না
উন্নত অনুসন্ধান
@@ -204,7 +204,7 @@
আমাদের তৈরি Anime দেখার অ্যাপ্লিকেশন
18+
এনিমে
- অনুসন্ধানের ফলে নির্বাচিত ভিডিওর মান লুকান
+ অনুসন্ধান ফলাফলে নির্বাচিত ভিডিও কুয়ালিটি লুকান
অ্যাপ
লাইভস্ট্রিম
APK ইনস্টলার
@@ -220,7 +220,7 @@
আপনার বর্তমান পর্বের অগ্রগতি স্বয়ংক্রিয়ভাবে সিঙ্ক করুন
প্লাগইন ডাউনলোড ফিল্টার করতে মোড নির্বাচন করুন
লিঙ্ক পুনরায় লোড হয়েছে
- অ্যাকাউন্ট পরিবর্তন
+ সুইচ অ্যাকাউন্ট
দাবিত্যাগ
এশিয়ান ড্রামা
সোর্স
@@ -256,7 +256,7 @@
টিভি লেআউট
ব্যবহারকারীর নাম
%1$d-%2$d
- নতুন সাইটের নাম
+ NewSiteName
ডিফল্ট
OVA
ওভিয়ে
@@ -302,7 +302,7 @@
নতুন এপিসোডের নোটিফিকেশন
অন্য এক্সটেনশনের মধ্যে খুঁজুন
কোন আপডেট পাওয়া যায়নি
- অ আ ক খ ১২৩
+ password123
আসছে %s সময়ের মধ্যে
বাতিল করুন
%s \nঅবশিষ্ট
@@ -322,7 +322,7 @@
আপডেট
ওয়াইফাই তে যে কোয়ালিটিতে দেখতে চান
মোবাইল ডাটায় যে কোয়ালিটিতে দেখতে চান
- প্লেয়ারের তথ্য দেখুন
+ ভিডিও প্লেয়ারে রেজুলেশন
ভিডিও বাফার সাইজ
ভিডিও বাফার লেনথ
যখন আইএসপি ব্লক করবে তখন কার্যকরী
@@ -354,72 +354,4 @@
স্পিচ রিকগনিশন উপলব্ধ নেই
কথা বলা শুরু করুন…
ডাউনলোড তালিকা
- সম্পূর্ণ সিরিজ প্লে করুন
- এই ভিডিওটি একটি টরেন্ট, অর্থাৎ আপনার ভিডিও অ্যাক্টিভিটি ট্র্যাক করা সম্ভব।\nএগোনোর আগে টরেন্টিং সম্পর্কে ভালোভাবে বুঝে নিন।
- ডিলিট করার জন্য আইটেম সিলেক্ট করুন
- বর্তমানে কোনো ডাউনলোড নেই।
- বর্তমানে কোনো ডাউনলোড অপেক্ষায় নেই।
- অফলাইনে দেখার জন্য উপলব্ধ
- সব সিলেক্ট করুন
- সব ডিসিলেক্ট করুন
- লোকাল ভিডিও খুলুন
- অতিরিক্ত ব্রাইটনেস
- ডিসপ্লে ব্রাইটনেস ১০০% ছাড়িয়ে গেলে ব্রাইটনেস ফিল্টার চালু করুন
- সার্চ সাজেশন
- টাইপ করার সময় সার্চ সাজেশন দেখান
- সাজেশন মুছে ফেলুন
- প্লেয়ার মেটাডেটা ওভারলে প্রদর্শন
- কাস্ট প্যানেল প্রদর্শন
- প্রাক-মুক্তি সংস্করণ ইনস্টলেশন
- প্রাক-মুক্তি সংস্করণ ইতোমধ্যে ইনস্টল রয়েছে।
- প্রাক-মুক্তি সংস্করণ ইনস্টল করতে সমস্যা হয়েছে।
- (%1$d | %2$s) মুছুন
- সাবধান
- আপনি কি নিম্নলিখিত আইটেমগুলো স্থায়ীভাবে মুছতে চান?\n\n%s
- ফাইল মুছুন
- আপনি কি নিশ্চিত যে আপনি %1$s-এ নিচের এপিসোডগুলো স্থায়ীভাবে মুছে ফেলতে চান?\n\n%2$s
- আপনি নিচের সিরিজগুলোর সব এপিসোডও স্থায়ীভাবে মুছে ফেলবেন:\n\n%s
- আপনি কি নিশ্চিত যে আপনি নিচের সিরিজটির সব এপিসোড স্থায়ীভাবে মুছে ফেলতে চান?\n\n%s
- সঙ্গীত
- অডিও বই
- মিডিয়া
- অডিও
- পডকাস্ট
- ভিডিও
- এনকোডিং ত্রুটি
- অসমর্থিত ত্রুটি
- কাস্ট মিরর
- প্লে মিরর"
- রেটিং লেবেল
- পর্বের বিবরণ
- প্লেয়ারে প্রদর্শিত - স্কিপের পরিমাণ
- প্লেয়ার দৃশ্যমান থাকলে যে সময় পরিমাণ স্কিপ করা হয়
- GitHub-এর সাথে সংযোগ করা যায়নি। jsDelivr proxy চালু করা হচ্ছে।
- jsDelivr ব্যবহার করে GitHub-এর সরাসরি লিংক ব্লক করা এড়িয়ে যান। এতে আপডেট কয়েকদিন বিলম্বিত হতে পারে।
- নিরাপত্তা
- অ্যাকাউন্টসমূহ
- লাইভ ঘড়ি দেখান
- পর্দার উপরের অংশে লাইভ ঘড়ি দেখায়। হোমপেজ ও প্লেয়ারে প্রযোজ্য
- %1$s %2$s
- অতিরিক্ত_উজ্জ্বলতা_সক্রিয়
- স্থানীয়ভাবে ব্যবহারকারী যাচাই
- ট্র্যাকিং যোগ করা
- সমন্বয়
- মূল্যায়ন
- %s ব্যবহারকারী প্রমাণিত
- %s যোগ করা হয়েছে
- /%d
- %s-এ লগইন করা যায়নি
- কোনোটি নয়
- স্বাভাবিক
- সব
- সর্বোচ্চ
- সর্বনিম্ন
- সীমারেখা
- ছায়া
- উত্থিত
- সাবটাইটেল সিঙ্ক করুন
- ১০০০ মিলিসেকেন্ড
- সাবটাইটেল বিলম্ব
- সাবটাইটেল যদি %d মিলিসেকেন্ড আগে দেখায় তবে এটি ব্যবহার করুন
diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml
index a441d894a..983a03db7 100644
--- a/app/src/main/res/values-b+cs/strings.xml
+++ b/app/src/main/res/values-b+cs/strings.xml
@@ -322,8 +322,8 @@
Náhodný
Již brzy…
Kam
- CamRip
- CamHD
+ Kam
+ Kam
HQ
HD
TS
@@ -344,7 +344,7 @@
Rozlišení
Neplatné ID
Odstranit skryté titulky z titulků
- Odstranit reklamy z titulků
+ Odstranit nadbytečné titulky
Extra
Označit jako zhlédnuté
Historie
@@ -660,7 +660,7 @@
Spouštím proces aktualizace doplňků!
Nebyly aktualizovány žádné doplňky.
Ep %s
- Úspěšně aktualizováno %d doplňků
+ Úspěšně aktualizováno %d doplňků!
Epizoda (sestupně)
Hodnocení (nejnižší)
Ručně aktualizovat doplňky
@@ -754,20 +754,4 @@
Video
Náhled
Živě
- Nastavení profilu
- Skrýt zdroje s negativní prioritou
- Skrýt zdroje s chybami
-
- - %d skrytý odkaz
- - %d skryté odkazy
- - %d skrytých odkazů
- - %d skrytých odkazů
-
- Zobrazit hodiny s reálným časem
- Zobrazit hodiny s reálným časem v horní části obrazovky. Platí pro domovskou obrazovku a přehrávač
- Logcat 🐈
- Spustit videa pozastavená
- Snížit stres při výběru zdrojů
- Hrana
- Pozadí
diff --git a/app/src/main/res/values-b+de/strings.xml b/app/src/main/res/values-b+de/strings.xml
index f00db9ab4..e674fafd6 100644
--- a/app/src/main/res/values-b+de/strings.xml
+++ b/app/src/main/res/values-b+de/strings.xml
@@ -639,7 +639,7 @@
Softwaredekodierung ermöglicht es dem Player, Videodateien abzuspielen, die von deinem Gerät nicht unterstützt werden, könnte jedoch bei hoher Auflösung zu ruckelnder oder instabiler Wiedergabe führen.
App neu starten und Stream Torrent Pop-Up akzeptieren, um fortzufahren.
Plugins aktualisieren
- %d Plugin(s) erfolgreich aktualisiert
+ %d Plugin(s) erfolgreich aktualisiert!
Plugins manuell aktualisieren
Podcast
Nicht unterstützter Fehler
@@ -735,13 +735,4 @@
Video
Vorschau
Live
- Echtzeituhr anzeigen
- Echtzeituhr oben auf dem Bildschirm anzeigen. Gilt für Startseite und Player
- Profil-Einstellungen
- Quellen mit negativer Priorität verbergen
- Quellen mit Fehlern ausblenden
-
- - %d ausgeblendeter Link
- - %d ausgeblendete Links
-
diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-b+es/strings.xml
index df6b181f1..167de546d 100644
--- a/app/src/main/res/values-b+es/strings.xml
+++ b/app/src/main/res/values-b+es/strings.xml
@@ -647,7 +647,7 @@
Actualizar complementos
Fecha de emisión (más antigua)
Puntuar %s
- %d complemento(s) actualizados correctamente
+ ¡%d complemento(s) actualizados correctamente!
No se actualizó ningún complemento.
Episodio (Ascendente)
Reconocimiento de habla no disponible
@@ -737,14 +737,4 @@
Vídeo
Vista previa
En Vivo
- Mostrar reloj en tiempo real
- Muestra un reloj en tiempo real en la parte superior de la pantalla. Se aplica a la página de inicio y al reproductor
- Configuración del perfil
- Ocultar fuentes con prioridad negativa
- Ocultar fuentes con errores
-
- - %d enlace oculto
- - %d enlaces ocultos
- - %d enlaces ocultos
-
diff --git a/app/src/main/res/values-b+fr/strings.xml b/app/src/main/res/values-b+fr/strings.xml
index bcfb3608a..bf401b20a 100644
--- a/app/src/main/res/values-b+fr/strings.xml
+++ b/app/src/main/res/values-b+fr/strings.xml
@@ -366,7 +366,7 @@
UHD
HDR
SDR
- CamHD
+ Cam
HQ
HD
4K
@@ -384,7 +384,7 @@
+30
Cam
Supprimer les éléments superflus des sous-titres
- CamRip
+ Cam
Trailer
Regardez les vidéos dans ces langues
Modifier le style de l\'application pour être assortie à votre appareil
@@ -641,7 +641,7 @@
Note %s
Date %s
Mettre à jour les plugins
- %d plugin(s) mis à jour avec succès
+ %d plugin(s) mis à jour avec succès !
Aucun plugin n\'a été mis à jour.
Note (Plus Haute)
Mettre à jour les plugins manuellement
@@ -737,18 +737,5 @@
Afficher les métadata de l\'overlay du lecteur vidéo
Vidéo
Prévisualisation
- Live
- Cacher les sources avec une priorité négative
- Afficher l\'heure
- Affiche l\'heure en haut de l\'écran. S\'applique à la page d\'Accueil et au lecteur
- Paramètres du profil
- Cacher les sources avec des erreurs
-
- - %d lien caché
- - %d liens cachés
- - %d liens cachés
-
- Logcat 🐈
- Commencer des vidéos en pause
- Réduire le stress lors du choix des sources
+ Direct
diff --git a/app/src/main/res/values-b+in/strings.xml b/app/src/main/res/values-b+in/strings.xml
index e9df06fc4..7d6475f31 100644
--- a/app/src/main/res/values-b+in/strings.xml
+++ b/app/src/main/res/values-b+in/strings.xml
@@ -313,8 +313,8 @@
Acak
Segera hadir…
Cam
- CamRip
- CamHD
+ Cam
+ Cam
HQ
HD
TS
@@ -444,7 +444,7 @@
Telah ditonton
Data salah
Hapus closed caption dari subtitlel
- Hapus iklan dari subtitle
+ Hapus teks tidak perlu pada subtitel
Audio Trek
Video Trek
Dukungan
@@ -661,7 +661,7 @@
Rating %s
Perbarui Plugin
Mulai proses pembaruan plugin!
- Berhasil memperbarui %d plugin(s)
+ Berhasil memperbarui %d plugin!
Perbarui plugin secara manual
Tidak ada plugin yang diperbarui.
Pemberitahuan pemutar untuk mengontrol pemutaran dari latar belakang
@@ -747,17 +747,4 @@
Live
Video
Pratinjau
- Tampilkan jam waktu nyata
- Tampilkan jam waktu nyata di bagian atas layar. Berlaku untuk halaman rumah dan pemain
- Pengaturan profil
- Sembunyikan sumber dengan prioritas negatif
- Sembunyikan sumber dengan kesalahan
-
- - %d link tersembunyi
-
- Edge
- Logcat 🐈
- Memulai video yang sedang dijeda
- Kurangi stres saat memilih sumber informasi
- Latar Belakang
diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-b+it/strings.xml
index 2211ec1dd..c47a84842 100644
--- a/app/src/main/res/values-b+it/strings.xml
+++ b/app/src/main/res/values-b+it/strings.xml
@@ -225,7 +225,7 @@
Livestream
NSFW
Altro
- Errore fonte
+ Errore sorgente
Errore remoto
Errore del renderer
Errore imprevisto del lettore
@@ -247,7 +247,7 @@
Controlla aggiornamenti
Blocca
Ridimensiona
- Fonte
+ Sorgente
Salta OP
Non mostrare di nuovo
Salta questo aggiornamento
@@ -346,12 +346,12 @@
Protagonista
Supporto
Secondario
- Fonte
+ Sorgente
Casuale
Prossimamente…
Cam
- CamRip
- CamHD
+ Cam
+ Cam
HQ
HD
TS
@@ -455,7 +455,7 @@
Opening misto
Installazione aggiornamento dell\'app…
Rimuovi le didascalie chiuse dai sottotitoli
- Rimuovere gli annunci dai sottotitoli
+ Rimuovi il bloat dai sottotitoli
Opening
Ending
Ending misto
@@ -670,7 +670,7 @@
Data di messa in onda (più vecchia)
Valutazione %s
Avvio del processo di aggiornamento plugin!
- %d plugin aggiornati correttamente
+ %d plugin aggiornati con successo!
Nessun plugin è stato aggiornato.
Notifiche lettore
La notifica del lettore per controllare la riproduzione in background
@@ -733,7 +733,7 @@
Cancella suggerimenti
Mostra pannello cast
Info sui media
- Nome fonte
+ Nome sorgente
Luminosità extra
Attiva il filtro di luminosità quando viene superato il 100% della luminosità dello schermo
extra_brightness_enabled
@@ -753,25 +753,10 @@
- %d download in coda
- %d download in coda
- Priorità fonti
- Decidi come le fonti video devono essere ordinate nel lettore
+ Priorità sorgente
+ Decidi come le sorgenti video devono essere ordinate nel lettore
Mostra sovrapposizione metadati lettore
Video
Anteprima
Live
- Impostazioni profilo
- Nascondi fonti con priorità negativa
- Nascondi fonti con errori
-
- - %d collegamento nascosto
- - %d collegamenti nascosti
- - %d collegamenti nascosti
-
- Mostra orologio in tempo reale
- Mostra un orologio in tempo reale nella parte superiore dello schermo. Si applica alla homepage e al lettore
- Logcat 🐈
- Avvia video in pausa
- Riduci lo stress nella scelta delle fonti
- Sfondo
- Bordo
diff --git a/app/src/main/res/values-b+ja/strings.xml b/app/src/main/res/values-b+ja/strings.xml
index c7bdb4ef0..c5e6272b2 100644
--- a/app/src/main/res/values-b+ja/strings.xml
+++ b/app/src/main/res/values-b+ja/strings.xml
@@ -383,7 +383,7 @@
お気に入り
生体認証は、このデバイスではサポートされていません
指紋、顔ID、PIN、パターン、パスワードでアプリをロック解除します.
- スマホやパソコンで %s にアクセスし、上記のコードを入力してください
+ スマホやPCで%sにアクセスし、上記のコードを入力してください
PINコードが期限切れになりました!
ライブラリが空です :(\nライブラリアカウントにログインするか、ローカルライブラリに表示を追加してください。
このリストは空です。 別のものに切り替えてみてください。
@@ -645,7 +645,7 @@
アプリを再起動し、ポップアップに同意して続行します。
ソフトウェアデコーディング
ソフトウェアデコーディングにより、機器でサポートされていないビデオファイルを再生できますが、高解像度で遅延や不安定な再生を引き起こす可能性があります。
- %d 個のプラグインを更新しました
+ %d 個のプラグインを正常に更新しました!
更新されたプラグインはありません。
プレーヤー通知
バックグラウンドから再生を制御するためのプレーヤー通知
@@ -733,12 +733,4 @@
動画
プレビュー
ライブ
- 現地時刻を表示
- 画面上部に現地時刻を表示します。ホーム画面とプレイヤーに適用されます
- プロフィール設定
- 優先度がマイナスのソースを非表示
- エラーが発生したソースを非表示
-
- - %d件の非表示リンク
-
diff --git a/app/src/main/res/values-b+ko/strings.xml b/app/src/main/res/values-b+ko/strings.xml
index 3059bc3be..4feeabe33 100644
--- a/app/src/main/res/values-b+ko/strings.xml
+++ b/app/src/main/res/values-b+ko/strings.xml
@@ -262,7 +262,7 @@
포스터 이미지
제목
Cam
- CamRip
+ Cam
Blu-ray
WP
DVD
@@ -405,7 +405,7 @@
인터넷에서 불러오기
로드됨 %s
공개 예정…
- CamHD
+ Cam
HD
TS
플레이어
@@ -493,7 +493,7 @@
음각
양각
다람쥐 헌 쳇바퀴에 타고파
- 자막에서 광고 제거
+ 불필요한 요소/코드 제거
엑스트라
https://example.com/example.mp4
트랙
@@ -664,7 +664,7 @@
플러그인 업데이트하기
수동으로 플러그인 업데이트 하기
플러그인 업데이트 절차를 시작합니다!
- 플러그인 %d개를 성공적으로 업데이트했습니다
+ 성공적으로 %d 플러그인을 업데이트 하였습니다!
업데이트 된 플러그인이 없습니다.
플레이어 알림
백그라운드에서 재생을 제어하기 위한 플레이어 알림
@@ -733,17 +733,4 @@
비디오
프리뷰
라이브
- 프로필 설정
- 오류가 있는 소스 숨기기
- 우선순위가 음수인 소스 숨기기
-
- - 숨겨진 링크 %d개
-
- 실시간 시계 표시
- 화면 상단에 실시간 시계를 표시합니다. 홈페이지와 플레이어에 적용됩니다
- Logcat 🐈
- 동영상을 정지한 채 시작
- 소스를 선택할 때의 부하 줄이기
- 배경
- 윤곽선
diff --git a/app/src/main/res/values-b+lv/strings.xml b/app/src/main/res/values-b+lv/strings.xml
index a8ecaa68a..531b5420b 100644
--- a/app/src/main/res/values-b+lv/strings.xml
+++ b/app/src/main/res/values-b+lv/strings.xml
@@ -8,7 +8,7 @@
Galvenais plakāts
Nākamais nejaušais
Iet atpakaļ
- Nomainīt pakalpojumu sniedzēju
+ Nomainīt dvēju
Apskatīt background
Ātrums (%.2fx)
Lidzīgi: %.1f
@@ -24,7 +24,7 @@
Nākamā epizode
Pārlūks
Izlaist ladešanos
- Ielādē…
+ Lādējas…
Skaties
Aizturēts
Pabeigts
@@ -42,7 +42,7 @@
Palaist epizodi
Ieladēt
Lejupielāde iepauzēta
- Lejupielāde sākta
+ Lādēšana sakās
Lejupielāde neizdevās
Lejupielāde atcelta
Lejupielāde pabeigta
@@ -50,7 +50,7 @@
Tīkla plūsma
Kļūda, ielādējot saites
Iekšējā atmiņa
- Dublēts
+ Dub
Dzēst datni
Atskaņot datni
Atsākt ielādi
@@ -74,8 +74,8 @@
Stūra tips
Fonts
Fonta lielums
- Meklēt pēc pakalpojumu sniedzējiem
- Meklēt pēc tipiem
+ Meklēt izmantojot devējus
+ Meklēt izmantojot tipus
%d Banāni iedoti veidotājiem
%d. epizode būs pieejama
Filtrs
@@ -105,9 +105,9 @@
Noņemt
Vairāk informācijas
@string/home_play
- Lai šis pakalpojumu sniedzējs darbotos pareizi, var būt nepieciešams VPN
- Šis pakalpojumu sniedzējs piedāvā torrentus, tāpēc ļoti ieteicams izmantot VPN
- Vietne nenodrošina metadatus. Ja tie nebūs pieejami vietnē, video lejupielāde neizdosies.
+ VPNs varētu būt vajadzīgs lai šis devējs strādātu pareizi
+ Šis devējs ir Torrents vpn ir rekomendēts
+ Dati nav doti no saites, video lādēšanas neizdosies ja neiksestē saitē.
Apraksts
Nav apraksts atrasts
Apraksts nav atrasts
@@ -150,7 +150,7 @@
Atjauninājumi un dublēšana
Informācija
Advancēta meklēšana
- Sniedz meklēšanas rezultātus, kas sagrupēti pēc pakalpojumu sniedzēja
+ Dod tev meklēšanas rezultātus citus no devēja
Rādīt anime papilda epizodi
Rādīt reklāmkadrus
Rādīt Kitsu vietnes plakātus
@@ -168,7 +168,7 @@
Iedot banānu izstrādātājiem
Iedotie banāni
Lietotnes valoda
- Šis pakalpojumu sniedzējs neatbalsta Chromecast
+ Šim devējam nav Chromecast pieņemšana
Nav linku strastu
Links kopēts cliobordā
Restartēt uz parasto value
@@ -193,7 +193,7 @@
-30
+30
Šis pilnibā dzesīs %s \nEsat parliecināts?
- %dmin.\natlikusi(-šas)
+ %dm \natlikušas
Pabeigts
Statuss
Gads
@@ -295,8 +295,8 @@
Vēlamie multimediji
Iespējot nepiedienīgu, izaicinošu saturu (NSFW) atbalstītajos papildinājumos
Subtitru kodējums
- Pakalpojumu sniedzēji
- Pakalpojumu sniedzēja tests
+ Devēji
+ Devēju tests
Izskats
Automātiski
Televizora izskats
@@ -378,7 +378,7 @@
https://piemērs.com/piemērs.mp4
Referents
Nākamais
- Skatīties video šajās valodās
+ Skatieties videoklipus šajās valodās
Iepriekšējais
Izlaist uzstādīšanu
Mainiet lietotnes izskatu, lai tā atbilstu savai ierīcei
@@ -570,7 +570,7 @@
Atjaunināt papildinājumus
Atjaunināt papildinājumus manuāli
Sāk papildinājuma atjaunināšanas procesu!
- Veiksmīgi atjaunināts(-i) %d papildinājums(-i)
+ Veiksmīgi atjaunināts(-i) %d papildinājums(-i)!
Neviens papildinājums netika atjaunināts.
Meklēt citos papildinājumos
Pārbaudīt visus papildinājumus
@@ -614,8 +614,4 @@
Papildu spilgtums
Papildu spilgtums iespējots
Meklēšanas ieteikumi
- Pārlādēt pakalpojumu sniedzēju
- Tiešraide
- Video
- Saites atkārtoti ielādētas
diff --git a/app/src/main/res/values-b+mk/strings.xml b/app/src/main/res/values-b+mk/strings.xml
index 64068ee29..4e37afdea 100644
--- a/app/src/main/res/values-b+mk/strings.xml
+++ b/app/src/main/res/values-b+mk/strings.xml
@@ -310,7 +310,7 @@
Видео резолуција
Азиски драми
Приклучокот е вчитан
- Отстрани реклами од преводите
+ Отстрани непотребен текст од преводите
Не е преземено: %d
Remove closed captions from subtitles
Аудио јазик
@@ -642,7 +642,7 @@
Започнување на процесот на ажурирање на приклучоци!
Ажурирај приклучоци
Ниту еден приклучок не е ажуриран.
- Успешно ажурирани %d приклучоци
+ Успешно ажурирани %d приклучоци!
Епизода (Растечки)
Епизода (Опаѓачки)
Рејтинг (Највисок)
@@ -738,9 +738,4 @@
- %d преземања во редицата
Во живо
- Започни паузирани видеа
- Поставки на профил
- Сокриј извори со негативен приоритет
- Сокриј извори со грешки
- Позадина
diff --git a/app/src/main/res/values-b+nl/strings.xml b/app/src/main/res/values-b+nl/strings.xml
index feec84263..8a2424326 100644
--- a/app/src/main/res/values-b+nl/strings.xml
+++ b/app/src/main/res/values-b+nl/strings.xml
@@ -682,17 +682,4 @@
Zet torrent aan in Instellingen/Providers/Media voorkeur
Herstart de app en accepteer de Stream Torrent pop-up om verder te gaan.
Handmatige Update Knop
- Open de app met Vingerafdruk, Face ID, PIN, Pattern en Wachtwoord.
- Seekbar preview
- Zet preview thumbnail aan op seekbar
- Software decodering
- Software decodering staat de mogelijkheid toe om de speler videobestanden af te laten spelen op jouw apparaat, maar het kan een haperige of onstabiele kijkervaring zorgen op hoge resolutie.
- Volume heeft de 100% overschreden
- Update Plugins
- Geen plugins zijn geupdate.
- Speler meldingen
- De speler melding om de kijkervaring van de achtergrond de besturen
- Online
- Hoeveel verschillende items kunnen in parallel gedownload worden
- Parallel downloads
diff --git a/app/src/main/res/values-b+pl/strings.xml b/app/src/main/res/values-b+pl/strings.xml
index 99adc74ee..35367a1b2 100644
--- a/app/src/main/res/values-b+pl/strings.xml
+++ b/app/src/main/res/values-b+pl/strings.xml
@@ -324,8 +324,8 @@
Losowy
Już wkrótce…
Cam
- CamRip
- CamHD
+ Cam
+ Cam
HQ
HD
TS
@@ -349,7 +349,7 @@
Niepoprawny URL
Błąd
Usuń CC z napisów
- Usuń reklamy z napisów
+ Usuń nadmiarowe informacje z napisów
Filtrowanie wg preferowanego języka
Dodatki
Zwiastun
@@ -649,7 +649,7 @@
Odcinek (malejąco)
Ocena (najwyższa)
Zaktualizuj wtyczki
- Pomyślnie zaktualizowano wtyczki: %d
+ Pomyślnie zaktualizowano wtyczki: %d!
Nie zaktualizowano żadnych wtyczek.
Zaktualizuj wtyczki ręcznie
Rozpoczęcie procesu aktualizacji wtyczek!
@@ -742,20 +742,4 @@
Wideo
Zapowiedź
Na żywo
- Ustawienia profilu
- Ukryj źródła z negatywnym priorytetem
- Ukryj źródła z błędami
-
- - %d ukryty odnośnik
- - %d ukryte odnośniki
- - %d ukrytych odnośników
- - %d ukrytych odnośników
-
- Pokaż zegar czasu rzeczywistego
- Wyświetl zegar czasu rzeczywistego u góry ekranu. Dotyczy strony głównej i odtwarzacza
- Logcat 🐈
- Uruchamiaj filmy wstrzymane
- Ogranicz obciążenie podczas wyboru źródeł
- Tło
- Krawędź
diff --git a/app/src/main/res/values-b+pt+BR/strings.xml b/app/src/main/res/values-b+pt+BR/strings.xml
index 97c5f9e4c..455478b5a 100644
--- a/app/src/main/res/values-b+pt+BR/strings.xml
+++ b/app/src/main/res/values-b+pt+BR/strings.xml
@@ -340,8 +340,8 @@
Aleatório
Em breve…
Cam
- CamRip
- Cam HD
+ Cam
+ Cam
HQ
HD
TS
@@ -365,7 +365,7 @@
URL inválida
Erro
Remover legendas ocultas(CC) das legendas
- Remover anúncios das legendas
+ Remover bloat das legendas
Filtrar por linguagem preferida
Extras
Trailer
@@ -662,7 +662,7 @@
Data %s
Atualizar plugins
Iniciando o processo de atualização do plugin!
- Atualização bem-sucedida para %d plugin(s)
+ Atualização bem-sucedida para %d plugin(s)!
Nenhum plugin foi atualizado.
Atualizar plugins manualmente
Notificações de reprodução
@@ -752,19 +752,4 @@
Vídeo
Visualização
Ao vivo
- Mostrar relógio em tempo real
- Exiba um relógio em tempo real na parte superior da tela. Aplica-se à página inicial e ao player
- Configurações de perfil
- Ocultar fontes com prioridade negativa
- Ocultar fontes com erros
-
- - %d link oculto
- - %d link ocultos
- - %d link ocultos
-
- Borda
- Logcat 🐈
- Iniciar vídeos pausados
- Reduza o estresse ao selecionar fontes
- Fundo
diff --git a/app/src/main/res/values-b+ru/strings.xml b/app/src/main/res/values-b+ru/strings.xml
index 7a174483f..38e576baa 100644
--- a/app/src/main/res/values-b+ru/strings.xml
+++ b/app/src/main/res/values-b+ru/strings.xml
@@ -739,15 +739,4 @@
Видео
Предпросмотр
Прямой эфир
- Отображать текущее время
- Отображать в верхней части экрана часы с текущим временем. Применяется к главной странице и плееру
- Настройки профиля
- Скрывать источники с отрицательным приоритетом
- Скрывать источники с ошибками
-
- - %d скрытая ссылка
- - %d скрытые ссылки
- - %d скрытых ссылок
- - %d скрытых ссылок
-
diff --git a/app/src/main/res/values-b+so/strings.xml b/app/src/main/res/values-b+so/strings.xml
index 8a8f29095..0eb787311 100644
--- a/app/src/main/res/values-b+so/strings.xml
+++ b/app/src/main/res/values-b+so/strings.xml
@@ -65,7 +65,7 @@
Kaydka gudaha
Trj
Way bilaabmatay soo dejintu
- Khadka ka daaro
+ Daalaco
Waa la joojiyey dejintan
Way bilaabantay cuaboonaysiintu
Soo raridda lifaaqyadu way fashilantay
@@ -91,7 +91,7 @@
Tirtir
Nooca cidhifyada
Isticmaal xaddiga iftiinka ee moobilkaaga halkii aad adigu habayn lahayd ugu horreynba
- Cusboonaysiinta iyo Kaydinta
+ Gurmadka iyo cusbooneysiinta
Waa la kaydiyet xogta gurmadka
Raadinta waxay kuugu kala qaybinaysaa mid kasta iyo qaybiyihiisa
Way fashilantay in xogta laga soo celiyo faylka gurmadka%s
@@ -100,7 +100,7 @@
Xogta waa la kaydiyey
Heerka
Ogolaanshaha lagu kaydinayo ma hayno. Fadlan isku day mar kale.
- Akoonada iyo Amniga
+ Koontooyinka
Xog
Si iskii ah usoo deji sidkanaha
Qari tayada muqaalka aad dooratay natiijada waxa aad raadiso
@@ -153,7 +153,7 @@
Tusi Boodhka Kitsu
Si iskii ah u cusbooneysii sidkanaha
Si iskii ah usoo deji dhamaan sidkanayaasha aan weli lasoo dejin ee ku jira kaydka aad keentay.
- Si toos ah u raadi update cusub markii aad furto appka.
+ Marka ugu horreysa ee aad appka furto ayaad arkaysaa haddii wax cusub yihiin
Light novel app, isla dhiseyaasha appkan baa leh
Tus wixii appka ku cusub
Dib uso bilow fadhiisinta appka
@@ -238,13 +238,13 @@
Hareeraha madow ka saar markaad daawanayso
Qoraal-hoosaadka
Habaynta Qrl-hoosaadka Koromakaastiga
- Xawaaraha muuqaalka
- Midig iyo bidix u jiid si aad u xakameyso halka uu muuqaalku marayo
+ Habka Xawaare-kordhinta
+ Midig iyo bidix u jiid si aad marba dhinac ugu dhaafiso muqaalka
Laba jeer taabo midig ama bidix si aad u dhaafiso ama ku ceshato
Laba jeer taabo si aad u dhaafiso
- Inta aad is-dhaafinayso (Ilbiriqsiyo ahaan)
+ Inta aad is dhaafinayso
Laba jeer taabo si aad u qallajiso
- Labo jeer dhexda taabo si aad u joojiso
+ Dhexda taabo si aad u qallajisato
Gurmad u samee xogta
Iftiinka moobilka istcimaal
Cusbooneysii marba halkaad marayso daawashada
@@ -285,7 +285,7 @@
La degtay
Soo degaya
Ka Saar
- Kor ama hoos u jiid geesaha muuqaalka si aad u maamusho codka ama iftiinka
+ Kor ama hoos u jiid labada dhinaca ee daaraha si aad u maamusho codka ama iftiinka
Iskii-u-daarka xalqadda xigta
Si iskii ah ayuu u daarayaa xalqadda xigta marka aad dhamaysato kaad daawanayso
Si iskii ah ha ula socdo appku hadba xalqadda aad marayso
@@ -310,7 +310,7 @@
Safe mode ayaa daaran
Dhammaan Kordhiyeyaasha waa la wada demiyey cillad timi aawadeed si aad u ogaato ka cillada sababaya.
%s (La xayiray)
- Dib u fur app-ka si aad u aragto isbeddelada.
+ Marka dambe eed gasho isticmaal
Eeg warbixinra Cilladda
Qiimeynta: %s
Lahaanshaha
@@ -424,8 +424,8 @@
Xayiran: %d
Aan dejinayn: %d
Waxa la cusbooneysiiyey %d sidkane
- Ugu horreyn cloudstream ma laha wax websaydyo uu filimaanta kasoo xigto, waxay noqonaysaa inaad adigu rakibato reboositarradooda. Sababtuna waa in mar dhexdaas ah na dacweeyeen shirkadda Sky UK Limited, markaa si aan mar dambe taasi u dhicin anagu kuma rakibi karno...\n\nDiscord naga soo qabo ama internetka ka baadh.
- Digniin: CloudStream mas\'uuliyad kama qaadanayo isticmaalka kordhinnada (extensions) dhinac saddexaad mana bixiyo wax taageero ah oo iyaga ku saabsan!
+ Ugu horreyn cloudstream ma laha wax websaydyo uu filimaanta kasoo xigto, waxay noqonaysaa inaad adigu rakibato reboositarradooda... \n \nSababtuna waa in mar dhexdaas ah na dacweeyeen shirkadda Sky UK Limited🤮, markaa si aan mar dambe taasi u dhicin anagu kuma rakibi karno... \n \nDiscord naga soo qabo ama internetka ka baadh.
+ Soo deji dhamaan sidkanayaasha reboositarkan?
Boodhka
Boodhka xalqadda
Boodhka weyn
@@ -465,45 +465,4 @@
Bilowga
Bilow isku qasan
Qoraalka dhamaadka
- Seasonka %1$d Xalqada %2$d waxaa la soo deyn doonaa
- %1$d saac %2$d daq %3$d ilb
- %1$ddaq %2$dilb
- %1$dilb
- Sawirka
- Ka bilow bilowga
- Waxa soo degaya
- Aqoonsiga codka ma shaqeynayo
- Bilow hadalka…
- biraawsar
- Daaro musalsalka oo dhan
- Fiidiyowgaan waa Torrent, taasi waxay la macno tahay in la raadraaci karo dhaqdhaqaaqaaga daawashada.\nHubi inaad fahamsan tahay sida Torrent-ku u shaqeeyo ka hor intaadan sii socon.
- Dooro waxyaabaha aad tirayso
- Ma jiro muuqaal aad soo degsatay.
- Hadda ma jiro wax safka ku jira oo soo degaya.
- Waxaa laa daawan karaa khad la\'aan
- Dooro dhammaan
- Ha dooran dhammaan
- Daaro muuqaallada taleefanka kuugu jira
- Linkska dib ayaa loo cusboonaysiiyay
- Magaca Repository-ka iyo URL-ka
- Waa la koobiyeeyay!
- Ogeysiiska xalqada cusub
- Ka raadi extensions-ka kale
- Muuji waxyaabaha laguu soo jeediyay
- Daaro
- Diiwaanka
- Wuxuu ku darayaa inaad doorato xawaaraha muuqaalka
- Iftiin dheeraad ah
- Daar filter-ka iftiinka marka uu iftiinku 100% ka bato
- Iftiinka dheeraadka ah waa shidan yahay
- Inta jeer ee la kaydinayo
- Kaydka
- Talooyinka Raadinta
- Soo bandhig talooyinka inta aad wax qorayso
- Tirtir Talooyinka
- Muuji xogta dulsaaran muuqaalka
- Soo bandhig guddiga dhibidda
- Dooro habka loo kala saarayo soo dejinta plugins-ka
- Soo deji nooca hordhaca ah
- Nooca hordhaca ah horey ayaa loo soo dejiyay.
diff --git a/app/src/main/res/values-b+sv/strings.xml b/app/src/main/res/values-b+sv/strings.xml
index d4ac26743..52d4e1d05 100644
--- a/app/src/main/res/values-b+sv/strings.xml
+++ b/app/src/main/res/values-b+sv/strings.xml
@@ -422,7 +422,7 @@
Felsäkert läge på
Starta om appen för att se ändringar.
Intern spelare
- CamHD
+ Kamera HD
Tillägg nedladdad
Lager URL eller Shortcode
Börjat hämta %1$d %2$s…
@@ -501,7 +501,7 @@
Ersätt alla
Filler
Förbikoppla ISP
- CamRip
+ Kamera
Kamera
Alla tillägg stängdes av på grund av en krasch för att hjälpa dig hitta det tillägget som orsakar problem.
Storlek
@@ -722,7 +722,7 @@
Inget konto
Dra uppåt igen för att gå över 100%
Startar plugin uppdateringsprocessen!
- %d plugin(s) har uppdaterats
+ %d plugin(s) har uppdaterats!
Inga plugins blev uppdaterade.
Spelarnotiser
Spelarnotisen för att styra uppspelningen i bakgrunden
@@ -735,14 +735,4 @@
Video
Förhandsvisning
Live
- Visa realtidsklocka
- Visa en realtidsklocka högst upp på skärmen. Gäller startsidan och spelaren
- Profilinställningar
- Dölj källor med negativ prioritet
- Dölj källor med fel
-
- - %d dold länk
- - %d dolda länkar
-
- Logcat 🐈
diff --git a/app/src/main/res/values-b+ta/strings.xml b/app/src/main/res/values-b+ta/strings.xml
index 2537eba0c..4cde39d8f 100644
--- a/app/src/main/res/values-b+ta/strings.xml
+++ b/app/src/main/res/values-b+ta/strings.xml
@@ -609,7 +609,7 @@
பயன்பாட்டிலிருந்து வெளியேறுவதற்கு முன் உரையாடலைக் காட்டு
உள்நாட்டில் அங்கீகரிக்கவும்
தள்ளுபடி
- உங்கள் ச்மார்ட்போன் அல்லது கணினியில் %s ஐப் பார்வையிட்டு மேலே உள்ள குறியீட்டை உள்ளிடவும்
+ உங்கள் ச்மார்ட்போன் அல்லது கணினியில் %s ஐப் பார்வையிட்டு மேலே உள்ள குறியீட்டை உள்ளிடவும்
சாதன முள் குறியீட்டைப் பெற முடியாது, உள்ளக அங்கீகாரத்தை முயற்சிக்கவும்
முள் குறியீடு இப்போது காலாவதியானது!
விளிம்பு அளவு
@@ -652,7 +652,7 @@
செருகுநிரல்களைப் புதுப்பிக்கவும்
செருகுநிரல்களை கைமுறையாக புதுப்பிக்கவும்
சொருகி புதுப்பிப்பு செயல்முறையைத் தொடங்குகிறது!
- %d செருகுநிரல்(கள்) வெற்றிகரமாக புதுப்பிக்கப்பட்டது
+ வெற்றிகரமாக புதுப்பிக்கப்பட்டது %d சொருகி (கள்)!
செருகுநிரல்கள் எதுவும் புதுப்பிக்கப்படவில்லை.
பிளேயர் அறிவிப்புகள்
பின்னணியில் இருந்து பின்னணியைக் கட்டுப்படுத்துவதற்கான பிளேயர் அறிவிப்பு
@@ -731,17 +731,4 @@
- %d பதிவிறக்கம் வரிசையில் உள்ளது
- %d பதிவிறக்கங்கள் வரிசையில் உள்ளன
- பிளேயர் மேனிலை தரவு மேலடுக்கைக் காட்டு
- ஒளிதோற்றம்
- உண்மையான நேர கடிகாரத்தைக் காட்டு
- திரையின் மேற்புறத்தில் நிகழ் நேர கடிகாரத்தைக் காட்டு. முகப்புப்பக்கம் மற்றும் பிளேயருக்குப் பொருந்தும்
- முன்னோட்டம்
- சுயவிவர அமைப்புகள்
- எதிர்மறையான முன்னுரிமையுடன் ஆதாரங்களை மறை
- பிழைகளுடன் ஆதாரங்களை மறைக்கவும்
-
- - %d மறைக்கப்பட்ட இணைப்பு
- - %d மறைக்கப்பட்ட இணைப்புகள்
-
- வாழ்க
diff --git a/app/src/main/res/values-b+tr/strings.xml b/app/src/main/res/values-b+tr/strings.xml
index c7e535c8d..d711505a8 100644
--- a/app/src/main/res/values-b+tr/strings.xml
+++ b/app/src/main/res/values-b+tr/strings.xml
@@ -351,8 +351,8 @@
Rastgele
Çok yakında…
Cam
- CamRip
- CamHD
+ Cam
+ Cam
HQ
HD
TS
@@ -376,7 +376,7 @@
Geçersiz URL
Hata
Altyazılardan seçmeli altyazıyı (CC) kaldır
- Altyazılardaki reklamları kaldır
+ Altyazılardaki şişkinliği kaldır
Tercih edilen medya diline göre filtrele
Ek içerikler
Fragman
@@ -661,7 +661,7 @@
Tarih %s
Eklentileri Güncelle
Eklenti güncellemesi başlıyor!
- %d eklenti başarıyla güncellendi
+ %d eklenti başarıyla güncellendi!
Hiçbir eklenti güncellenmedi.
Eklentileri el ile güncelle
Oynatıcı bildirimi arka planda oynatmasını kontrol etmek içindir
@@ -749,18 +749,4 @@
Canlı
Ön Gösterim
Video
- Gerçek zamanlı saati göster
- Ekranın üst kısmında gerçek zamanlı bir saat göster. Ana sayfa ve oynatıcı için uygulanır
- Profil ayarları
- Eksi önceliğe sahip kaynakları gizle
- Hatalı kaynakları gizle
-
- - %d gizli bağlantı
- - %d gizli bağlantı
-
- Logcat 🐈
- Videoları duraklatılmış olarak başlat
- Kaynak seçerken zorlanmayı azalt
- Kenar
- Arka plan
diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml
index 9b1bf5eb6..ba46736ac 100644
--- a/app/src/main/res/values-b+uk/strings.xml
+++ b/app/src/main/res/values-b+uk/strings.xml
@@ -381,13 +381,13 @@
Додатково
https://example.com/example.mp4
Cam
- CamRip
- CamHD
+ Cam
+ Cam
HQ
HD
TS
TC
- Вилучати рекламу з субтитрів
+ Вилучати роздуття субтитрів
Джерело переходу (необов’язково)
Далі
Переглядайте відео на цих мовах
@@ -646,7 +646,7 @@
Еп. %s
Дата %s
Оновити розширення
- Успішно оновлено %d розширення(-ь)
+ Успішно оновлено %d розширення(-ь)!
Оновити розширення вручну
Починається оновлення розширень!
Не оновлено жодного розширення.
@@ -739,20 +739,4 @@
Відео
Передперегляд
Наживо
- Показувати поточний час
- Показувати поточний час у верхній частині екрана (на головній та у програвачі)
- Налаштування профілю
- Приховати джерела з від’ємним пріоритетом
- Приховати джерела з помилками
-
- - %d приховане посилання
- - %d приховані посилання
- - %d прихованих посилань
- - %d прихованих посилань
-
- Logcat 🐈
- Запускати відео на паузі
- Зменште стрес під час вибору джерел
- Край
- Тло
diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml
index e6ffba130..3b2b3876f 100644
--- a/app/src/main/res/values-b+vi/strings.xml
+++ b/app/src/main/res/values-b+vi/strings.xml
@@ -20,7 +20,7 @@
Tốc độ (%.2fx)
Đánh giá: %.1f
- Đã tìm thấy bản cập nhật mới!\n%1$s -> %2$s
+ Đã tìm thấy bản cập nhật mới! \n%1$s -> %2$s
Bộ lọc
%d phút
CloudStream
@@ -36,7 +36,7 @@
Tập tiếp theo
Thể loại
Chia sẻ
- Phát bằng Trình duyệt
+ Mở bằng trình duyệt
Bỏ tải
Đang tải…
Đang xem
@@ -255,13 +255,13 @@
Chất lượng xem ưu tiên (WiFi)
Số ký tự tối đa tiêu đề trình phát
Hiển thị thông tin trình phát
- Kích thước bộ đệm video
- Thời lượng bộ đệm video
+ Kích thước bộ nhớ đệm video
+ Thời lượng bộ nhớ đệm
Bộ nhớ đệm video trên thiết bị
Xoá bộ nhớ đệm hình ảnh và video
Sẽ gây lỗi nếu đặt quá cao trên thiết bị có bộ nhớ thấp như Android TV.
- Sẽ gây lỗi nếu đặt quá cao trên thiết bị có dung lượng lưu trữ thấp như Android TV.
- DNS qua HTTPS
+ Sẽ gây lỗi nếu đặt quá cao trên máy có dung lượng lưu trữ thấp như Android TV.
+ DNS over HTTPS
Rất hữu ích để bỏ chặn ISP
Bản sao trang web
Xoá trang web
@@ -350,8 +350,8 @@
Ngẫu nhiên
Sắp có…
Cam
- Bản CAM(Quay rạp)
- Bản HD(Đẹp)
+ Cam
+ Cam
HQ
HD
TS
@@ -375,7 +375,7 @@
Lỗi đường dẫn
Lỗi
Xóa bỏ chú thích chi tiết trong phụ đề
- Xóa quảng cáo trong phụ đề
+ Xóa bỏ nội dung thừa trong phụ đề
Lọc theo ngôn ngữ ưu tiên
Thêm
Trailer
@@ -660,7 +660,7 @@
Cập nhật tiện ích mở rộng
Không có tiện ích mở rộng nào được cập nhật.
Ngày phát sóng (Cũ nhất)
- Đã cập nhật thành công %d tiện ích mở rộng
+ Đã cập nhật thành công %d tiện ích mở rộng!
Xếp hạng (Cao nhất)
Cập nhật tiện ích mở rộng thủ công
Tập (Giảm dần)
@@ -748,17 +748,4 @@
Trực tiếp
Video
Xem trước
- Hiển thị đồng hồ thời gian thực
- Hiển thị đồng hồ thời gian thực ở phía trên màn hình. Áp dụng cho trang chủ và trình phát
- Cài đặt hồ sơ
- Ẩn nguồn có độ ưu tiên âm
- Ẩn nguồn có lỗi
-
- - %d liên kết bị ẩn
-
- Nhật ký gỡ lỗi
- Phát video đang dừng
- Nền
- Góc
- Giảm stress khi chọn nguồn
diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml
index 67e627004..56ec8f43e 100644
--- a/app/src/main/res/values-b+zh/strings.xml
+++ b/app/src/main/res/values-b+zh/strings.xml
@@ -352,8 +352,8 @@
随机
即将到来…
Cam
- CamRip
- CamHD
+ Cam
+ Cam
HQ
HD
TS
@@ -655,7 +655,7 @@
得分 %s
日期 %s
更新插件
- 成功更新了 %d 个插件
+ 成功更新了 %d 个插件!
没有插件被更新。
手动更新插件
集数(升序)
@@ -747,17 +747,4 @@
视频
预览
播放中
- 显示实时时钟
- 在屏幕顶部显示实时时钟。应用于主页和播放器
- 配置文件设置
- 隐藏负优先级的来源
- 隐藏有错误的来源
-
- - %d 条隐藏链接
-
- Logcat 🐈
- 开始暂停的视频
- 降低选择源时的压力
- 边缘
- 后台
diff --git a/app/src/main/res/values-b+be/strings.xml b/app/src/main/res/values-be/strings.xml
similarity index 99%
rename from app/src/main/res/values-b+be/strings.xml
rename to app/src/main/res/values-be/strings.xml
index 9fbf8a8d5..3d9200faf 100644
--- a/app/src/main/res/values-b+be/strings.xml
+++ b/app/src/main/res/values-be/strings.xml
@@ -125,7 +125,7 @@
Працягнуць прагляд
Выдаліць
Больш інфармацыі
- @string/home_play
+ \@string/home_play
Для карэктнай працы гэтага пастаўшчыка можа спатрэбіцца VPN
Гэты пастаўшчык — Torrent, рэкамендуецца VPN
Вэб-сайт не пастаўляе метададзеных, загрузіць відэа не ўдасца, калі на сайце яго няма.
diff --git a/app/src/main/res/values-b+ca/strings.xml b/app/src/main/res/values-ca/strings.xml
similarity index 99%
rename from app/src/main/res/values-b+ca/strings.xml
rename to app/src/main/res/values-ca/strings.xml
index 621243c78..495eb5e3c 100644
--- a/app/src/main/res/values-b+ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -123,7 +123,7 @@
Continua mirant
Eliminar
Més info
- @string/home_play
+ \@string/home_play
Potser necessiteu una VPN per que aquest proveïdor funcioni correctament
Aquest proveïdor és un Torrent, es recomana fer servir una VPN
El lloc no proveeix metadades, la càrrega del video fallarà si no existeix al lloc.
diff --git a/app/src/main/res/values-lo/strings.xml b/app/src/main/res/values-lo/strings.xml
deleted file mode 100644
index c1a3daa02..000000000
--- a/app/src/main/res/values-lo/strings.xml
+++ /dev/null
@@ -1,744 +0,0 @@
-
-
- %1$s ຕອນ %2$d
- ນັກສະແດງ: %s
- ຕອນທີ %d ຈະອອກໃນອີກ
- ຊີຊັນ %1$d ຕອນ %2$d ຈະອອກໃນອີກ
- %1$dd %2$dh %3$dm
- %1$dh %2$dm
- %dນ
- %1$dh %2$dm %3$ds
- %1$dm %2$ds
- %1$ds
- ໂປສເຕີ
- ໂປສເຕີ
- ໂປສເຕີຕອນ
- ໂປສເຕີຫຼັກ
- ສຸ່ມຖັດໄປ
- ກັບຄືນ
- ຫຼິ້ນແຕ່ເລີ່ມຕົ້ນ
- ປ່ຽນຜູ້ໃຫ້ບໍລິການ
- ຕົວຢ່າງພື້ນຫຼັງ
- ຄວາມໄວ (%.2fx)
- ຄະແນນ: %.1f
- ພົບການອັບເດດໃໝ່!\n%1$s -> %2$s
- ເນື້ອຫາເສີມ (Filler)
- %d ນາທີ
- CloudStream
- ຫຼິ້ນດ້ວຍ CloudStream
- ໜ້າຫຼັກ
- ຄົ້ນຫາ
- ດາວໂຫຼດ
- ຄິວດາວໂຫຼດ
- ການຕັ້ງຄ່າ
- ຄົ້ນຫາ…
- ຄົ້ນຫາ %s…
- ບໍ່ສາມາດໃຊ້ການຮັບຮູ້ສຽງໄດ້
- ເລີ່ມເວົ້າ…
- ບໍ່ມີຂໍ້ມູນ
- ຕົວເລືອກເພີ່ມເຕີມ
- ຕອນຕໍ່ໄປ
- ປະເພດ
- ແບ່ງປັນ
- ເປີດໃນບຣາວເຊີ
- ບຣາວເຊີ
- ຂ້າມການໂຫຼດ
- ກຳລັງໂຫຼດ…
- ກຳລັງເບິ່ງ
- ພັກໄວ້ກ່ອນ
- ເບິ່ງຈົບແລ້ວ
- ເລີກເບິ່ງ
- ແຜນທີ່ຈະເບິ່ງ
- ກຳລັງເບິ່ງຄືນ
- ຫຼິ້ນຮູບເງົາ
- ຫຼິ້ນຕົວຢ່າງ
- ຫຼິ້ນໄລຟ໌ສົດ
- ສະຕຣີມ Torrent
- ຫຼິ້ນຊີຣີທັງໝົດ
- ວິດີໂອນີ້ແມ່ນ Torrent, ຊຶ່ງໝາຍຄວາມວ່າການເຄື່ອນໄຫວຂອງທ່ານສາມາດຖືກຕິດຕາມໄດ້.\nກະລຸນາໝັ້ນໃຈວ່າທ່ານເຂົ້າໃຈກ່ຽວກັບ Torrent ກ່ອນທີ່ຈະດຳເນີນການຕໍ່.
- ແຫຼ່ງວິດີໂອ
- ຄຳບັນຍາຍ (Subtitles)
- ລອງເຊື່ອມຕໍ່ໃໝ່…
- ກັບຄືນ
- ຫຼິ້ນຕອນນີ້
- ດາວໂຫຼດ
- ດາວໂຫຼດແລ້ວ
- ກຳລັງດາວໂຫຼດ
- ຢຸດການດາວໂຫຼດຊົ່ວຄາວ
- ເລີ່ມການດາວໂຫຼດ
- ດາວໂຫຼດບໍ່ສຳເລັດ
- ຍົກເລີກການດາວໂຫຼດ
- ດາວໂຫຼດສຳເລັດ
- ເລືອກລາຍການທີ່ຈະລຶບ
- ຍັງບໍ່ມີລາຍການດາວໂຫຼດໃນຂະນະນີ້.
- ຍັງບໍ່ມີຄິວດາວໂຫຼດໃນຂະນະນີ້.
- ສາມາດເບິ່ງແບບອອຟໄລໄດ້
- ເລືອກທັງໝົດ
- ຍົກເລີກການເລືອກທັງໝົດ
- ເລີ່ມການອັບເດດ
- ສະຕຣີມຜ່ານເຄືອຂ່າຍ
- ເປີດວິດີໂອໃນເຄື່ອງ
- ເກີດຂໍ້ຜິດພາດໃນການໂຫຼດລິ້ງ
- ໂຫຼດລິ້ງໃໝ່ແລ້ວ
- ໜ່ວຍຄວາມຈຳພາຍໃນ
- ພາກ
- ຊັບ
- ລຶບໄຟລ໌
- ຫຼິ້ນໄຟລ໌
- ດາວໂຫຼດຕໍ່
- ຢຸດການດາວໂຫຼດ
- ຂໍ້ມູນເພີ່ມເຕີມ
- ເຊື່ອງ
- ຫຼິ້ນ
- ຂໍ້ມູນ
- ກັ່ນຕອງບຸກມາກ
- ບຸກມາກ
- ລຶບອອກ
- ຕັ້ງສະຖານະການເບິ່ງ
- ນຳໃຊ້
- ຄັດລອກ
- ປິດ
- ລ້າງຂໍ້ມູນ
- ບັນທຶກ
- ຊື່ ແລະ URL ຂອງ Repository
- ຄັດລອກແລ້ວ!
- ແຈ້ງເຕືອນຕອນໃໝ່
- ຄົ້ນຫາໃນສ່ວນເສີມອື່ນໆ
- ສະແດງລາຍການແນະນຳ
- ຄວາມໄວເຄື່ອງຫຼິ້ນ
- ຕັ້ງຄ່າຄຳບັນຍາຍ
- ສີຂໍ້ຄວາມ
- ສີຂອບຂໍ້ຄວາມ
- ສີພື້ນຫຼັງ
- ສີໜ້າຕ່າງ
- ປະເພດຂອບ
- ລະດັບຄວາມສູງຂອງຄຳບັນຍາຍ
- ຟອນທ໌
- ຂະໜາດຟອນທ໌
- ຄົ້ນຫາໂດຍໃຊ້ຜູ້ໃຫ້ບໍລິການ
- ຄົ້ນຫາໂດຍໃຊ້ປະເພດ
- ມອບ Benene ໃຫ້ຜູ້ພັດທະນາແລ້ວ %d ອັນ
- ຍັງບໍ່ໄດ້ມອບ Benene
- ເລືອກພາສາອັດຕະໂນມັດ
- ພາສາທີ່ຈະດາວໂຫຼດ
- ພາສາຂອງຄຳບັນຍາຍ
- ກົດຄ້າງເພື່ອຄືນຄ່າເລີ່ມຕົ້ນ
- ນຳເຂົ້າຟອນທ໌ໂດຍການວາງໄວ້ໃນ %s
- ເບິ່ງຕໍ່
- ລຶບອອກ
- ຂໍ້ມູນເພີ່ມເຕີມ
- \@string/home_play
- ອາດຈຳເປັນຕ້ອງໃຊ້ VPN ເພື່ອໃຫ້ຜູ້ໃຫ້ບໍລິການນີ້ເຮັດວຽກໄດ້ຢ່າງຖືກຕ້ອງ
- ຜູ້ໃຫ້ບໍລິການນີ້ແມ່ນ Torrent, ແນະນຳໃຫ້ໃຊ້ VPN
- ເວັບໄຊທ໌ບໍ່ໄດ້ໃຫ້ຂໍ້ມູນ Metadata, ວິດີໂອອາດຈະໂຫຼດບໍ່ໄດ້ຖ້າມັນບໍ່ມີຢູ່ໃນເວັບໄຊທ໌.
- ຄຳອະທິບາຍ
- ບໍ່ພົບເນື້ອເລື່ອງ
- ບໍ່ພົບຄຳອະທິບາຍ
- ສະແດງ Logcat 🐈
- ບັນທຶກ (Log)
- ຮູບຊ້ອນຮູບ (PiP)
- ຫຼິ້ນຕໍ່ເນື່ອງໃນເຄື່ອງຫຼິ້ນຂະໜາດນ້ອຍເທິງແອັບຯອື່ນໆ
- ປຸ່ມປັບຂະໜາດເຄື່ອງຫຼິ້ນ
- ລຶບຂອບສີດຳອອກ
- ຄຳບັນຍາຍ
- ຕັ້ງຄ່າຄຳບັນຍາຍໃນເຄື່ອງຫຼິ້ນ
- ຄຳບັນຍາຍ Chromecast
- ຕັ້ງຄ່າຄຳບັນຍາຍ Chromecast
- ຄວາມໄວໃນການຫຼິ້ນ
- ເພີ່ມຕົວເລືອກຄວາມໄວໃນເຄື່ອງຫຼິ້ນ
- ປັດເພື່ອເລື່ອນເບິ່ງ
- ປັດໄປທາງຊ້າຍ ຫຼື ຂວາ ເພື່ອຄວບຄຸມຕຳແໜ່ງໃນວິດີໂອ
- ປັດເພື່ອປ່ຽນການຕັ້ງຄ່າ
- ປັດຂຶ້ນ ຫຼື ລົງ ທາງດ້ານຊ້າຍ ຫຼື ຂວາ ເພື່ອປ່ຽນຄວາມສະຫວ່າງ ຫຼື ສຽງ
- ຫຼິ້ນຕອນຕໍ່ໄປອັດຕະໂນມັດ
- ເລີ່ມຕອນຕໍ່ໄປເມື່ອຕອນປັດຈຸບັນຈົບລົງ
- ແຕະສອງຄັ້ງເພື່ອເລື່ອນ
- ແຕະສອງຄັ້ງເພື່ອຢຸດ
- ໄລຍະເວລາການເລື່ອນ (ວິນາທີ)
- ແຕະສອງຄັ້ງທາງດ້ານຂວາ ຫຼື ຊ້າຍ ເພື່ອເລື່ອນໄປໜ້າ ຫຼື ຖອຍຫຼັງ
- ແຕະສອງຄັ້ງຢູ່ເຄິ່ງກາງເພື່ອຢຸດ
- ໃຊ້ຄວາມສະຫວ່າງຂອງລະບົບ
- ໃຊ້ຄວາມສະຫວ່າງຂອງລະບົບໃນເຄື່ອງຫຼິ້ນແທນການໃຊ້ແຜ່ນມົວສີດຳ
- ຄວາມສະຫວ່າງພິເສດ
- ເປີດໃຊ້ຟິວເຕີຄວາມສະຫວ່າງເມື່ອຄວາມສະຫວ່າງໜ້າຈໍເກີນ 100%
- extra_brightness_enabled
- ອັບເດດຄວາມຄືບໜ້າການເບິ່ງ
- ຊິງຄ໌ຄວາມຄືບໜ້າຂອງຕອນປັດຈຸບັນໂດຍອັດຕະໂນມັດ
- ກູ້ຄືນຂໍ້ມູນຈາກຂໍ້ມູນສຳຮອງ
- ສຳຮອງຂໍ້ມູນ
- ຄວາມຖີ່ໃນການສຳຮອງຂໍ້ມູນ
- ໂຫຼດໄຟລ໌ສຳຮອງແລ້ວ
- ບໍ່ສາມາດກູ້ຄືນຂໍ້ມູນຈາກໄຟລ໌ %s
- ບັນທຶກຂໍ້ມູນແລ້ວ
- ບໍ່ມີສິດໃນການເຂົ້າເຖິງບ່ອນເກັບຂໍ້ມູນ. ກະລຸນາລອງໃໝ່.
- ເກີດຂໍ້ຜິດພາດໃນການສຳຮອງຂໍ້ມູນ %s
- ຄົ້ນຫາ
- ຄັງເກັບ
- ບັນຊີ ແລະ ຄວາມປອດໄພ
- ອັບເດດ ແລະ ສຳຮອງຂໍ້ມູນ
- ຂໍ້ມູນ
- ການຄົ້ນຫາຂັ້ນສູງ
- ສະແດງຜົນການຄົ້ນຫາແຍກຕາມຜູ້ໃຫ້ບໍລິການ
- ຄຳແນະນຳການຄົ້ນຫາ
- ສະແດງຄຳແນະນຳໃນຂະນະທີ່ພິມຄົ້ນຫາ
- ລ້າງຄຳແນະນຳ
- ສະແດງຕອນເສີມ (filler) ສຳລັບອານິເມະ
- ສະແດງຂໍ້ມູນ Metadata ເທິງເຄື່ອງຫຼິ້ນ
- ສະແດງຕົວຢ່າງ (Trailer)
- ສະແດງໂປສເຕີຈາກ Kitsu
- ສະແດງລາຍຊື່ນັກສະແດງ
- ເຊື່ອງຄຸນນະພາບວິດີໂອທີ່ເລືອກໃນຜົນການຄົ້ນຫາ
- ອັບເດດປລັກອິນອັດຕະໂນມັດ
- ດາວໂຫຼດປລັກອິນອັດຕະໂນມັດ
- ເລືອກໂໝດການກັ່ນຕອງການດາວໂຫຼດປລັກອິນ
- ຕິດຕັ້ງປລັກອິນທັງໝົດທີ່ຍັງບໍ່ໄດ້ຕິດຕັ້ງຈາກ repository ທີ່ເພີ່ມໄວ້ໂດຍອັດຕະໂນມັດ.
- ສະແດງການອັບເດດແອັບຯ
- ຄົ້ນຫາການອັບເດດໃໝ່ໂດຍອັດຕະໂນມັດຫຼັງຈາກເປີດແອັບຯ.
- ຕັ້ງຄ່າໃໝ່ທັງໝົດ
- ຕິດຕັ້ງເວີຊັນທົດລອງ (Pre-release)
- ຕິດຕັ້ງເວີຊັນທົດລອງແລ້ວ.
- ບໍ່ສາມາດຕິດຕັ້ງເວີຊັນທົດລອງໄດ້.
- ຕົວຕິດຕັ້ງ APK
- ບາງອຸປະກອນບໍ່ຮອງຮັບຕົວຕິດຕັ້ງແພັກເກດແບບໃໝ່. ລອງໃຊ້ຕົວເລືອກແບບເກົ່າ (Legacy) ຖ້າບໍ່ສາມາດອັບເດດໄດ້.
- Github
- ແອັບຯ Light novel ໂດຍຜູ້ພັດທະນາດຽວກັນ
- ແອັບຯ Anime ໂດຍຜູ້ພັດທະນາດຽວກັນ
- ເຂົ້າຮ່ວມ Discord
- ມອບ Benene ໃຫ້ຜູ້ພັດທະນາ
- ມອບ Benene ແລ້ວ
- ພາສາຂອງແອັບຯ
- ຜູ້ໃຫ້ບໍລິການນີ້ບໍ່ຮອງຮັບ Chromecast
- ບໍ່ພົບລິ້ງ
- ຄັດລອກລິ້ງລົງໃນຄລິບບອດແລ້ວ
- ຫຼິ້ນຕອນນີ້
- ຄືນຄ່າເປັນຄ່າເລີ່ມຕົ້ນ
- ຊີຊັນ
- %1$s %2$d%3$s
- ບໍ່ມີຊີຊັນ
- ຕອນ
- ຕອນ
- %1$d-%2$d
- %1$d %2$s
- ຈະມາໃນອີກ %s
- S
- E
- ບໍ່ພົບຕອນ
- ລຶບ
- ລຶບໄຟລ໌
- ລຶບໄຟລ໌ທັງໝົດ
- ລຶບ (%1$d | %2$s)
- ຍົກເລີກ
- ຢຸດ
- ເລີ່ມ
- ບໍ່ສຳເລັດ
- ສຳເລັດ
- ຄຳເຕືອນ
- ຫຼິ້ນຕໍ່
- -30
- +30
- ນີ້ຈະລຶບ %s ຢ່າງຖາວອນ\nທ່ານແນ່ໃຈບໍ່?
- ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການລຶບລາຍການຕໍ່ໄປນີ້ຢ່າງຖາວອນ?\n\n%s
- ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການລຶບຕອນຕໍ່ໄປນີ້ໃນ %1$s ຢ່າງຖາວອນ?\n\n%2$s
- ທ່ານຈະລຶບທຸກຕອນໃນຊີຣີຕໍ່ໄປນີ້ຢ່າງຖາວອນ:\n\n%s
- ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການລຶບທຸກຕອນໃນຊີຣີຕໍ່ໄປນີ້ຢ່າງຖາວອນ?\n\n%s
- ເຫຼືອອີກ\n%d ນາທີ
- ເຫຼືອອີກ\n%s
- ກຳລັງສາຍ
- ຈົບແລ້ວ
- ສະຖານະ
- ປີ
- ຄະແນນ
- ຄວາມຍາວ
- ເວັບໄຊທ໌
- ເລື່ອງຫຍໍ້
- ຢູ່ໃນຄິວ
- ບໍ່ມີຄຳບັນຍາຍ
- ຄ່າເລີ່ມຕົ້ນ
- ວ່າງ
- ໃຊ້ໄປແລ້ວ
- ແອັບຯ
- ຮູບເງົາ
- ຊີຣີ
- ກາຕູນ
- ອານິເມະ
- Torrents
- ສາລະຄະດີ
- OVA
- ຊີຣີເອເຊຍ
- ໄລຟ໌ສົດ
- 18+ (NSFW)
- ອື່ນໆ
- ຮູບເງົາ
- ຊີຣີ
- ກາຕູນ
- ອານິເມະ
- OVA
- Torrent
- ສາລະຄະດີ
- ຊີຣີເອເຊຍ
- ໄລຟ໌ສົດ
- 18+ (NSFW)
- ວິດີໂອ
- ເພງ
- ໜັງສືສຽງ
- ສື່
- ສຽງ
- ພອດແຄສຕ໌
- ວິດີໂອ
- ຂໍ້ຜິດພາດຈາກແຫຼ່ງວິດີໂອ
- ຂໍ້ຜິດພາດທາງໄກ
- ຂໍ້ຜິດພາດໃນການປະມວນຜົນສັນຍານ
- ຂໍ້ຜິດພາດໃນການເຂົ້າລະຫັດ
- ຂໍ້ຜິດພາດ: ບໍ່ຮອງຮັບ
- ເກີດຂໍ້ຜິດພາດທີ່ບໍ່ຄາດຄິດໃນເຄື່ອງຫຼິ້ນ
- ການດາວໂຫຼດຜິດພາດ, ກະລຸນາກວດສອບສິດການເຂົ້າເຖິງບ່ອນເກັບຂໍ້ມູນ
- Chromecast ຕອນນີ້
- Chromecast mirror
- Cast mirror
- ຫຼິ້ນໃນແອັບຯ
- ຫຼິ້ນ mirror"
- ຫຼິ້ນໃນ %s
- ດາວໂຫຼດອັດຕະໂນມັດ
- ດາວໂຫຼດ mirror
- ໂຫຼດລິ້ງໃໝ່
- ດາວໂຫຼດຄຳບັນຍາຍ
- ປ້າຍກຳກັບຄຸນນະພາບ
- ປ້າຍກຳກັບພາກສຽງ
- ປ້າຍກຳກັບຄຳບັນຍາຍ
- ປ້າຍກຳກັບຄະແນນ
- ຊື່ເລື່ອງ
- ຂໍ້ຄວາມບອກຕອນ
- ເປີດ/ປິດ ອົງປະກອບ UI ເທິງໂປສເຕີ
- ບໍ່ພົບການອັບເດດ
- ກວດສອບການອັບເດດ
- ລັອກ
- ປັບຂະໜາດ
- ແຫຼ່ງວິດີໂອ
- ຂ້າມ OP
- ບໍ່ຕ້ອງສະແດງອີກ
- ຂ້າມການອັບເດດນີ້
- ອັບເດດ
- ຄຸນນະພາບການເບິ່ງທີ່ຕ້ອງການ (WiFi)
- ຄຸນນະພາບການເບິ່ງທີ່ຕ້ອງການ (ເນັດມືຖື)
- ຈຳນວນຕົວອັກສອນສູງສຸດຂອງຊື່ໃນເຄື່ອງຫຼິ້ນ
- ສະແດງຂໍ້ມູນເຄື່ອງຫຼິ້ນ
- ຂະໜາດບັບເຟີວິດີໂອ
- ຄວາມຍາວບັບເຟີວິດີໂອ
- ແຄດວິດີໂອໃນພື້ນທີ່ເກັບຂໍ້ມູນ
- ລ້າງແຄດວິດີໂອ ແລະ ຮູບພາບ
- ເມື່ອສະແດງເຄື່ອງຫຼິ້ນ - ໄລຍະການເລື່ອນ
- ໄລຍະການເລື່ອນທີ່ໃຊ້ເມື່ອເຫັນເຄື່ອງຫຼິ້ນ
- ເມື່ອເຊື່ອງເຄື່ອງຫຼິ້ນ - ໄລຍະການເລື່ອນ
- ໄລຍະການເລື່ອນທີ່ໃຊ້ເມື່ອເຄື່ອງຫຼິ້ນຖືກເຊື່ອງໄວ້
- ອາດເຮັດໃຫ້ແອັບຯຄ້າງໄດ້ ຖ້າຕັ້ງຄ່າສູງເກີນໄປໃນອຸປະກອນທີ່ມີແຮມໜ້ອຍ ເຊັ່ນ Android TV.
- ອາດເຮັດໃຫ້ເກີດບັນຫາ ຖ້າຕັ້ງຄ່າສູງເກີນໄປໃນອຸປະກອນທີ່ມີພື້ນທີ່ໜ້ອຍ ເຊັ່ນ Android TV.
- DNS over HTTPS
- ມີປະໂຫຍດໃນການຂ້າມການບລັອກຈາກ ISP
- GitHub Proxy
- ບໍ່ສາມາດເຊື່ອມຕໍ່ GitHub ໄດ້. ກຳລັງເປີດໃຊ້ jsDelivr proxy…
- ຂ້າມການບລັອກ URL ຂອງ GitHub ໂດຍໃຊ້ jsDelivr. ອາດຈະເຮັດໃຫ້ການອັບເດດຊ້າລົງ 2-3 ມື້.
- ຄັດລອກເວັບໄຊທ໌
- ລຶບເວັບໄຊທ໌
- ເພີ່ມເວັບໄຊທ໌ທີ່ຄືກັບເວັບໄຊທ໌ທີ່ມີຢູ່ແລ້ວ ແຕ່ໃຊ້ URL ອື່ນ
- ບ່ອນເກັບໄຟລ໌ດາວໂຫຼດ
- NGINX server URL
- ສະແດງອານິເມະ ພາກສຽງ/ຄຳບັນຍາຍ
- ພໍດີກັບໜ້າຈໍ
- ຍືດໃຫ້ເຕັມ
- ຊູມ
- ຂໍ້ຄວາມປະຕິເສດຄວາມຮັບຜິດຊອບ
- ການຂ້າມ ISP
- ລິ້ງ
- ອັບເດດແອັບຯ
- ສຳຮອງຂໍ້ມູນ
- ສ່ວນເສີມ
- ການກະທຳ
- ແຄດ
- Android TV
- ການຄວບຄຸມດ້ວຍທ່າທາງ
- ຄວາມປອດໄພ
- ບັນຊີ
- ຄຸນສົມບັດເຄື່ອງຫຼິ້ນ
- ຄຳບັນຍາຍ
- ຮູບແບບການສະແດງຜົນ
- ຄ່າເລີ່ມຕົ້ນ
- ຮູບລັກສະນະ
- ຄຸນສົມບັດ
- ທົ່ວໄປ
- ສະແດງໂມງເວລາຈິງ
- ສະແດງໂມງເວລາຈິງຢູ່ເທິງສຸດຂອງໜ້າຈໍ. ນຳໃຊ້ກັບໜ້າຫຼັກ ແລະ ເຄື່ອງຫຼິ້ນ
- ປຸ່ມສຸ່ມ
- ສະແດງປຸ່ມສຸ່ມໃນໜ້າທຳອິດ ແລະ ຫ້ອງສະໝຸດ
- ພາສາຂອງສ່ວນເສີມ
- ຮູບແບບແອັບ
- ສື່ທີ່ມັກ
- ເປີດເນື້ອຫາ 18+ (NSFW) ໃນສ່ວນເສີມທີ່ຮອງຮັບ
- ການເຂົ້າລະຫັດຄຳບັນຍາຍ
- ຜູ້ໃຫ້ບໍລິການ
- ທົດສອບຜູ້ໃຫ້ບໍລິການ
- ທົດສອບສ່ວນເສີມທັງໝົດ
- ການທົດສອບນີ້ແມ່ນສຳລັບຜູ້ພັດທະນາເທົ່ານັ້ນ ແລະ ບໍ່ໄດ້ຢືນຢັນ ຫຼື ປະຕິເສດການເຮັດວຽກຂອງສ່ວນເສີມໃດໆ.
- ຮູບແບບ
- ອັດຕະໂນມັດ
- ຮູບແບບ TV
- ຮູບແບບໂທລະສັບ
- ຮູບແບບຕົວຈຳລອງ
- ສີຫຼັກ
- ທີມຂອງແອັບ
- ຕຳແໜ່ງຊື່ຂອງໂປສເຕີ
- ວາງຊື່ໄວ້ກ້ອງໂປສເຕີ
- ລະຫັດຜ່ານ123
- ຊື່ຜູ້ໃຊ້
- hello@world.com
- 127.0.0.1
- ຊື່ເວັບໄຊໃໝ່
- https://example.com
- ລະຫັດພາສາ (lo)
- %1$s %2$s
- ບັນຊີ
- ອອກຈາກລະບົບ
- ເຂົ້າສູ່ລະບົບ
- ຢືນຢັນຕົວຕົນພາຍໃນເຄື່ອງ
- ສະຫຼັບບັນຊີ
- ເພີ່ມບັນຊີ
- ສ້າງບັນຊີ
- ເພີ່ມການຕິດຕາມ
- ເພີ່ມ %s ແລ້ວ
- ຊິງຄ໌ຂໍ້ມູນ
- ໃຫ້ຄະແນນແລ້ວ
- %d / 10
- /??
- /%d
- %s ຢືນຢັນຕົວຕົນແລ້ວ
- ບໍ່ສາມາດເຂົ້າສູ່ລະບົບໄດ້ທີ່ %s
- ປິດໃຊ້ງານ
- ບໍ່ມີ
- ປົກກະຕິ
- ທັງໝົດ
- ສູງສຸດ
- ຕ່ຳສຸດ
- ເສັ້ນຂອບ
- ຍຸບລົງ
- ເງົາ
- ນູນຂຶ້ນ
- ປັບເວລາຄຳບັນຍາຍ
- 1000 ms
- ຄວາມຊັກຊ້າຂອງຄຳບັນຍາຍ
- ໃຊ້ສິ່ງນີ້ຖ້າຄຳບັນຍາຍສະແດງໄວເກີນໄປ %d ms
- ໃຊ້ສິ່ງນີ້ຖ້າຄຳບັນຍາຍສະແດງຊ້າເກີນໄປ %d ms
- ບໍ່ມີການຊັກຊ້າຂອງຄຳບັນຍາຍ
- ເຝີດີມີຊີ້ນໝູອົບອັດແໜ້ນ
- ແນະນຳ
- ໂຫຼດ %s ແລ້ວ
- ໂຫຼດຈາກໄຟລ໌
- ໂຫຼດຈາກອິນເຕີເນັດ
- ໂຫຼດອັນທຳອິດທີ່ມີ
- ໄຟລ໌ທີ່ດາວໂຫຼດແລ້ວ
- ນັກສະແດງຫຼັກ
- ນັກສະແດງສົມທົບ
- ນັກສະແດງພື້ນຫຼັງ
- ແຫຼ່ງຂໍ້ມູນ
- ສຸ່ມ
- ໄວໆນີ້…
- ກ້ອງ
- ກ້ອງ
- ກ້ອງ
- HQ
- HD
- TS
- TC
- Blu-ray
- WP
- DVD
- 4K
- SD
- UHD
- HDR
- SDR
- ເວັບ
- ຮູບໂປສເຕີ
- ຮູບ QR Code
- ເຄື່ອງຫຼິ້ນ
- ຄວາມລະອຽດ ແລະ ຊື່
- ຊື່
- ຄວາມລະອຽດ
- ຂໍ້ມູນສື່
- ID ບໍ່ຖືກຕ້ອງ
- ຂໍ້ມູນບໍ່ຖືກຕ້ອງ
- URL ບໍ່ຖືກຕ້ອງ
- ຂໍ້ຜິດພາດ
- ເອົາຄຳອະທິບາຍສຽງອອກຈາກຄຳບັນຍາຍ
- ເອົາສ່ວນເກີນອອກຈາກຄຳບັນຍາຍ
- ກັ່ນຕອງຕາມພາສາສື່ທີ່ມັກ
- ເພີ່ມເຕີມ
- ຕົວຢ່າງໜັງ
- https://example.com/example.mp4
- Referer (ທາງເລືອກ)
- ຖັດໄປ
- ເບິ່ງວິດີໂອໃນພາສາເຫຼົ່ານີ້
- ກ່ອນໜ້າ
- ຂ້າມການຕັ້ງຄ່າ
- ປ່ຽນຮູບລັກສະນະຂອງແອັບໃຫ້ເໝາະສົມກັບອຸປະກອນຂອງທ່ານ
- ພາກສ່ວນໃດທີ່ເຈົ້າຢາກເບິ່ງ
- ສຳເລັດ
- ສ່ວນເສີມ
- ເພີ່ມບ່ອນເກັບຂໍ້ມູນ (Repository)
- ຊື່ບ່ອນເກັບຂໍ້ມູນ (ທາງເລືອກ)
- URL ບ່ອນເກັບຂໍ້ມູນ ຫຼື ລະຫັດສັ້ນ
- ໂຫຼດປລັກອິນແລ້ວ
- ດາວໂຫຼດປລັກອິນແລ້ວ
- ລົບປລັກອິນແລ້ວ
- ບໍ່ສາມາດໂຫຼດ %s ໄດ້
- 18+
- ເລີ່ມດາວໂຫຼດ %1$d %2$s…
- ດາວໂຫຼດ %1$d %2$s ສຳເລັດ
- %s ທັງໝົດຖືກດາວໂຫຼດແລ້ວ
- ບໍ່ພົບປລັກອິນໃນບ່ອນເກັບຂໍ້ມູນ
- ບໍ່ພົບບ່ອນເກັບຂໍ້ມູນ, ກວດເບິ່ງ URL ຫຼື ລອງໃຊ້ VPN
- ດາວໂຫຼດເປັນກຸ່ມ
- ປລັກອິນ
- ປລັກອິນ
- ນີ້ຈະລົບປລັກອິນທັງໝົດໃນບ່ອນເກັບຂໍ້ມູນນີ້ຄືກັນ
- ລົບບ່ອນເກັບຂໍ້ມູນ
- ລົບປລັກອິນ
- ດາວໂຫຼດລາຍຊື່ເວັບໄຊທີ່ເຈົ້າຕ້ອງການໃຊ້
- ດາວໂຫຼດແລ້ວ: %d
- ປິດການໃຊ້ງານ: %d
- ຍັງບໍ່ໄດ້ດາວໂຫຼດ: %d
- ອັບເດດແລ້ວ %d ປລັກອິນ
- CloudStream ບໍ່ມີເວັບໄຊຕິດຕັ້ງມາໃຫ້ແຕ່ທຳອິດ. ເຈົ້າຕ້ອງຕິດຕັ້ງເວັບໄຊຈາກບ່ອນເກັບຂໍ້ມູນ (Repositories). \n \nເຂົ້າຮ່ວມ Discord ຂອງພວກເຮົາ ຫຼື ຄົ້ນຫາທາງອອນລາຍ.
- ເບິ່ງບ່ອນເກັບຂໍ້ມູນຂອງຊຸມຊົນ
- ລາຍການສາທາລະນະ
- ເຮັດໃຫ້ຄຳບັນຍາຍທັງໝົດເປັນຕົວພິມໃຫຍ່
- ຄຳເຕືອນ: CloudStream ບໍ່ມີສ່ວນຮັບຜິດຊອບໃດໆໃນການໃຊ້ສ່ວນເສີມຈາກບຸກຄົນທີສາມ ແລະ ບໍ່ມີການຊ່ວຍເຫຼືອສຳລັບພວກມັນ!
- %s (ປິດໃຊ້ງານ)
- ແທຣັກ
- ແທຣັກສຽງ
- ແທຣັກວິດີໂອ
- ເລີ່ມແອັບໃໝ່ເພື່ອເບິ່ງການປ່ຽນແປງ.
- ເລີ່ມໃໝ່
- ຢຸດ
- ເປີດໂໝດປອດໄພ
- ສ່ວນເສີມທັງໝົດຖືກປິດເນື່ອງຈາກແອັບຂັດຂ້ອງ ເພື່ອຊ່ວຍໃຫ້ເຈົ້າຫາຕົວທີ່ເຮັດໃຫ້ເກີດບັນຫາ.
- ເບິ່ງຂໍ້ມູນການຂັດຂ້ອງ
- ຄະແນນ: %s
- ລາຍລະອຽດ
- ເວີຊັນ
- ສະຖານະ
- ຂະໜາດ
- ຜູ້ຂຽນ
- ທີ່ຮອງຮັບ
- ພາສາ
- ກະລຸນາຕິດຕັ້ງສ່ວນເສີມກ່ອນ
- HLS Playlist
- ເຄື່ອງຫຼິ້ນວິດີໂອທີ່ມັກ
- ເຄື່ອງຫຼິ້ນພາຍໃນແອັບ
- ຖາມທຸກຄັ້ງ
- ເລືອກອຸປະກອນ Cast
- ບໍ່ພົບແອັບ
- ທຸກພາສາ
- ຂ້າມ %s
- ເພງເປີດ
- ເພງປິດ
- ສະຫຼຸບຄວາມເດີມ
- ເພງປິດແບບປະສົມ
- ເພງເປີດແບບປະສົມ
- ລາຍຊື່ຜູ້ສ້າງ
- ຕົວຢ່າງຕອນຕໍ່ໄປ
- ບົດນຳ
- ລ້າງປະຫວັດ
- ປະຫວັດ
- ສະແດງປຸ່ມຂ້າມສຳລັບເພງເປີດ/ເພງປິດ
- ຂໍ້ຄວາມຫຼາຍເກີນໄປ. ບໍ່ສາມາດບັນທຶກໃສ່ຄລິບບອດໄດ້.
- ເກີດຂໍ້ຜິດພາດໃນການເຂົ້າເຖິງຄລິບບອດ, ກະລຸນາລອງໃໝ່.
- ເກີດຂໍ້ຜິດພາດໃນການຄັດລອກ, ກະລຸນາຄັດລອກ logcat ແລະ ຕິດຕໍ່ທີມງານ.
- ໝາຍວ່າເບິ່ງແລ້ວ
- ເອົາອອກຈາກປະຫວັດການເບິ່ງ
- ເຈົ້າແນ່ໃຈບໍ່ວ່າຕ້ອງການອອກ?
- ແມ່ນ
- ບໍ່
- ຕົກລົງ
- ຍົກເລີກ
- ເປີດບ່ອນເກັບຂໍ້ມູນ
- ປິດການປະຢັດແບັດເຕີຣີ
- ເພື່ອໃຫ້ການດາວໂຫຼດ ແລະ ການແຈ້ງເຕືອນລາຍການທີ່ຕິດຕາມບໍ່ຖືກຂັດຈັງຫວະ, CloudStream ຕ້ອງການການອະນຸຍາດໃຫ້ເຮັດວຽກຢູ່ພື້ນຫຼັງ. ເມື່ອກົດ ຕົກລົງ, ເຈົ້າຈະເຫັນໜ້າຕ່າງຂໍອະນຸຍາດ, ກະລຸນາກົດ \'ອະນຸຍາດ\'.\n\nໝາຍເຫດ: ການອະນຸຍາດນີ້ບໍ່ໄດ້ໝາຍຄວາມວ່າແອັບຈະກິນແບັດເຕີຣີ. ມັນຈະເຮັດວຽກຢູ່ພື້ນຫຼັງສະເພາະເວລາທີ່ຈຳເປັນ ເຊັ່ນ: ເວລາຮັບການແຈ້ງເຕືອນ ຫຼື ດາວໂຫຼດວິດີໂອ.
- ການໃຊ້ແບັດເຕີຣີຂອງແອັບຖືກຕັ້ງເປັນແບບບໍ່ຈຳກັດແລ້ວ
- ບໍ່ສາມາດເປີດຂໍ້ມູນແອັບ CloudStream ໄດ້.
- ກຳລັງດາວໂຫຼດອັບເດດແອັບ…
- ກຳລັງຕິດຕັ້ງອັບເດດແອັບ…
- ບໍ່ສາມາດຕິດຕັ້ງເວີຊັນໃໝ່ຂອງແອັບໄດ້
- ແບບເກົ່າ
- PackageInstaller
- ແອັບຈະຖືກອັບເດດເມື່ອທ່ານອອກ
- ຈັດຮຽງຕາມ
- ຈັດຮຽງ
- ຄະແນນ (ສູງ ຫາ ຕ່ຳ)
- ຄະແນນ (ຕ່ຳ ຫາ ສູງ)
- ອັບເດດ (ໃໝ່ ຫາ ເກົ່າ)
- ອັບເດດ (ເກົ່າ ຫາ ໃໝ່)
- ຕາມຕົວອັກສອນ (ກ ຫາ ຮ)
- ຕາມຕົວອັກສອນ (ຮ ຫາ ກ)
- ຕອນ (ນ້ອຍ ຫາ ຫຼາຍ)
- ຕອນ (ຫຼາຍ ຫາ ນ້ອຍ)
- ຄະແນນ (ສູງສຸດ)
- ຄະແນນ (ຕ່ຳສຸດ)
- ວັນທີສາຍ (ໃໝ່ສຸດ)
- ວັນທີສາຍ (ເກົ່າສຸດ)
- ຕອນ %s
- ຄະແນນ %s
- ວັນທີ %s
- ເລືອກຫ້ອງສະໝຸດ
- ເປີດດ້ວຍ
- ຫ້ອງສະໝຸດຂອງເຈົ້າຍັງວ່າງ :( \nເຂົ້າສູ່ລະບົບບັນຊີຫ້ອງສະໝຸດ ຫຼື ເພີ່ມລາຍການໃສ່ຫ້ອງສະໝຸດໃນເຄື່ອງ.
- ລາຍການນີ້ຍັງວ່າງ. ລອງສະຫຼັບໄປອັນອື່ນເບິ່ງ.
- ພົບໄຟລ໌ໂໝດປອດໄພ!\nຈະບໍ່ໂຫຼດສ່ວນເສີມໃດໆຕອນເລີ່ມຕົ້ນ ຈົນກວ່າໄຟລ໌ຈະຖືກລົບອອກ.
- ຄືນຄ່າ
- ກຳລັງອັບເດດລາຍການທີ່ຕິດຕາມ
- ຕິດຕາມແລ້ວ
- ຕິດຕາມ %s ແລ້ວ
- ຍົກເລີກການຕິດຕາມ %s ແລ້ວ
- ຕອນທີ %d ອອກແລ້ວ!
- ຕິດຕາມ
- ຍົກເລີກການຕິດຕາມ
- ໂປຣໄຟລ໌ %d
- ຕັ້ງຄ່າໂປຣໄຟລ໌
- ເຊື່ອງແຫຼ່ງຂໍ້ມູນທີ່ມີລຳດັບຄວາມສຳຄັນຕິດລົບ
- ເຊື່ອງແຫຼ່ງຂໍ້ມູນທີ່ມີຂໍ້ຜິດພາດ
-
- - ເຊື່ອງ %d ລິ້ງ
-
- Wi-Fi
- ຂໍ້ມູນມືຖື
- ຕັ້ງເປັນຄ່າເລີ່ມຕົ້ນ
- ໃຊ້ງານ
- ແກ້ໄຂ
- ລຳດັບຄວາມສຳຄັນຂອງແຫຼ່ງຂໍ້ມູນ
- ຕັດສິນໃຈວ່າຈະໃຫ້ຈັດຮຽງແຫຼ່ງວິດີໂອແນວໃດໃນເຄື່ອງຫຼິ້ນ
- ໂປຣໄຟລ໌
- ຊ່ວຍເຫຼືອ
- ຢູ່ບ່ອນນີ້ ເຈົ້າສາມາດປ່ຽນວິທີການຈັດຮຽງແຫຼ່ງຂໍ້ມູນ. ຖ້າວິດີໂອມີລຳດັບຄວາມສຳຄັນສູງກວ່າ ມັນຈະປາກົດຢູ່ທາງເທິງໃນການເລືອກແຫຼ່ງຂໍ້ມູນ. ຜົນລວມຂອງລຳດັບຄວາມສຳຄັນຂອງແຫຼ່ງຂໍ້ມູນ ແລະ ຄຸນນະພາບ ແມ່ນລຳດັບຄວາມສຳຄັນຂອງວິດີໂອ. \n\nແຫຼ່ງຂໍ້ມູນ A: 3 \nຄຸນນະພາບ B: 7 \nຈະມີລຳດັບຄວາມສຳຄັນລວມແມ່ນ 10. \n\nໝາຍເຫດ: ຖ້າຜົນລວມແມ່ນ 10 ຫຼື ຫຼາຍກວ່າ, ເຄື່ອງຫຼິ້ນຈະຂ້າມການໂຫຼດໂດຍອັດຕະໂນມັດເມື່ອລິ້ງນັ້ນຖືກໂຫຼດ!
- ຄຸນນະພາບ
- ພື້ນຫຼັງໂປຣໄຟລ໌
- UI ບໍ່ສາມາດສ້າງໄດ້ຢ່າງຖືກຕ້ອງ, ນີ້ແມ່ນບັກໃຫຍ່ ແລະ ຄວນລາຍງານທັນທີ %s
- ເຈົ້າໄດ້ລົງຄະແນນແລ້ວ
- ລາຍການທີ່ມັກ
- ເພີ່ມ %s ເຂົ້າໃນລາຍການທີ່ມັກແລ້ວ
- ເອົາ %s ອອກຈາກລາຍການທີ່ມັກແລ້ວ
- ເພີ່ມເຂົ້າໃນລາຍການທີ່ມັກ
- ເອົາອອກຈາກລາຍການທີ່ມັກ
- ພົບລາຍການທີ່ອາດຈະຊ້ຳກັນ
- ເພີ່ມ
- ແທນທີ່
- ແທນທີ່ທັງໝົດ
- ເບິ່ງຄືວ່າມີລາຍການທີ່ອາດຈະຊ້ຳກັນຢູ່ໃນຫ້ອງສະໝຸດຂອງເຈົ້າ: \'%s.\' \n\nເຈົ້າຕ້ອງການເພີ່ມລາຍການນີ້ຕໍ່ໄປ, ແທນທີ່ອັນເກົ່າ ຫຼື ຍົກເລີກການກະທຳ?
- ພົບລາຍການທີ່ອາດຈະຊ້ຳກັນໃນຫ້ອງສະໝຸດຂອງເຈົ້າ: \n\n%s \n\nເຈົ້າຕ້ອງການເພີ່ມລາຍການນີ້ຕໍ່ໄປ, ແທນທີ່ອັນເກົ່າ ຫຼື ຍົກເລີກການກະທຳ?
- ໃສ່ PIN
- ໃສ່ PIN ສຳລັບ %s
- ໃສ່ PIN ປັດຈຸບັນ
- ລັອກໂປຣໄຟລ໌
- PIN
- PIN ບໍ່ຖືກຕ້ອງ. ກະລຸນາລອງໃໝ່.
- PIN ຕ້ອງມີ 4 ຕົວ
- ເລືອກບັນຊີ
- ບໍ່ມີບັນຊີ
- ຈັດການບັນຊີ
- ແກ້ໄຂບັນຊີ
- ເຂົ້າສູ່ລະບົບໂດຍ %s
- ຂ້າມການເລືອກບັນຊີຕອນເລີ່ມຕົ້ນ
- ໃຊ້ບັນຊີເລີ່ມຕົ້ນ
- ໝຸນວິດີໂອ
- ສະແດງປຸ່ມສະຫຼັບການວາງແນວໜ້າຈໍ
- ເປີດການສະຫຼັບແນວໜ້າຈໍອັດຕະໂນມັດຕາມແນວວິດີໂອ
- ໝຸນອັດຕະໂນມັດ
- ມັກ
- ເລີກມັກ
- ປົດລັອກ CloudStream
- ລັອກດ້ວຍໄບໂອເມທຣິກ (ລາຍນິ້ວມື/ໃບໜ້າ)
- ການຢືນຢັນດ້ວຍລະຫັດຜ່ານ/PIN
- ອຸປະກອນນີ້ບໍ່ຮອງຮັບການຢືນຢັນດ້ວຍໄບໂອເມທຣິກ
- ປົດລັອກແອັບດ້ວຍລາຍນິ້ວມື, ໃບໜ້າ, PIN, ຮູບແບບ ຫຼື ລະຫັດຜ່ານ.
- ຫຼັງຈາກພະຍາຍາມລົ້ມເຫຼວຫຼາຍຄັ້ງ, ໜ້າຕ່າງຈະປິດລົງ. ໃຫ້ເລີ່ມແອັບໃໝ່ເພື່ອລອງອີກຄັ້ງ.
- ຂໍ້ມູນ CloudStream ຂອງທ່ານຖືກສຳຮອງໄວ້ແລ້ວ. ເຖິງແມ່ນວ່າມັນຈະເກີດຂຶ້ນໄດ້ຍາກ, ແຕ່ອຸປະກອນແຕ່ລະເຄື່ອງອາດເຮັດວຽກຕ່າງກັນ. ໃນກໍລະນີທີ່ທ່ານຖືກລັອກບໍ່ໃຫ້ເຂົ້າເຖິງແອັບ, ໃຫ້ລ້າງຂໍ້ມູນແອັບທັງໝົດ ແລະ ກູ້ຄືນຈາກໄຟລ໌ສຳຮອງ. ພວກເຮົາຂໍອະໄພໃນຄວາມບໍ່ສະດວກ.
- ຕັ້ງຄ່າໃໝ່
- CloudStream Wiki
- ເຂົ້າໄປທີ່ %s ໃນມືຖື ຫຼື ຄອມພິວເຕີຂອງທ່ານ ແລະ ໃສ່ລະຫັດຂ້າງເທິງ
- ບໍ່ສາມາດຮັບລະຫັດ PIN ຂອງອຸປະກອນໄດ້, ລອງຢືນຢັນຕົວຕົນພາຍໃນເຄື່ອງ
- ລະຫັດ PIN ໝົດອາຍຸແລ້ວ!
- ລະຫັດຈະໝົດອາຍຸໃນ %1$dm %2$ds
- ວັນທີວາງຈຳໜ່າຍ (ໃໝ່ ຫາ ເກົ່າ)
- ວັນທີວາງຈຳໜ່າຍ (ເກົ່າ ຫາ ໃໝ່)
- ເຊື່ອງຊື່ປຸ່ມຄວບຄຸມໃນເຄື່ອງຫຼິ້ນ
- ຕົວຢ່າງຮູບໃນແຖບເລື່ອນເວລາ
- ເປີດສະແດງຮູບຕົວຢ່າງເທິງແຖບເລື່ອນເວລາ
- ຍັງບໍ່ໄດ້ໂຫຼດຄຳບັນຍາຍ
- ຕຳແໜ່ງໂຟນເດີສຳຮອງຂໍ້ມູນ
- ກຳນົດເອງ
- ຢືນຢັນກ່ອນອອກ
- ສະແດງໜ້າຕ່າງຢືນຢັນກ່ອນອອກຈາກແອັບ
- ສະແດງ
- ບໍ່ສະແດງ
- ຂະໜາດເສັ້ນຂອບ
- ເປີດໃຊ້ torrent ໃນ ຕັ້ງຄ່າ/ຜູ້ໃຫ້ບໍລິການ/ສື່ທີ່ມັກ
- ເລີ່ມແອັບໃໝ່ ແລະ ກົດຍອມຮັບ Stream Torrent ເພື່ອດຳເນີນການຕໍ່.
- ການຖອດລະຫັດດ້ວຍຊອບແວ
- ການຖອດລະຫັດດ້ວຍຊອບແວຊ່ວຍໃຫ້ຫຼິ້ນໄຟລ໌ທີ່ອຸປະກອນບໍ່ຮອງຮັບໄດ້, ແຕ່ອາດເຮັດໃຫ້ກະຕຸກ ຫຼື ບໍ່ສະຖຽນໃນຄວາມລະອຽດສູງ.
- ລະດັບສຽງເກີນ 100% ແລ້ວ
- ເລື່ອນຂຶ້ນອີກຄັ້ງເພື່ອໃຫ້ສຽງດັງເກີນ 100%
- ອັບເດດປລັກອິນ
- ອັບເດດປລັກອິນດ້ວຍຕົນເອງ
- ເລີ່ມຂະບວນການອັບເດດປລັກອິນ!
- ອັບເດດ %d ປລັກອິນສຳເລັດແລ້ວ
- ບໍ່ມີປລັກອິນໃດຖືກອັບເດດ.
- ການແຈ້ງເຕືອນເຄື່ອງຫຼິ້ນ
- ການແຈ້ງເຕືອນເຄື່ອງຫຼິ້ນສຳລັບຄວບຄຸມການຫຼິ້ນຈາກພື້ນຫຼັງ
- ຝັງໃນຕົວ
- ອອນລາຍ
- ເຮັດໃຫ້ຄຳບັນຍາຍທັງໝົດເປັນຕົວເຂັ້ມ
- ເຮັດໃຫ້ຄຳບັນຍາຍທັງໝົດເປັນຕົວອຽງ
- ຄວາມມົນຂອງພື້ນຫຼັງ
- ຈຳນວນລາຍການທີ່ສາມາດດາວໂຫຼດພ້ອມກັນໄດ້
- ດາວໂຫຼດຂະໜານ
- ການເຊື່ອມຕໍ່ພ້ອມກັນ
- ຈຳນວນການເຊື່ອມຕໍ່ພ້ອມກັນທີ່ແຕ່ລະການດາວໂຫຼດສາມາດໃຊ້ໄດ້
- ໄປທີ່ລາຍການດາວໂຫຼດ
- ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ. \n\nກະລຸນາເຊື່ອມຕໍ່ອິນເຕີເນັດແລ້ວລອງໃໝ່, ຫຼື ເບິ່ງໄຟລ໌ທີ່ດາວໂຫຼດໄວ້ໃນຂະນະທີ່ອອບລາຍ.
- ປ່ຽນຂອບເຂດຂອງໜ້າຈໍ
- Overscan
- ປ່ຽນຂະໜາດຂອງໂປສເຕີ
- ຂະໜາດໂປສເຕີ
- ກົດຄ້າງເພື່ອສະຫຼັບຄວາມໄວ
- ກົດຄ້າງໄວ້ເພື່ອຫຼິ້ນດ້ວຍຄວາມໄວ 2 ເທົ່າ
- ແກ້ໄຂຮູບໂປຣໄຟລ໌
- ໃສ່ URL ຮູບໂປຣໄຟລ໌
- ບໍ່ພົບ URL
- URL ຫຼື ຮູບພາບບໍ່ຖືກຕ້ອງ
- ອັບເດດຮູບພາບສຳເລັດແລ້ວ
- ໝາຍວ່າເບິ່ງແລ້ວຈົນຮອດຕອນນີ້
- ເອົາເຄື່ອງໝາຍເບິ່ງແລ້ວອອກຈົນຮອດຕອນນີ້
- ໂຫຼດໃໝ່ແລ້ວ
- ໂຫຼດຜູ້ໃຫ້ບໍລິການໃໝ່
- ຊື່
- ຊື່ແຫຼ່ງຂໍ້ມູນ
- ຄວາມລະອຽດ ແລະ ຊື່
- ດາວໂຫຼດທັງໝົດ
- ຍົກເລີກທັງໝົດ
- ເຈົ້າຕ້ອງການດາວໂຫຼດຕອນ %s ບໍ່?
- ເຈົ້າຕ້ອງການຍົກເລີກການດາວໂຫຼດທີ່ຄ້າງຢູ່ໃນຄິວທັງໝົດບໍ່?
- ການຈັດວາງຄຳບັນຍາຍ
- ລຸ່ມຊ້າຍ
- ລຸ່ມກາງ
- ລຸ່ມຂວາ
- ກາງຊ້າຍ
- ກາງ
- ກາງຂວາ
- ເທິງຊ້າຍ
- ເທິງກາງ
- ເທິງຂວາ
-
- - ກຳລັງດາວໂຫຼດ %d ລາຍການ
-
-
- - %d ລາຍການຢູ່ໃນຄິວ
-
- ຖ່າຍທອດສົດ
-
diff --git a/app/src/main/res/values-b+sq/strings.xml b/app/src/main/res/values-sq/strings.xml
similarity index 98%
rename from app/src/main/res/values-b+sq/strings.xml
rename to app/src/main/res/values-sq/strings.xml
index d083809bb..a13f87bfa 100644
--- a/app/src/main/res/values-b+sq/strings.xml
+++ b/app/src/main/res/values-sq/strings.xml
@@ -117,7 +117,7 @@
Madhësia e shkrimit
Kërko duke përdorur ofruesit
Kërko duke përdorur tipet
- %d Banane të dhëna për developer-at
+ %d Banane të dhëna per developer-at
Asnjë Banane e dhënë
Zgjedhje automatike e gjuhës
Shkarko gjuhët
@@ -127,7 +127,7 @@
Vazhdo shikimin
Hiq
Më shumë informacion
- @string/home_play
+ \@string/home_play
Një VPN mund të nevojitet që ky ofrues të funksionojë në rregull
Ky ofrues është Torrent, rekomandohet një VPN
Metadata nuk ofrohet nga kjo faqe, ngarkimi i videos do të dështojë nëse nuk ekziston në këtë faqe.
@@ -216,7 +216,7 @@
%1$s %2$d%3$s
Asnje sezon
Episodi
- Episode
+ Episodet
%1$d-%2$d
%1$d %2$s
Vazhdon në %s
@@ -269,7 +269,7 @@
NSFW
Tjera
Film
- Serial
+ Seri
Vizatimorë
Anime
OVA
@@ -679,7 +679,7 @@
Përditëso shtesat
Përditëso shtesat manualisht
Filloi procesi i përditësimit të shtesave!
- %d Shtes(a) u përditësuan me sukses
+ %d Shtes(at) u përditësuan me sukses!
Nuk u përditësua asnjë shtesë.
Njoftimet e luajtësit
Njoftimi për kontrollin e luajtjes nga sfondi
@@ -735,13 +735,4 @@
- %d shkarkime në radhë
Live
- Cilësimet e profil-it
- Fshih burimet me prioritet negativ
- Fshih burimet me error-e
-
- - %d link i fshehur
- - %d linqe të fshehura
-
- Shfaq orën në kohë reale
- Shfaq një orë në krye të ekranit. Aplikohet në faqen kryesore dhe në luajtës
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 53b29475b..7c71b5571 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -70,7 +70,6 @@
-
diff --git a/app/src/main/res/values/donottranslate-strings.xml b/app/src/main/res/values/donottranslate-strings.xml
index a1334f0b1..6a4c82713 100644
--- a/app/src/main/res/values/donottranslate-strings.xml
+++ b/app/src/main/res/values/donottranslate-strings.xml
@@ -101,7 +101,6 @@
opensubtitles_key
subdl_key
animeskip_key
- tv_layout_clock_key
pref_category_security_key
pref_category_gestures_key
@@ -142,16 +141,16 @@
Any legal issues regarding the content on this application
should be taken up with the actual file hosts and providers themselves as we are not affiliated with them.
- \n\nIn case of copyright infringement, please directly contact the responsible parties or the streaming websites.
+ In case of copyright infringement, please directly contact the responsible parties or the streaming websites.
- \n\nThe app is purely for educational and personal use.
+ The app is purely for educational and personal use.
- \n\nCloudStream does not host any content on the app, and has no control over what media is put up or taken down.
+ CloudStream does not host any content on the app, and has no control over what media is put up or taken down.
CloudStream functions like any other search engine, such as Google. CloudStream does not host, upload or
- manage any videos, films or content. It simply crawls, aggregates and displays links in a convenient,
+ manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient,
user-friendly interface.
- \n\nIt merely scrapes 3rd-party websites that are publicly accessible via any regular web browser. It is the
+ It merely scrapes 3rd-party websites that are publicly accessible via any regular web browser. It is the
responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use
CloudStream at your own risk.
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 66eb3533c..31cf951cf 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -115,7 +115,6 @@
Outline Color
Background Color
Window Color
- Edge
Edge Type
Subtitle Elevation
Font
@@ -140,7 +139,6 @@
No Plot Found
No Description Found
Show Logcat 🐈
- Logcat 🐈
Log
Picture-in-picture
Continues playback in a miniature player on top of other apps
@@ -158,8 +156,6 @@
Slide up or down on the left or right side to change brightness or volume
Autoplay next episode
Start the next episode when the current one ends
- Start videos paused
- Reduce stress when selecting sources
Double tap to seek
Double tap to pause
Player seek amount (Seconds)
@@ -378,8 +374,6 @@
Looks
Features
General
- Show real time clock
- Show a real time clock at the top of the screen. Applies to the homepage and player
Random Button
Show random button on Homepage and Library
Extension languages
@@ -462,8 +456,8 @@
Random
Coming soon…
Cam
- CamRip
- CamHD
+ Cam
+ Cam
HQ
HD
TS
@@ -489,7 +483,7 @@
Invalid URL
Error
Remove closed captions from subtitles
- Remove ads from subtitles
+ Remove bloat from subtitles
Filter by preferred media language
Extras
Trailer
@@ -628,13 +622,6 @@
Subscribe
Unsubscribe
Profile %d
- Profile settings
- Hide sources with a negative priority
- Hide sources with errors
-
- - %d hidden link
- - %d hidden links
-
Wi-Fi
Mobile data
Set default
@@ -737,7 +724,7 @@
Update plugins manually
Starting plugin update process!
- Successfully updated %d plugin(s)
+ Successfully updated %d plugin(s)!
No plugins were updated.
Player notifications
The player notification for controlling the playback from the background
@@ -746,7 +733,6 @@
Make all subtitles bold
Make all subtitles italic
Background Radius
- Background
How many different items that can be downloaded in parallel
Parallel downloads
Concurrent connections
@@ -797,4 +783,5 @@
- %d downloads queued
Live
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index a5f8492dc..a63b47264 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -63,7 +63,6 @@
- @color/iconColor
- @color/white
- @color/black
- - @color/grayTextColor
- ?android:attr/selectableItemBackground
- @style/CustomPreferenceThemeOverlay
@@ -179,7 +178,6 @@
- @color/lightGrayTextColor
- @color/black
- @color/white
- - @color/transparent
- @color/blackText
- true
- true
diff --git a/app/src/main/res/xml/settings_ui.xml b/app/src/main/res/xml/settings_ui.xml
index 774724f99..1b516ffa3 100644
--- a/app/src/main/res/xml/settings_ui.xml
+++ b/app/src/main/res/xml/settings_ui.xml
@@ -89,12 +89,6 @@
android:icon="@drawable/metadata_overlay_icon"
android:key="@string/show_player_metadata_key"
android:title="@string/show_player_metadata_overlay" />
-
{
- jvmArgs("--enable-native-access=ALL-UNNAMED")
-}
-
-compose.desktop {
- application {
- mainClass = "com.lagradost.cloudstream4.MainKt"
- nativeDistributions {
- targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
- packageName = "CloudStream"
- packageVersion = "1.0.0"
-
- val iconsRoot = project.file("desktop-icons")
- macOS {
- // iconFile.set(iconsRoot.resolve("icon-mac.icns"))
- }
- windows {
- iconFile.set(iconsRoot.resolve("icon-windows.ico"))
- // menuGroup = "Compose Examples"
- // see https://wixtoolset.org/documentation/manual/v3/howtos/general/generate_guids.html
- // upgradeUuid = ""
- }
- linux {
- iconFile.set(iconsRoot.resolve("icon-linux.png"))
- }
- }
-
- //buildTypes.release.proguard {
- // configurationFiles.from(project.file("rules.pro"))
- //}
- }
-}
\ No newline at end of file
diff --git a/desktopApp/src/desktop-icons/icon-linux.png b/desktopApp/src/desktop-icons/icon-linux.png
deleted file mode 100644
index 394459001..000000000
Binary files a/desktopApp/src/desktop-icons/icon-linux.png and /dev/null differ
diff --git a/desktopApp/src/desktop-icons/icon-windows.ico b/desktopApp/src/desktop-icons/icon-windows.ico
deleted file mode 100644
index defddc2e9..000000000
Binary files a/desktopApp/src/desktop-icons/icon-windows.ico and /dev/null differ
diff --git a/desktopApp/src/jvmMain/kotlin/com/lagradost/cloudstream4/Main.kt b/desktopApp/src/jvmMain/kotlin/com/lagradost/cloudstream4/Main.kt
deleted file mode 100644
index 4ff13fe80..000000000
--- a/desktopApp/src/jvmMain/kotlin/com/lagradost/cloudstream4/Main.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.lagradost.cloudstream4
-
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.Text
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.window.Window
-import androidx.compose.ui.window.application
-import com.lagradost.cloudstream4.compose.BlackButton
-import com.lagradost.cloudstream4.compose.LocalFocusOutlineDefault
-import com.lagradost.cloudstream4.compose.WhiteButton
-import com.lagradost.cloudstream4.generated.resources.Res
-import com.lagradost.cloudstream4.generated.resources.app_name
-import com.lagradost.cloudstream4.generated.resources.default_icon
-import com.lagradost.cloudstream4.generated.resources.preview
-import com.lagradost.cloudstream4.theme.CloudStreamTheme
-import com.lagradost.cloudstream4.theme.CloudStreamThemeMode
-import com.mihon.presentation.settings.widget.SwitchPreferenceWidget
-import org.jetbrains.compose.resources.painterResource
-import org.jetbrains.compose.resources.stringResource
-
-fun main() = application {
- Window(
- onCloseRequest = ::exitApplication,
- title = stringResource(Res.string.app_name),
- icon = painterResource(Res.drawable.default_icon)
- ) {
- CloudStreamTheme(mode = CloudStreamThemeMode.Dark) {
- CompositionLocalProvider(LocalFocusOutlineDefault provides false) {
- Scaffold(
- containerColor = MaterialTheme.colorScheme.background,
- contentColor = MaterialTheme.colorScheme.onBackground
- ) {
- Column {
- Text("Hello, World!")
- Row {
- WhiteButton("Hello in White") {
- }
- BlackButton("Hello in Black") {
- }
- }
- var checked by remember { mutableStateOf(false) }
- SwitchPreferenceWidget(
- title = "hello", subtitle = "world", icon = painterResource(
- Res.drawable.preview
- ),
- checked = checked,
- onCheckedChanged = { checked = !checked }
- )
- }
- }
- }
-
- }
- }
-}
\ No newline at end of file
diff --git a/desktopApp/src/readme.md b/desktopApp/src/readme.md
deleted file mode 100644
index 0bebb8638..000000000
--- a/desktopApp/src/readme.md
+++ /dev/null
@@ -1 +0,0 @@
-Based on https://github.com/JetBrains/compose-multiplatform/tree/master/examples/imageviewer/desktopApp
\ No newline at end of file
diff --git a/fastlane/metadata/android/bn/changelogs/2.txt b/fastlane/metadata/android/bn/changelogs/2.txt
deleted file mode 100644
index 91032cb8a..000000000
--- a/fastlane/metadata/android/bn/changelogs/2.txt
+++ /dev/null
@@ -1 +0,0 @@
-- চেঞ্জলগ যোগ করা হয়েছে!
diff --git a/fastlane/metadata/android/bn/full_description.txt b/fastlane/metadata/android/bn/full_description.txt
deleted file mode 100644
index e1866f54f..000000000
--- a/fastlane/metadata/android/bn/full_description.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-CloudStream-3 দিয়ে সিনেমা, টিভি-সিরিজ ও অ্যানিমে স্ট্রিম ও ডাউনলোড করা যায়।
-
-অ্যাপে কোনো বিজ্ঞাপন ও অ্যানালিটিক্স নেই এবং
-এটি একাধিক ট্রেলার ও মুভি সাইট সাপোর্ট করে, আরও আছে যেমন—
-বুকমার্ক
-সাবটাইটেল ডাউনলোড
-ক্রোমকাস্ট সাপোর্ট
diff --git a/fastlane/metadata/android/bn/short_description.txt b/fastlane/metadata/android/bn/short_description.txt
deleted file mode 100644
index 3d62da485..000000000
--- a/fastlane/metadata/android/bn/short_description.txt
+++ /dev/null
@@ -1 +0,0 @@
-সিনেমা, টিভি সিরিজ ও অ্যানিমে স্ট্রিম ও ডাউনলোড করুন।
diff --git a/fastlane/metadata/android/bn/title.txt b/fastlane/metadata/android/bn/title.txt
deleted file mode 100644
index dde89d58f..000000000
--- a/fastlane/metadata/android/bn/title.txt
+++ /dev/null
@@ -1 +0,0 @@
-CloudStream
diff --git a/fastlane/metadata/android/lo-LA/changelogs/2.txt b/fastlane/metadata/android/lo-LA/changelogs/2.txt
deleted file mode 100644
index a9c33c045..000000000
--- a/fastlane/metadata/android/lo-LA/changelogs/2.txt
+++ /dev/null
@@ -1 +0,0 @@
-- ເພີ່ມບັນທຶກການປ່ຽນແປງແລ້ວ!
diff --git a/fastlane/metadata/android/lo-LA/full_description.txt b/fastlane/metadata/android/lo-LA/full_description.txt
deleted file mode 100644
index 6c1396482..000000000
--- a/fastlane/metadata/android/lo-LA/full_description.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-CloudStream-3 ຊ່ວຍໃຫ້ທ່ານສາມາດເບິ່ງ ແລະ ດາວໂຫຼດຮູບເງົາ, ຊີຣີສ໌ ແລະ ອານິເມະ.
-
-ແອັບນີ້ບໍ່ມີໂຄສະນາ ແລະ ບໍ່ມີການເກັບກຳຂໍ້ມູນການໃຊ້ງານ ແລະ
-
-ຮອງຮັບເວັບໄຊຕົວຢ່າງຮູບເງົາ ແລະ ເວັບໄຊຮູບເງົາຫຼາຍແຫ່ງ, ແລະ ອື່ນໆ ເຊັ່ນ:
-
-ລາຍການທີ່ບັນທຶກໄວ້
-
-ດາວໂຫຼດຄຳບັນຍາຍ
-
-ຮອງຮັບ Chromecast
diff --git a/fastlane/metadata/android/lo-LA/short_description.txt b/fastlane/metadata/android/lo-LA/short_description.txt
deleted file mode 100644
index 507aa8a70..000000000
--- a/fastlane/metadata/android/lo-LA/short_description.txt
+++ /dev/null
@@ -1 +0,0 @@
-ເບິ່ງ ແລະ ດາວໂຫຼດຮູບເງົາ, ຊີຣີສ໌ ແລະ ອານິເມະ.
diff --git a/fastlane/metadata/android/lo-LA/title.txt b/fastlane/metadata/android/lo-LA/title.txt
deleted file mode 100644
index dde89d58f..000000000
--- a/fastlane/metadata/android/lo-LA/title.txt
+++ /dev/null
@@ -1 +0,0 @@
-CloudStream
diff --git a/fastlane/metadata/android/sq/full_description.txt b/fastlane/metadata/android/sq/full_description.txt
index a7881e62e..f9d6e6f9e 100644
--- a/fastlane/metadata/android/sq/full_description.txt
+++ b/fastlane/metadata/android/sq/full_description.txt
@@ -1,7 +1,7 @@
CloudStream-3 ju lejon të shikoni dhe shkarkoni filma, seriale televizive dhe anime.
Aplikacioni vjen pa asnjë reklamë dhe analitikë
-dhe suporton faqe të shumta trailer-ash dhe filmash, dhe më shumë, p.sh.
+dhe suporton faqe të shumta trailerash dhe filmash dhe më shumë, p.sh.
Të ruajturat
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c6f0ee55a..e38895451 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,7 +1,7 @@
# https://docs.gradle.org/current/userguide/plugins.html#sec:version_catalog_plugin_application
# https://docs.gradle.org/current/userguide/dependency_versions.html#sec:strict-version
[versions]
-activity = "1.13.0"
+activityKtx = "1.13.0"
androidGradlePlugin = "9.1.1"
animeDb = "1.0.2"
annotation = "1.10.0"
@@ -55,14 +55,8 @@ tvprovider = "1.1.0"
video = "1.0.0"
workRuntimeKtx = "2.11.2"
zipline = "1.27.0"
-composeMaterial3 = "1.12.0-alpha03"
-composeMultiplatform = "1.12.0"
-lifecycleViewmodelCompose = "2.11.0"
-# jvmTarget = "11" is needed for compose, otherwise you will get
-# "Cannot inline bytecode built with JVM target 11 into bytecode" or
-# "Error was captured in composition. java.lang.AbstractMethodError"
-jvmTarget = "11"
+jvmTarget = "1.8"
jdkToolchain = "17"
minSdk = "23"
compileSdk = "37"
@@ -72,14 +66,12 @@ versionCode = "68"
versionName = "4.8.0"
[libraries]
-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity" }
-activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activity" }
+activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" }
anime-db = { module = "com.github.recloudstream:anime-db", version.ref = "animeDb" }
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" }
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
-coil-network-ktor3 = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" }
coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" }
colorpicker = { module = "com.github.recloudstream:color-picker-android", version.ref = "colorpicker" }
conscrypt-android = { module = "org.conscrypt:conscrypt-android", version.ref = "conscryptAndroid" }
@@ -104,16 +96,12 @@ kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref =
kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlinGradlePlugin" }
kotlinx-atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "kotlinxAtomicfu" }
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" }
-kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
kotlinx-io-core = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = "kotlinxIOCore" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
ksoup = { module = "com.fleeksoft.ksoup:ksoup", version.ref = "ksoup" }
-ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" }
-ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
-ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
ktor-http = { module = "io.ktor:ktor-http", version.ref = "ktor" }
lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleKtx" }
lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleKtx" }
@@ -148,16 +136,6 @@ video = { module = "com.google.android.mediahome:video", version.ref = "video" }
work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" }
zipline = { module = "app.cash.zipline:zipline-android", version.ref = "zipline" }
-compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "composeMultiplatform" }
-compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "composeMaterial3" }
-compose-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "composeMultiplatform" }
-compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeMultiplatform" }
-compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "composeMultiplatform" }
-compose-ui-tooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "composeMultiplatform" }
-compose-ui-tooling-preview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "composeMultiplatform" }
-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
-coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
-
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
android-lint = { id = "com.android.lint", version.ref = "androidGradlePlugin" }
@@ -168,9 +146,6 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm" , version.ref = "kotlinGradlePlug
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlinGradlePlugin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinGradlePlugin" }
-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlinGradlePlugin" }
-compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
-
[bundles]
coil = ["coil", "coil-network-okhttp"]
cryptography = ["cryptography-core", "cryptography-provider-optimal"]
@@ -178,4 +153,3 @@ lifecycle = ["lifecycle-livedata-ktx", "lifecycle-viewmodel-ktx"]
media3 = ["media3-cast", "media3-common", "media3-container", "media3-datasource-cronet", "media3-datasource-okhttp", "media3-exoplayer", "media3-exoplayer-dash", "media3-exoplayer-hls", "media3-session", "media3-ui"]
navigation = ["navigation-fragment-ktx", "navigation-ui-ktx"]
nextlib = ["nextlib-media3ext", "nextlib-mediainfo"]
-compose = ["compose-foundation", "compose-material3", "compose-resources", "compose-runtime", "compose-ui", "compose-ui-tooling", "compose-ui-tooling-preview", "lifecycle-viewmodel-compose", "coil-compose"]
\ No newline at end of file
diff --git a/library/api/jvm/library.api b/library/api/jvm/library.api
index 389c8e83f..195cd49fe 100644
--- a/library/api/jvm/library.api
+++ b/library/api/jvm/library.api
@@ -2497,7 +2497,7 @@ public class com/lagradost/cloudstream3/extractors/Filesim : com/lagradost/cloud
public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
-public class com/lagradost/cloudstream3/extractors/Firestream : com/lagradost/cloudstream3/utils/ExtractorApi {
+public final class com/lagradost/cloudstream3/extractors/Firestream : com/lagradost/cloudstream3/utils/ExtractorApi {
public fun ()V
public fun getExtractorUrl (Ljava/lang/String;)Ljava/lang/String;
public fun getMainUrl ()Ljava/lang/String;
@@ -5457,6 +5457,14 @@ public class com/lagradost/cloudstream3/extractors/Vids : com/lagradost/cloudstr
public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
+public final class com/lagradost/cloudstream3/extractors/Vidsonic : com/lagradost/cloudstream3/utils/ExtractorApi {
+ public fun ()V
+ public fun getMainUrl ()Ljava/lang/String;
+ public fun getName ()Ljava/lang/String;
+ public fun getRequiresReferer ()Z
+ public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
public final class com/lagradost/cloudstream3/extractors/VinovoSi : com/lagradost/cloudstream3/extractors/VinovoTo {
public fun ()V
public fun getMainUrl ()Ljava/lang/String;
@@ -5471,12 +5479,6 @@ public class com/lagradost/cloudstream3/extractors/VinovoTo : com/lagradost/clou
public fun getUrl (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
-public class com/lagradost/cloudstream3/extractors/Vixeo : com/lagradost/cloudstream3/extractors/Vidsonic {
- public fun ()V
- public fun getMainUrl ()Ljava/lang/String;
- public fun getName ()Ljava/lang/String;
-}
-
public class com/lagradost/cloudstream3/extractors/VkExtractor : com/lagradost/cloudstream3/utils/ExtractorApi {
public fun ()V
public fun getMainUrl ()Ljava/lang/String;
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/ParCollections.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/ParCollections.kt
index 0f33a6ff6..9e6cb99e5 100644
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/ParCollections.kt
+++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/ParCollections.kt
@@ -122,27 +122,4 @@ suspend fun runAllAsync(
}
}
}.map { it.await() }
-}
-
-/** amap with stronger cancellation guarantee, aka will only return when each job is joined */
-@OptIn(DelicateCoroutinesApi::class)
-@InternalAPI
-@Throws
-suspend fun Collection.cmap(f: suspend (A) -> B): List =
- with(CoroutineScope(currentCoroutineContext())) {
- // 1. Spawn all jobs
- map { async { f(it) } }.map { deferred ->
- // 2. Await all jobs without throwing, and join if canceled
- try {
- Result.success(deferred.await())
- } catch (e: CancellationException) {
- withContext(NonCancellable) {
- deferred.join()
- }
- Result.failure(e)
- } catch (t : Throwable) {
- Result.failure(t)
- }
- // 3. Throw if something goes wrong
- }.map { it.getOrThrow() }
- }
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt
index c4a74b908..f1d33e853 100644
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt
+++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Firestream.kt
@@ -10,12 +10,7 @@ import com.lagradost.cloudstream3.utils.newExtractorLink
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
-@Prerelease
-class FirestreamSite : Firestream() {
- override val mainUrl = "https://firestream.site"
-}
-
-open class Firestream : ExtractorApi() {
+class Firestream : ExtractorApi() {
override val name = "Firestream"
override val mainUrl = "https://firestream.to"
override val requiresReferer = false
@@ -26,7 +21,7 @@ open class Firestream : ExtractorApi() {
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit,
) {
- val id = url.removeSuffix("/").substringAfterLast("/").substringBefore("?")
+ val id = url.removeSuffix("/").substringAfterLast("/")
val url = getExtractorUrl(id)
val doc = app.get(url).document
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Hexload.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Hexload.kt
deleted file mode 100644
index 15ed8df6e..000000000
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Hexload.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.lagradost.cloudstream3.extractors
-
-import com.fasterxml.jackson.annotation.JsonProperty
-import com.lagradost.api.Log
-import com.lagradost.cloudstream3.Prerelease
-import com.lagradost.cloudstream3.SubtitleFile
-import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.utils.ExtractorApi
-import com.lagradost.cloudstream3.utils.ExtractorLink
-import com.lagradost.cloudstream3.utils.newExtractorLink
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-
-@Prerelease
-class Hexload: ExtractorApi() {
- override val name: String = "Hexload"
- override val mainUrl: String = "https://hexload.com"
- override val requiresReferer: Boolean = false
-
- private val jsDictEntryRegex = Regex("""(\w+):\s*['"](\w+)['"]""")
-
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val respData = app.get(url).text.substringAfter("data: {").substringBefore("success")
- val requestArgs = jsDictEntryRegex.findAll(respData).associate {
- it.groupValues[1] to it.groupValues[2]
- }
- Log.d("requestArgs", requestArgs.toString())
- val streamResponse = app.post("$mainUrl/download", data = requestArgs)
- .parsed()
-
- callback.invoke(newExtractorLink(
- name = name,
- source = name,
- url = streamResponse.result.url
- ))
- }
-
- @Serializable
- private data class StreamResponse(
- val result: StreamResult,
- @JsonProperty("server_time")
- @SerialName("server_time")
- val serverTime: String,
- val msg: String,
- val status: Long,
- )
-
- @Serializable
- private data class StreamResult(
- val size: String,
- val url: String,
- @JsonProperty("content_type")
- @SerialName("content_type")
- val contentType: String,
- @JsonProperty("thumb_url")
- @SerialName("thumb_url")
- val thumbUrl: String,
- @JsonProperty("image_url")
- @SerialName("image_url")
- val imageUrl: String,
- @JsonProperty("file_name")
- @SerialName("file_name")
- val fileName: String,
- val md5: String,
- )
-}
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubuCloud.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubuCloud.kt
deleted file mode 100644
index 85151b1d7..000000000
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubuCloud.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.lagradost.cloudstream3.extractors
-
-import com.lagradost.cloudstream3.Prerelease
-import com.lagradost.cloudstream3.SubtitleFile
-import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.utils.ExtractorApi
-import com.lagradost.cloudstream3.utils.ExtractorLink
-import com.lagradost.cloudstream3.utils.newExtractorLink
-
-@Prerelease
-open class HubuCloud: ExtractorApi() {
- override val name: String = "Hubu"
- override val mainUrl: String = "https://hubu.cloud"
- override val requiresReferer: Boolean = false
-
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val doc = app.get(url).document
-
- val streamUrl = doc.select("source").attr("src")
- callback.invoke(newExtractorLink(source = name, name = name, url = streamUrl))
- }
-}
\ No newline at end of file
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Playmate.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Playmate.kt
deleted file mode 100644
index e319d4c17..000000000
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Playmate.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.lagradost.cloudstream3.extractors
-
-import com.fasterxml.jackson.annotation.JsonProperty
-import com.lagradost.cloudstream3.Prerelease
-import com.lagradost.cloudstream3.SubtitleFile
-import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.utils.ExtractorApi
-import com.lagradost.cloudstream3.utils.ExtractorLink
-import com.lagradost.cloudstream3.utils.ExtractorLinkType
-import com.lagradost.cloudstream3.utils.newExtractorLink
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-
-@Prerelease
-class Playmate : ExtractorApi() {
- override val name: String = "Playmate"
- override val mainUrl: String = "https://playmate.to"
- override val requiresReferer: Boolean = false
-
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val id = url.substringAfterLast("/")
-
- val resp = app.post(
- "$mainUrl/api/s",
- json = mapOf("c" to id, "d" to "web"),
- headers = mapOf("User-Agent" to "Mozilla/5.0 (X11; Linux x86_64; rv:153.0) Gecko/20100101 Firefox/153.0")
- ).parsed()
-
- callback.invoke(newExtractorLink(source = name, name = name, url = resp.sx, type = ExtractorLinkType.M3U8))
- }
-
- @Serializable
- private data class StreamInfo(
- @SerialName("sx")
- @JsonProperty("sx")
- val sx: String,
- )
-}
\ No newline at end of file
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt
index e3a33b0cb..04c60675e 100644
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt
+++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidara.kt
@@ -1,7 +1,6 @@
package com.lagradost.cloudstream3.extractors
import com.fasterxml.jackson.annotation.JsonProperty
-import com.lagradost.cloudstream3.Prerelease
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.newSubtitleFile
@@ -44,46 +43,6 @@ class VidaraSo : Vidara() {
override val mainUrl = "https://vidara.so"
}
-@Prerelease
-class Odysseusa : Vidara() {
- override val mainUrl = "https://odysseusa.cc"
-}
-
-@Prerelease
-class Handfacesnap : Vidara() {
- override val mainUrl = "https://handfacesnap.cc"
-}
-
-@Prerelease
-class Namefacesnap : Vidara() {
- override val mainUrl = "https://namefacesnap.cc"
-}
-
-@Prerelease
-class Thebesthostertv : Vidara() {
- override val mainUrl = "https://thebesthosterv.com"
-}
-
-@Prerelease
-class Vidmatrixa : Vidara() {
- override val mainUrl = "https://vidmatrixa.com"
-}
-
-@Prerelease
-class VidChampions : Vidara() {
- override val mainUrl = "https://vidchampions.com/"
-}
-
-@Prerelease
-class Antarcticadocs : Vidara() {
- override val mainUrl = "https://antarcticadocs.com"
-}
-
-@Prerelease
-class Nameitweb : Vidara() {
- override val mainUrl = "https://nameitweb.com"
-}
-
open class Vidara : ExtractorApi() {
override val name = "Vidara"
override val mainUrl = "https://vidara.to"
@@ -95,9 +54,7 @@ open class Vidara : ExtractorApi() {
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit,
) {
- // URLs sometimes contain a mirror selector query parameter, e.g. "https://vidara.to/xkNOuHSrCNMj?24"
- // that is not part of the file code and hence we must remove it
- val fileCode = url.substringAfterLast("/").substringBefore("?")
+ val fileCode = url.substringAfterLast("/")
val fileInfo = app.post(
"$mainUrl/api/stream", json = mapOf(
"filecode" to fileCode,
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidsonic.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidsonic.kt
index 4cd85fdb5..49560d456 100644
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidsonic.kt
+++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidsonic.kt
@@ -1,46 +1,18 @@
package com.lagradost.cloudstream3.extractors
-import com.lagradost.cloudstream3.Prerelease
+import com.lagradost.api.Log
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.base64Decode
-import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.ExtractorLinkType
import com.lagradost.cloudstream3.utils.newExtractorLink
-import com.lagradost.nicehttp.NiceResponse
-import kotlinx.serialization.json.JsonObject
-import kotlinx.serialization.json.jsonPrimitive
-open class Vixeo : Vidsonic() {
- override val name: String = "Vixeo"
- override val mainUrl: String = "https://vixeo.io"
-
- @Prerelease
- override fun extractEncodedVideoUrl(response: NiceResponse): String {
- // Vixeo encodes the stream URL as Base64 instead of inlining it into JavaScript
- val dataConfigBase64 = response.document.select("#streamsonic-player-root").attr("data-config")
- val dataConfigRaw = base64Decode(dataConfigBase64)
- val dataConfig = AppUtils.parseJson(dataConfigRaw)
- return dataConfig["source"]!!.jsonPrimitive.content
- }
-}
-
-@Prerelease
-open class Vidsonic : ExtractorApi() {
+class Vidsonic() : ExtractorApi() {
override val name: String = "Vidsonic"
override val mainUrl: String = "https://vidsonic.net"
override val requiresReferer: Boolean = false
- @Prerelease
- open fun extractEncodedVideoUrl(response: NiceResponse): String {
- return response.text
- .substringAfter("const _0x1 = ")
- .substringBefore(";")
- .replace("'", "")
- }
-
override suspend fun getUrl(
url: String,
referer: String?,
@@ -59,8 +31,12 @@ open class Vidsonic : ExtractorApi() {
// return _0x5.split('').reverse().join('');
// };
// const _0x7 = _0x2(_0x1); <-- now contains the stream URL
- val response = app.get(url)
- val encodedStreamUrl = extractEncodedVideoUrl(response)
+
+ val response = app.get(url).text
+ val encodedStreamUrl = response
+ .substringAfter("const _0x1 = ")
+ .substringBefore(";")
+ .replace("'", "")
// (improved) Kotlin implementation of the JavaScript code from above
val streamUrl = encodedStreamUrl
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt
index faf498024..163059a97 100644
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt
+++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt
@@ -11,7 +11,6 @@ import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.extractors.Acefile
import com.lagradost.cloudstream3.extractors.Ahvsh
import com.lagradost.cloudstream3.extractors.Aico
-import com.lagradost.cloudstream3.extractors.Antarcticadocs
import com.lagradost.cloudstream3.extractors.Asnwish
import com.lagradost.cloudstream3.extractors.Auvexiug
import com.lagradost.cloudstream3.extractors.Awish
@@ -80,7 +79,6 @@ import com.lagradost.cloudstream3.extractors.FileMoonSx
import com.lagradost.cloudstream3.extractors.FilemoonV2
import com.lagradost.cloudstream3.extractors.Filesim
import com.lagradost.cloudstream3.extractors.Firestream
-import com.lagradost.cloudstream3.extractors.FirestreamSite
import com.lagradost.cloudstream3.extractors.Multimoviesshg
import com.lagradost.cloudstream3.extractors.FlaswishCom
import com.lagradost.cloudstream3.extractors.Flyfile
@@ -111,9 +109,7 @@ import com.lagradost.cloudstream3.extractors.HDMomPlayer
import com.lagradost.cloudstream3.extractors.HDPlayerSystem
import com.lagradost.cloudstream3.extractors.HDStreamAble
import com.lagradost.cloudstream3.extractors.Habetar
-import com.lagradost.cloudstream3.extractors.Handfacesnap
import com.lagradost.cloudstream3.extractors.Haxloppd
-import com.lagradost.cloudstream3.extractors.Hexload
import com.lagradost.cloudstream3.extractors.Hgcloudto
import com.lagradost.cloudstream3.extractors.HglinkTo
import com.lagradost.cloudstream3.extractors.HgplayCDN
@@ -121,7 +117,6 @@ import com.lagradost.cloudstream3.extractors.Hotlinger
import com.lagradost.cloudstream3.extractors.HubCloud
import com.lagradost.cloudstream3.extractors.Hxfile
import com.lagradost.cloudstream3.extractors.HlsWish
-import com.lagradost.cloudstream3.extractors.HubuCloud
import com.lagradost.cloudstream3.extractors.InternetArchive
import com.lagradost.cloudstream3.extractors.JWPlayer
import com.lagradost.cloudstream3.extractors.Jeniusplay
@@ -163,8 +158,6 @@ import com.lagradost.cloudstream3.extractors.Multimovies
import com.lagradost.cloudstream3.extractors.Mvidoo
import com.lagradost.cloudstream3.extractors.MyVidPlay
import com.lagradost.cloudstream3.extractors.Mwish
-import com.lagradost.cloudstream3.extractors.Namefacesnap
-import com.lagradost.cloudstream3.extractors.Nameitweb
import com.lagradost.cloudstream3.extractors.NathanFromSubject
import com.lagradost.cloudstream3.extractors.Nekostream
import com.lagradost.cloudstream3.extractors.Nekowish
@@ -172,7 +165,6 @@ import com.lagradost.cloudstream3.extractors.Neonime7n
import com.lagradost.cloudstream3.extractors.Neonime8n
import com.lagradost.cloudstream3.extractors.Obeywish
import com.lagradost.cloudstream3.extractors.Odnoklassniki
-import com.lagradost.cloudstream3.extractors.Odysseusa
import com.lagradost.cloudstream3.extractors.OkRuHTTP
import com.lagradost.cloudstream3.extractors.OkRuHTTPMobile
import com.lagradost.cloudstream3.extractors.OkRuSSL
@@ -186,7 +178,6 @@ import com.lagradost.cloudstream3.extractors.PlayLtXyz
import com.lagradost.cloudstream3.extractors.PlayRu
import com.lagradost.cloudstream3.extractors.PlayerVoxzer
import com.lagradost.cloudstream3.extractors.Playerwish
-import com.lagradost.cloudstream3.extractors.Playmate
import com.lagradost.cloudstream3.extractors.Playmogo
import com.lagradost.cloudstream3.extractors.Rabbitstream
import com.lagradost.cloudstream3.extractors.RapidVid
@@ -278,8 +269,6 @@ import com.lagradost.cloudstream3.extractors.VidHidePro6
import com.lagradost.cloudstream3.extractors.VidHideHub
import com.lagradost.cloudstream3.extractors.Ryderjet
import com.lagradost.cloudstream3.extractors.Streamcash
-import com.lagradost.cloudstream3.extractors.Thebesthostertv
-import com.lagradost.cloudstream3.extractors.VidChampions
import com.lagradost.cloudstream3.extractors.VidMoxy
import com.lagradost.cloudstream3.extractors.VidStack
import com.lagradost.cloudstream3.extractors.VideoSeyred
@@ -306,10 +295,8 @@ import com.lagradost.cloudstream3.extractors.Vidaraw
import com.lagradost.cloudstream3.extractors.Vidarax
import com.lagradost.cloudstream3.extractors.Vidavaca
import com.lagradost.cloudstream3.extractors.Vide0Net
-import com.lagradost.cloudstream3.extractors.Vidmatrixa
import com.lagradost.cloudstream3.extractors.Vids
import com.lagradost.cloudstream3.extractors.Vidsonic
-import com.lagradost.cloudstream3.extractors.Vixeo
import com.lagradost.cloudstream3.extractors.VkExtractor
import com.lagradost.cloudstream3.extractors.Voe
import com.lagradost.cloudstream3.extractors.Voe1
@@ -1152,7 +1139,6 @@ val extractorApis: AtomicMutableList = atomicListOf(
Vicloud(),
Uservideo(),
Userscloud(),
- HubuCloud(),
Movhide(),
StreamhideCom(),
@@ -1195,14 +1181,6 @@ val extractorApis: AtomicMutableList = atomicListOf(
Vidaratem(),
VidaaraxCom(),
VidaaraxNet(),
- Odysseusa(),
- Handfacesnap(),
- Namefacesnap(),
- Thebesthostertv(),
- Vidmatrixa(),
- VidChampions(),
- Antarcticadocs(),
- Nameitweb(),
GamoVideo(),
Gdriveplayerapi(),
@@ -1315,7 +1293,6 @@ val extractorApis: AtomicMutableList = atomicListOf(
Ds2play(),
Ds2video(),
Vidsonic(),
- Vixeo(),
InternetArchive(),
VidStack(),
GDMirrorbot(),
@@ -1342,10 +1319,7 @@ val extractorApis: AtomicMutableList = atomicListOf(
ByseQekaho(),
Flyfile(),
Firestream(),
- FirestreamSite(),
Vids(),
- Playmate(),
- Hexload(),
)
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/M3u8Helper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/M3u8Helper.kt
index 3a4c21d98..d34f5f500 100644
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/M3u8Helper.kt
+++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/M3u8Helper.kt
@@ -121,15 +121,6 @@ object M3u8Helper2 {
return !url.startsWith("https://") && !url.startsWith("http://")
}
- /** Resolves a playlist entry against the playlist url, as done for variants in [HlsPlaylistParser] */
- private fun resolveUrl(playlistUrl: String, url: String): String {
- return if (isNotCompleteUrl(url)) {
- HlsPlaylistParser.UrlUtil.resolveToUrl(playlistUrl, url).toString()
- } else {
- url
- }
- }
-
@Throws
suspend fun m3u8Generation(
m3u8: M3u8Helper.M3u8Stream,
@@ -329,7 +320,11 @@ object M3u8Helper2 {
if (!match.isNullOrEmpty()) {
encryptionState = true
- val encryptionUrl = resolveUrl(playlistStream.streamUrl, match[2])
+ var encryptionUrl = match[2]
+
+ if (isNotCompleteUrl(encryptionUrl)) {
+ encryptionUrl = "${getParentLink(playlistStream.streamUrl)}/$encryptionUrl"
+ }
encryptionIv = match[3].encodeToByteArray()
val encryptionKeyResponse =
@@ -344,7 +339,12 @@ object M3u8Helper2 {
val relativeUrl = getParentLink(playlistStream.streamUrl)
val allTsList = TS_EXTENSION_REGEX.findAll(playlistResponse + "\n").map { ts ->
val time = ts.groupValues[1]
- val url = resolveUrl(playlistStream.streamUrl, ts.groupValues[3].trim())
+ val value = ts.groupValues[3]
+ val url = if (isNotCompleteUrl(value)) {
+ "$relativeUrl/${value}"
+ } else {
+ value
+ }
TsLink(url = url, time = time.toDoubleOrNull())
}.toList()
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt
index 7d81412f0..497e129a0 100644
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt
+++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt
@@ -62,7 +62,18 @@ object SubtitleHelper {
halfMatch: Boolean? = false
): LanguageMetadata? {
if (languageName.isNullOrBlank() || languageName.length < 2) return null
- val lowLangName = languageName.lowercase().replace(garbageRegex, "").trim()
+ // Workaround to avoid junk like "English (original audio)" or "Spanish 123"
+ // or "اَلْعَرَبِيَّةُ (Original Audio) 1" or "English (hindi sub)"…
+ // Will still keep "-" to be compatible with language tags such as pr-bt
+ val garbage = Regex(
+ "\\([^)]*(?:dub|sub|original|audio|code)[^)]*\\)|" + // junk words in parenthesis
+ "[\\u064B-\\u065B]|" + // arabic diacritics
+ "\\d|" + // numbers
+ "[^\\p{L}\\p{Mn}\\p{Mc}\\p{Me} ()-]" // non-letter (from any language)
+ )
+
+
+ val lowLangName = languageName.lowercase().replace(garbage, "").trim()
val index = indexMapLanguageName[lowLangName]
?: indexMapNativeName[lowLangName]
@@ -222,22 +233,10 @@ object SubtitleHelper {
*/
fun isWellFormedTagIETF(langTagIETF: String?): Boolean {
if (langTagIETF.isNullOrBlank() || langTagIETF.length < 2) return false
- return langTagIETF.matches(langTagRegex)
- }
- // Workaround to avoid junk like "English (original audio)" or "Spanish 123"
- // or "اَلْعَرَبِيَّةُ (Original Audio) 1" or "English (hindi sub)"…
- // Will still keep "-" to be compatible with language tags such as pr-bt
- private val garbageRegex = Regex(
- "\\([^)]*(?:dub|sub|original|audio|code)[^)]*\\)|" + // junk words in parenthesis
- "[\\u064B-\\u065B]|" + // arabic diacritics
- "\\d|" + // numbers
- "[^\\p{L}\\p{Mn}\\p{Mc}\\p{Me} ()-]" // non-letter (from any language)
- )
-
- // Written by Addison Phillips,
- // https://www.langtag.net/philips-regexp.html
- private val langTagRegex = """
+ // Written by Addison Phillips,
+ // https://www.langtag.net/philips-regexp.html
+ val langTagRegex = """
+(^[xX](\x2d\p{Alnum}{1,8})*$)
+|(((^\p{Alpha}{2,8}(?=\x2d|$)){1}
+((\x2d\p{Alpha}{3})(?=\x2d|$)){0,3}
@@ -247,10 +246,9 @@ object SubtitleHelper {
+((\x2d([a-wyzA-WYZ](?=\x2d))(\x2d(\p{Alnum}{2,8})+)*))*
+(\x2d[xX](\x2d\p{Alnum}{1,8})*)?)$
""".trimMargin("+").toRegex()
- // language tags (en-US, es-419, pt-BR, zh-hant-TW) that includes country
- private val countryRegex = Regex("[-_](\\p{Alnum}{2,3})$", RegexOption.IGNORE_CASE)
- // 2 times a symbol between regional indicator "[A]" and "[Z]"
- private val unicodeFlagRegex = Regex("[\uD83C\uDDE6-\uD83C\uDDFF]{2}")
+ return langTagIETF.matches(langTagRegex)
+ }
+
/**
* Try to get a flag emoji form a language code
* or two letters country code (ISO 3166-1-alfa-2)
@@ -258,6 +256,11 @@ object SubtitleHelper {
fun getFlagFromIso(inp: String?): String? {
if (inp.isNullOrBlank() || inp.length < 2) return null
+ // 2 times a symbol between regional indicator "[A]" and "[Z]"
+ val unicodeFlagRegex = Regex("[\uD83C\uDDE6-\uD83C\uDDFF]{2}")
+ // language tags (en-US, es-419, pt-BR, zh-hant-TW) that includes country
+ val countryRegex = Regex("[-_](\\p{Alnum}{2,3})$", RegexOption.IGNORE_CASE)
+
val country = countryRegex.find(inp)?.groupValues?.get(1)
val flagEmoji =
diff --git a/library/src/jvmTest/kotlin/com/lagradost/cloudstream3/utils/M3u8HelperTest.kt b/library/src/jvmTest/kotlin/com/lagradost/cloudstream3/utils/M3u8HelperTest.kt
deleted file mode 100644
index b58f90d4e..000000000
--- a/library/src/jvmTest/kotlin/com/lagradost/cloudstream3/utils/M3u8HelperTest.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.lagradost.cloudstream3.utils
-
-import com.sun.net.httpserver.HttpServer
-import kotlinx.coroutines.runBlocking
-import java.net.InetSocketAddress
-import java.util.Collections
-import kotlin.test.AfterTest
-import kotlin.test.BeforeTest
-import kotlin.test.Test
-import kotlin.test.assertContentEquals
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
-
-class M3u8HelperTest {
- private lateinit var server: HttpServer
- private lateinit var host: String
- private val requestedPaths: MutableList = Collections.synchronizedList(mutableListOf())
- private val segment = ByteArray(256) { (it or 0x80).toByte() }
-
- @BeforeTest
- fun setUp() {
- server = HttpServer.create(InetSocketAddress("127.0.0.1", 0), 0)
- host = "127.0.0.1:${server.address.port}"
- server.createContext("/") { exchange ->
- val path = exchange.requestURI.path
- requestedPaths += path
- val body = when (path) {
- "/a/b/index.m3u8" -> playlist(key = null).encodeToByteArray()
- "/a/b/encrypted.m3u8" -> playlist(key = "/keys/k.bin").encodeToByteArray()
- "/a/b/seg0.ts", "/root/seg1.ts", "/proto/seg2.ts" -> segment
- "/keys/k.bin" -> ByteArray(16)
- else -> null
- }
- val bytes = body ?: "404 not found".encodeToByteArray()
- exchange.sendResponseHeaders(if (body == null) 404 else 200, bytes.size.toLong())
- exchange.responseBody.use { it.write(bytes) }
- }
- server.start()
- }
-
- @AfterTest
- fun tearDown() {
- server.stop(0)
- }
-
- private fun playlist(key: String?): String = buildString {
- appendLine("#EXTM3U")
- appendLine("#EXT-X-TARGETDURATION:10")
- if (key != null) appendLine("#EXT-X-KEY:METHOD=AES-128,URI=\"$key\"")
- appendLine("#EXTINF:10.0,")
- appendLine("seg0.ts")
- appendLine("#EXTINF:10.0,")
- appendLine("/root/seg1.ts")
- appendLine("#EXTINF:10.0,")
- appendLine("//$host/proto/seg2.ts")
- appendLine("#EXT-X-ENDLIST")
- }
-
- @Test
- fun hslLazyResolvesSegmentUrlsAgainstPlaylistUrl() = runBlocking {
- val data = M3u8Helper2.hslLazy(
- M3u8Helper.M3u8Stream("http://$host/a/b/index.m3u8?token=abc"),
- requireAudio = false,
- )
-
- assertEquals(
- listOf(
- "http://$host/a/b/seg0.ts",
- "http://$host/root/seg1.ts",
- "http://$host/proto/seg2.ts",
- ),
- data.allTsLinks.map { it.url },
- )
- for (index in 0 until data.size) {
- assertContentEquals(segment, data.resolveLink(index))
- }
- }
-
- @Test
- fun hslLazyResolvesKeyUrlAgainstPlaylistUrl() = runBlocking {
- M3u8Helper2.hslLazy(
- M3u8Helper.M3u8Stream("http://$host/a/b/encrypted.m3u8"),
- requireAudio = false,
- )
-
- assertTrue("/keys/k.bin" in requestedPaths, "requested: $requestedPaths")
- }
-}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index dd92448b0..73bf5a195 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -18,4 +18,4 @@ dependencyResolutionManagement {
}
rootProject.name = "CloudStream"
-include(":app", ":shared", ":library", ":docs", ":desktopApp")
+include(":app", ":library", ":docs")
diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts
deleted file mode 100644
index 6bca3488e..000000000
--- a/shared/build.gradle.kts
+++ /dev/null
@@ -1,66 +0,0 @@
-plugins {
- alias(libs.plugins.kotlin.multiplatform)
- alias(libs.plugins.android.lint)
- alias(libs.plugins.android.multiplatform.library)
- alias(libs.plugins.compose.multiplatform)
- alias(libs.plugins.compose.compiler)
-}
-
-kotlin {
- android {
- // Must be unique
- namespace = "com.lagradost.cloudstream4"
- compileSdk = libs.versions.compileSdk.get().toInt()
- minSdk = libs.versions.minSdk.get().toInt()
-
- androidResources {
- enable = true
- }
- }
-
- jvm()
-
- compilerOptions {
- freeCompilerArgs.add("-Xexpect-actual-classes")
- }
-
- sourceSets {
- all {
- languageSettings {
- optIn("com.lagradost.cloudstream3.InternalAPI")
- optIn("com.lagradost.cloudstream3.Prerelease")
- }
- }
-
- commonMain.dependencies {
- implementation(libs.coil.network.ktor3)
- implementation(libs.bundles.compose)
- implementation(libs.kotlinx.collections.immutable)
- implementation(project(":library"))
- }
-
- androidMain.dependencies {
- implementation(libs.activity.compose)
- implementation(libs.preference.ktx)
- implementation(libs.ktor.client.android)
- }
-
- appleMain.dependencies {
- implementation(libs.ktor.client.darwin)
- }
-
- jvmMain.dependencies {
- implementation(libs.ktor.client.java)
- }
- }
-}
-
-dependencies {
- androidRuntimeClasspath(libs.compose.ui.tooling)
-}
-
-compose.resources {
- publicResClass = true
- packageOfResClass = "com.lagradost.cloudstream4.generated.resources"
- generateResClass = auto
-}
\ No newline at end of file
diff --git a/shared/readme.md b/shared/readme.md
deleted file mode 100644
index 084244cf3..000000000
--- a/shared/readme.md
+++ /dev/null
@@ -1,112 +0,0 @@
-## Motivation
-
-This readme is documentation for humans to understand ***why***, and ***not how*** the core architecture
-for CloudStream will be designed going forward.
-
-## Design decisions
-
-The following design decisions has been tried in the sister project QuickNovel with great success, and a
-seamless porting process. For inspiration and code in a similar codebase, take a look at QuickNovel.
-
-### File structure
-
-The `shared` directory is supposed to be for shared compose UI between platforms, and can act as a glue for business logic.
-This is separate from the `library` that includes the primitives for a headless, cross-platform plugin system.
-Note that we do not want a shared screen layout, because each platform has its own design language and quirks.
-Instead, the ViewModels should be entirely detached from the rendering process, and each UI component should be shared
-between every platform. This ensures ViewModels cant cheat with dirty escape hatches, a leaky api or
-with UI elements that hijack business logic.
-
-**Include**:
-*Viewmodels*, *UI components* and *Theming*
-
-**Avoid**:
-*Entire Screens*, *Plugin APIs* and *Android Layout XML*
-
-Please also note that while it would be nice with an e.g. cross-platform downloader or video player.
-It is simply infeasible and undesirable to write such a system. Therefore, each platform should have
-a seperate implemenation of platform specific behavior that closly interop with native systems.
-
-### MVI
-
-MVI (Model View Intent) is the core structure for building cross-platform UI we will use.
-The reason for this decision is that current MVVM architecture created a lot of boilerplate,
-and is very unwieldy when working with compose. We will instead use a single immutable UI state, using
-kotlin immutable collections, as that creates thread-safe and performant UI. Additionally, because the viewmodel
-is shared between platforms, the Viewmodel must be provided with an implementation specific
-database/preference/downloaded by using dependency injection and composition.
-
-**Use**:
-*PersistentList*, *val*, *dependency injection* and *composition*
-
-**Avoid**:
-*List*, *var*, *globals* and *inheritance*
-
-Please note that this process aims to replace the current UI in a backwards compatible way. Only
-the navigation graph should change to point to the new fragments. Then we can roll back if some
-critical issue is found with the compose rewrite, as well as compare functionality without a checkout.
-
-### Resources and assets
-
-**Images**: For this project the accepted image format is `XML Drawable resources` instead of
-`ImageVector` or `.svg`. This is primarily due to make the review, cross-platform, and edit
-processing easier. ImageVector is undesirable because file must be reviewed to not contain malicious
-code, and does not have a built-in preview. SVG files on the other hand are currently not natively
-supported on Android.
-
-**Translation**: CloudStream already has a huge translation, however to make the porting process easier,
-we should limit any copy-paste from the android project until everything is converted to compose.
-This is in part because weblate is not set up target the cross-platform compose yet.
-
-**Styling/Themes**: In compose there is no `styles.xml`/`colors.xml`/`dimens.xml`. Therefore,
-styling is done entierly in code, but should use the exact same colors to avoid a styling mismatch
-when porting.
-
-**UI/UX**: The intended experience when using CloudStream should be the same, but the UI should be
-refreshed to a more Material 3 style, such as rounded corners and a greater focus on light theme
-support. However, no glassmorphic, blurred or gradient UI is accepted.
-
-### Settings and Mihon
-
-The settings system has been entirely forked from Tachiyomi, now Mihon project. This is because
-Mihon has one of the best FOSS codebases for Android, and has a great declarative preference system
-unmatched by any other tested Compose settings system. Unfortunately, this is not provided as a
-library and is therefore instead forked by importing the exact files used.
-
-### Why compose?
-
-During the lifetime of CloudStream, the app has been redesigned and rewritten several times.
-
-**C# Downloader → Xamarin Native → Xamarin Cross platform → Android Native XML → ViewBinding → Compose Multiplatform**
-
-We have experimented with different UI technologies such as **Blazor**, **Electron**, **PWA**,
-**WPF**, **UWP**, **Windows Forms**, and **CLI/Console** for CloudStream.
-We have also investigated projectes written in **Flutter** and **React Native**.
-
-However, none of these frameworks provided a good developer experience, that was also performant and
-cross-platform. Compose was the only real option, but did not exist until after we started on
-CloudStream 3. Android Native XML was simply the least bad option when building an Android app.
-
-### CloudStream 4?
-
-The current android app is named CloudStream 3, and this Compose rewrite will be named
-CloudStream 4. However, the android app will be upgraded, not replaced with Compose. It aims to be a
-seamless and gradual rollout of updates for everyone using the Android app. Therefore, CloudStream 4
-is used to refer to the new compose UI, features, and cross-platform support.
-
-## AI Usage
-
-Due to the prevalence and popularity of LLMs, CloudStream 3 has been rewritten entirely 4+ times by
-independent actors using Compose to target desktop with different LLM models. These have not been merged, due to the
-absolute mess of the AI generated codebase, both in code quality and performance.
-
-Therefore, I (LagradOst) will not accept or allow any pull requests concering the Compose rewrite
-before I put the core structures are in place. The compose rewrite is supposed to be a refactor and
-quality improvement for everyone involved, to solve the massive tech debt we already have. XML and
-old plans forced bad design decissions, which we can improve greatly upon. However, in no part do I
-want AI to lay the groundwork for something this important.
-
-The idea is to provide a solid staring point so it is easy for first time contributors and LLMs to
-understand and work with the code, not ship a product as fast as possible. This is not a project
-with only one person involved. A messy AI generated codebase makes reviews and contributions very hard
-for everyone involved.
diff --git a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.android.kt b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.android.kt
deleted file mode 100644
index 04ba9dd36..000000000
--- a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.android.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.lagradost.cloudstream4
-
-import android.content.Context
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import com.mihon.common.preference.AndroidPreferenceStore
-
-
-@Composable
-actual fun rememberAppSettings() : AppSettings {
- val context = LocalContext.current
- val value = remember(context) { AppSettings(context) }
- return value
-}
-
-fun AppSettings(context: Context) : AppSettings {
- return AppSettings(preferences = AndroidPreferenceStore(context))
-}
diff --git a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/ComposeFragment.kt b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/ComposeFragment.kt
deleted file mode 100644
index 9a91e3e16..000000000
--- a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/ComposeFragment.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.lagradost.cloudstream4.compose
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.platform.ComposeView
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.ViewCompositionStrategy
-import com.lagradost.cloudstream4.rememberAppSettings
-import com.lagradost.cloudstream4.theme.CloudStreamTheme
-import com.lagradost.cloudstream4.theme.perfToColor
-import com.lagradost.cloudstream4.theme.perfToMode
-import com.mihon.presentation.LocalBackPress
-import com.mihon.presentation.settings.collectAsState
-
-/** Backwards compatible fragment for compose, before we switch entirely to compose navigation */
-fun Screen.createComposeView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?,
-): View = ComposeView(inflater.context).apply {
- setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
-
- setContent {
- val settings = rememberAppSettings()
- val mode by settings.ui.theme.collectAsState()
- val primaryColor by settings.ui.primaryColor.collectAsState()
- val layout by settings.ui.layout.collectAsState()
- val layoutFlag = DeviceLayout.layoutToFlag(LocalContext.current, layout)
-
- CloudStreamTheme(
- mode = perfToMode(mode),
- primaryColor = perfToColor(primaryColor),
- ) {
- val backDispatcher = checkNotNull(LocalOnBackPressedDispatcherOwner.current) {
- "No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner"
- }.onBackPressedDispatcher
-
- CompositionLocalProvider(
- LocalBackPress provides backDispatcher::onBackPressed,
- DeviceLayout.LocalLayout provides layoutFlag
- ) {
- this@createComposeView.Content()
- }
- }
- }
-}
\ No newline at end of file
diff --git a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/Layout.kt b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/Layout.kt
deleted file mode 100644
index 96838ae5e..000000000
--- a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/compose/Layout.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.lagradost.cloudstream4.compose
-
-import android.app.UiModeManager
-import android.content.Context
-import android.content.res.Configuration
-import android.os.Build
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.ProvidableCompositionLocal
-import androidx.compose.runtime.staticCompositionLocalOf
-import com.lagradost.cloudstream4.compose.DeviceLayout.Companion.LocalLayout
-
-@JvmInline
-@Immutable
-value class DeviceLayout(private val value: Int) {
- infix fun or(other: DeviceLayout) =
- DeviceLayout(value or other.value)
-
- internal infix fun and(other: DeviceLayout) = (value and other.value) != 0
-
- companion object {
- val LocalLayout: ProvidableCompositionLocal = staticCompositionLocalOf { DeviceLayout(-1) }
- fun isAutoTv(context: Context): Boolean {
- val uiModeManager = context.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")
- }
- internal fun layoutToFlag(context : Context, from : Int): DeviceLayout {
- return when(from) {
- -1 -> if (isAutoTv(context)) TV else PHONE
- 0 -> PHONE
- 1 -> TV
- 2 -> EMULATOR
- else -> PHONE
- }
- }
- }
-}
-
-val PHONE = DeviceLayout(0b00001)
-val TV = DeviceLayout(0b00010)
-val EMULATOR = DeviceLayout(0b00100)
-
-@Composable
-fun isLayout(flags: DeviceLayout) : Boolean = LocalLayout.current and flags
\ No newline at end of file
diff --git a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/theme/ColorPreference.kt b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/theme/ColorPreference.kt
deleted file mode 100644
index 17d715372..000000000
--- a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/theme/ColorPreference.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-package com.lagradost.cloudstream4.theme
-
-import android.content.Context
-import android.os.Build
-import androidx.preference.PreferenceManager
-
-/** TODO: This should be replaced with a better system */
-
-fun perfToMode(perf: String?) =
- when (perf) {
- "System" -> CloudStreamThemeMode.FollowSystem
- "Black" -> CloudStreamThemeMode.Dark
- "Light" -> CloudStreamThemeMode.Light
- "Amoled" -> CloudStreamThemeMode.Amoled
- "AmoledLight" -> CloudStreamThemeMode.AmoledLight
- "Dracula" -> CloudStreamThemeMode.Dracula
- "Lavender" -> CloudStreamThemeMode.Lavender
- "SilentBlue" -> CloudStreamThemeMode.SilentBlue
- "Monet" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- CloudStreamThemeMode.Dynamic
- } else {
- CloudStreamThemeMode.Dark
- }
-
- else -> CloudStreamThemeMode.Dark
- }
-
-fun perfToColor(perf : String?) = when (perf) {
- "Normal" -> CloudStreamPrimaryColor.NORMAL
- "Blue" -> CloudStreamPrimaryColor.BLUE
- "Purple" -> CloudStreamPrimaryColor.PURPLE
- "Green" -> CloudStreamPrimaryColor.GREEN
- "GreenApple" -> CloudStreamPrimaryColor.GREEN_APPLE
- "Red" -> CloudStreamPrimaryColor.RED
- "Banana" -> CloudStreamPrimaryColor.BANANA
- "Party" -> CloudStreamPrimaryColor.PARTY
- "Pink" -> CloudStreamPrimaryColor.PINK
- "CarnationPink" -> CloudStreamPrimaryColor.CARNATION_PINK
- "Maroon" -> CloudStreamPrimaryColor.MAROON
- "DarkGreen" -> CloudStreamPrimaryColor.DARK_GREEN
- "NavyBlue" -> CloudStreamPrimaryColor.NAVY_BLUE
- "Grey" -> CloudStreamPrimaryColor.GREY
- "White" -> CloudStreamPrimaryColor.WHITE
- "Brown" -> CloudStreamPrimaryColor.BROWN
- "Orange" -> CloudStreamPrimaryColor.ORANGE
- "DandelionYellow" -> CloudStreamPrimaryColor.DANDELION_YELLOW
- "CoolBlue" -> CloudStreamPrimaryColor.COOL_BLUE
- "Lavender" -> CloudStreamPrimaryColor.LAVENDER
- "Monet" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- CloudStreamPrimaryColor.DYNAMIC
- } else {
- CloudStreamPrimaryColor.NORMAL
- }
-
- "Monet2" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- CloudStreamPrimaryColor.DYNAMIC_TWO
- } else {
- CloudStreamPrimaryColor.NORMAL
- }
-
- else -> CloudStreamPrimaryColor.NORMAL
-}
-
-fun Context.loadThemeMode(): CloudStreamThemeMode {
- val prefs = PreferenceManager.getDefaultSharedPreferences(this)
- return perfToMode(prefs.getString("theme_key", "AmoledLight"))
-}
-
-fun Context.loadPrimaryColor(): CloudStreamPrimaryColor {
- val prefs = PreferenceManager.getDefaultSharedPreferences(this)
- return perfToColor(prefs.getString("primary_color_key", "Normal"))
-}
\ No newline at end of file
diff --git a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.android.kt b/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.android.kt
deleted file mode 100644
index 73b188c25..000000000
--- a/shared/src/androidMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.android.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.lagradost.cloudstream4.theme
-
-import android.os.Build
-import androidx.annotation.RequiresApi
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.colorResource
-
-internal actual object DeviceTheme {
- @Composable
- @ReadOnlyComposable
- @RequiresApi(Build.VERSION_CODES.S)
- private fun buildMonetScheme(): CloudStreamColorScheme {
- return if (true) { // isSystemInDarkTheme() disabled until everything is Compose, as otherwise you get bugs
- CloudStreamColorScheme(
- background = colorResource(android.R.color.system_neutral1_900),
- surfaceVariant = colorResource(android.R.color.system_neutral1_800),
- surface = colorResource(android.R.color.system_neutral1_800),
- surfaceContainer = colorResource(android.R.color.system_neutral1_800),
- onBackground = colorResource(android.R.color.system_neutral1_100),
- onSurfaceVariant = colorResource(android.R.color.system_neutral2_400),
- icon = colorResource(android.R.color.system_neutral1_100),
- primary = colorResource(android.R.color.system_accent1_200),
- ongoing = CloudStreamPalette.Ongoing,
- isLight = false,
- )
- } else {
- CloudStreamColorScheme(
- background = colorResource(android.R.color.system_neutral1_10),
- surfaceVariant = colorResource(android.R.color.system_neutral1_100),
- surface = colorResource(android.R.color.system_neutral1_100),
- surfaceContainer = colorResource(android.R.color.system_neutral1_100),
- onBackground = colorResource(android.R.color.system_neutral1_900),
- onSurfaceVariant = colorResource(android.R.color.system_neutral2_600),
- icon = colorResource(android.R.color.system_neutral1_900),
- primary = colorResource(android.R.color.system_accent1_600),
- ongoing = CloudStreamPalette.Ongoing,
- isLight = true,
- )
- }
- }
-
- @Composable
- @ReadOnlyComposable
- actual fun resolveDynamicTheme(): CloudStreamColorScheme {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- buildMonetScheme()
- } else {
- darkScheme()
- }
- }
-
- @Composable
- @ReadOnlyComposable
- actual fun resolveDynamicPrimaryColor(): Color {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- colorResource(android.R.color.system_accent1_200)
- } else {
- CloudStreamPrimaryColor.NORMAL.color
- }
- }
-
- @Composable
- @ReadOnlyComposable
- actual fun resolveDynamicSecondaryColor(): Color {
- return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- colorResource(android.R.color.system_accent2_200)
- } else {
- CloudStreamPrimaryColor.NORMAL.color
- }
- }
-}
\ No newline at end of file
diff --git a/shared/src/androidMain/kotlin/com/mihon/common/preference/AndroidPreference.kt b/shared/src/androidMain/kotlin/com/mihon/common/preference/AndroidPreference.kt
deleted file mode 100644
index b766dd4b3..000000000
--- a/shared/src/androidMain/kotlin/com/mihon/common/preference/AndroidPreference.kt
+++ /dev/null
@@ -1,235 +0,0 @@
-package com.mihon.common.preference
-
-import android.content.SharedPreferences
-import android.content.SharedPreferences.Editor
-import androidx.core.content.edit
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.conflate
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.stateIn
-
-sealed class AndroidPreference(
- private val preferences: SharedPreferences,
- private val keyFlow: Flow,
- private val key: String,
- private val defaultValue: T,
-) : PreferenceData {
-
- abstract fun read(preferences: SharedPreferences, key: String, defaultValue: T): T
-
- abstract fun write(key: String, value: T): Editor.() -> Unit
-
- override fun key(): String {
- return key
- }
-
- override fun get(): T {
- return try {
- read(preferences, key, defaultValue)
- } catch (e: ClassCastException) {
- //logcat { "Invalid value for $key; deleting" }
- delete()
- defaultValue
- }
- }
-
- override fun set(value: T) {
- preferences.edit(action = write(key, value))
- }
-
- override fun isSet(): Boolean {
- return preferences.contains(key)
- }
-
- override fun delete() {
- preferences.edit {
- remove(key)
- }
- }
-
- override fun defaultValue(): T {
- return defaultValue
- }
-
- override fun changes(): Flow {
- return keyFlow
- .filter { it == key || it == null }
- .onStart { emit("ignition") }
- .map { get() }
- .conflate()
- }
-
- override fun stateIn(scope: CoroutineScope): StateFlow {
- return changes().stateIn(scope, SharingStarted.Eagerly, get())
- }
-
- class StringPrimitive(
- preferences: SharedPreferences,
- keyFlow: Flow,
- key: String,
- defaultValue: String,
- ) : AndroidPreference(preferences, keyFlow, key, defaultValue) {
- override fun read(
- preferences: SharedPreferences,
- key: String,
- defaultValue: String,
- ): String {
- return preferences.getString(key, defaultValue) ?: defaultValue
- }
-
- override fun write(key: String, value: String): Editor.() -> Unit = {
- putString(key, value)
- }
- }
-
- class LongPrimitive(
- preferences: SharedPreferences,
- keyFlow: Flow,
- key: String,
- defaultValue: Long,
- ) : AndroidPreference(preferences, keyFlow, key, defaultValue) {
- override fun read(preferences: SharedPreferences, key: String, defaultValue: Long): Long {
- return preferences.getLong(key, defaultValue)
- }
-
- override fun write(key: String, value: Long): Editor.() -> Unit = {
- putLong(key, value)
- }
- }
-
- class IntPrimitive(
- preferences: SharedPreferences,
- keyFlow: Flow,
- key: String,
- defaultValue: Int,
- ) : AndroidPreference(preferences, keyFlow, key, defaultValue) {
- override fun read(preferences: SharedPreferences, key: String, defaultValue: Int): Int {
- return preferences.getInt(key, defaultValue)
- }
-
- override fun write(key: String, value: Int): Editor.() -> Unit = {
- putInt(key, value)
- }
- }
-
- class FloatPrimitive(
- preferences: SharedPreferences,
- keyFlow: Flow,
- key: String,
- defaultValue: Float,
- ) : AndroidPreference(preferences, keyFlow, key, defaultValue) {
- override fun read(preferences: SharedPreferences, key: String, defaultValue: Float): Float {
- return preferences.getFloat(key, defaultValue)
- }
-
- override fun write(key: String, value: Float): Editor.() -> Unit = {
- putFloat(key, value)
- }
- }
-
- class BooleanPrimitive(
- preferences: SharedPreferences,
- keyFlow: Flow,
- key: String,
- defaultValue: Boolean,
- ) : AndroidPreference(preferences, keyFlow, key, defaultValue) {
- override fun read(
- preferences: SharedPreferences,
- key: String,
- defaultValue: Boolean,
- ): Boolean {
- return preferences.getBoolean(key, defaultValue)
- }
-
- override fun write(key: String, value: Boolean): Editor.() -> Unit = {
- putBoolean(key, value)
- }
- }
-
- class StringSetPrimitive(
- preferences: SharedPreferences,
- keyFlow: Flow,
- key: String,
- defaultValue: Set,
- ) : AndroidPreference>(preferences, keyFlow, key, defaultValue) {
- override fun read(
- preferences: SharedPreferences,
- key: String,
- defaultValue: Set,
- ): Set {
- return preferences.getStringSet(key, defaultValue) ?: defaultValue
- }
-
- override fun write(key: String, value: Set): Editor.() -> Unit = {
- putStringSet(key, value)
- }
- }
-
- class ObjectAsString(
- preferences: SharedPreferences,
- keyFlow: Flow,
- key: String,
- defaultValue: T,
- private val serializer: (T) -> String,
- private val deserializer: (String) -> T,
- ) : AndroidPreference(preferences, keyFlow, key, defaultValue) {
- override fun read(preferences: SharedPreferences, key: String, defaultValue: T): T {
- return try {
- preferences.getString(key, null)?.let(deserializer) ?: defaultValue
- } catch (e: Exception) {
- defaultValue
- }
- }
-
- override fun write(key: String, value: T): Editor.() -> Unit = {
- putString(key, serializer(value))
- }
- }
-
- class ObjectAsInt(
- preferences: SharedPreferences,
- keyFlow: Flow,
- key: String,
- defaultValue: T,
- private val serializer: (T) -> Int,
- private val deserializer: (Int) -> T,
- ) : AndroidPreference(preferences, keyFlow, key, defaultValue) {
- override fun read(preferences: SharedPreferences, key: String, defaultValue: T): T {
- return try {
- if (preferences.contains(key)) preferences.getInt(key, 0).let(deserializer) else defaultValue
- } catch (e: Exception) {
- defaultValue
- }
- }
-
- override fun write(key: String, value: T): Editor.() -> Unit = {
- putInt(key, serializer(value))
- }
- }
-
- class ObjectSetAsStringSet(
- preferences: SharedPreferences,
- keyFlow: Flow,
- key: String,
- defaultValue: Set,
- private val serializer: (T) -> String,
- private val deserializer: (String) -> T?,
- ) : AndroidPreference>(preferences, keyFlow, key, defaultValue) {
- override fun read(preferences: SharedPreferences, key: String, defaultValue: Set): Set {
- return try {
- preferences.getStringSet(key, null)?.mapNotNull(deserializer)?.toSet() ?: defaultValue
- } catch (_: Exception) {
- defaultValue
- }
- }
-
- override fun write(key: String, value: Set): Editor.() -> Unit = {
- putStringSet(key, value.map(serializer).toSet())
- }
- }
-}
diff --git a/shared/src/androidMain/kotlin/com/mihon/common/preference/AndroidPreferenceStore.kt b/shared/src/androidMain/kotlin/com/mihon/common/preference/AndroidPreferenceStore.kt
deleted file mode 100644
index 192bc77ed..000000000
--- a/shared/src/androidMain/kotlin/com/mihon/common/preference/AndroidPreferenceStore.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-package com.mihon.common.preference
-
-import android.content.Context
-import android.content.SharedPreferences
-import androidx.preference.PreferenceManager
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.callbackFlow
-
-class AndroidPreferenceStore(private val sharedPreferences: SharedPreferences) : PreferenceStore {
- constructor(context: Context) : this(PreferenceManager.getDefaultSharedPreferences(context))
-
- private val keyFlow = sharedPreferences.keyFlow
-
- override fun getString(key: String, defaultValue: String): PreferenceData {
- return AndroidPreference.StringPrimitive(sharedPreferences, keyFlow, key, defaultValue)
- }
-
- override fun getLong(key: String, defaultValue: Long): PreferenceData {
- return AndroidPreference.LongPrimitive(sharedPreferences, keyFlow, key, defaultValue)
- }
-
- override fun getInt(key: String, defaultValue: Int): PreferenceData {
- return AndroidPreference.IntPrimitive(sharedPreferences, keyFlow, key, defaultValue)
- }
-
- override fun getFloat(key: String, defaultValue: Float): PreferenceData {
- return AndroidPreference.FloatPrimitive(sharedPreferences, keyFlow, key, defaultValue)
- }
-
- override fun getBoolean(key: String, defaultValue: Boolean): PreferenceData {
- return AndroidPreference.BooleanPrimitive(sharedPreferences, keyFlow, key, defaultValue)
- }
-
- override fun getStringSet(key: String, defaultValue: Set): PreferenceData> {
- return AndroidPreference.StringSetPrimitive(sharedPreferences, keyFlow, key, defaultValue)
- }
-
- override fun getObjectFromString(
- key: String,
- defaultValue: T,
- serializer: (T) -> String,
- deserializer: (String) -> T,
- ): PreferenceData {
- return AndroidPreference.ObjectAsString(
- preferences = sharedPreferences,
- keyFlow = keyFlow,
- key = key,
- defaultValue = defaultValue,
- serializer = serializer,
- deserializer = deserializer,
- )
- }
-
- override fun getObjectFromInt(
- key: String,
- defaultValue: T,
- serializer: (T) -> Int,
- deserializer: (Int) -> T,
- ): PreferenceData {
- return AndroidPreference.ObjectAsInt(
- preferences = sharedPreferences,
- keyFlow = keyFlow,
- key = key,
- defaultValue = defaultValue,
- serializer = serializer,
- deserializer = deserializer,
- )
- }
-
- override fun getObjectSetFromStringSet(
- key: String,
- defaultValue: Set,
- serializer: (T) -> String,
- deserializer: (String) -> T?,
- ): PreferenceData> {
- return AndroidPreference.ObjectSetAsStringSet(
- preferences = sharedPreferences,
- keyFlow = keyFlow,
- key = key,
- defaultValue = defaultValue,
- serializer = serializer,
- deserializer = deserializer,
- )
- }
-
- override fun getAll(): Map {
- return sharedPreferences.all ?: emptyMap()
- }
-}
-
-private val SharedPreferences.keyFlow
- get() = callbackFlow {
- val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key: String? ->
- trySend(
- key,
- )
- }
- registerOnSharedPreferenceChangeListener(listener)
- awaitClose {
- unregisterOnSharedPreferenceChangeListener(listener)
- }
- }
\ No newline at end of file
diff --git a/shared/src/androidMain/kotlin/com/mihon/common/preference/DataPreference.kt b/shared/src/androidMain/kotlin/com/mihon/common/preference/DataPreference.kt
deleted file mode 100644
index dcf4eedfa..000000000
--- a/shared/src/androidMain/kotlin/com/mihon/common/preference/DataPreference.kt
+++ /dev/null
@@ -1,238 +0,0 @@
-package com.mihon.common.preference
-
-import android.content.SharedPreferences
-import android.content.SharedPreferences.Editor
-import androidx.core.content.edit
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.conflate
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.stateIn
-
-
-sealed class DataPreference(
- private val preferences: SharedPreferences,
- private val keyFlow: MutableSharedFlow,
- private val key: String,
- private val defaultValue: T,
-) : PreferenceData {
-
- abstract fun read(preferences: SharedPreferences, key: String, defaultValue: T): T
-
- abstract fun write(key: String, value: T): Editor.() -> Unit
-
- override fun key(): String {
- return key
- }
-
- override fun get(): T {
- return try {
- read(preferences, key, defaultValue)
- } catch (e: ClassCastException) {
- //logcat { "Invalid value for $key; deleting" }
- delete()
- defaultValue
- }
- }
-
- override fun set(value: T) {
- preferences.edit(action = write(key, value))
- keyFlow.tryEmit(key)
- }
-
- override fun isSet(): Boolean {
- return preferences.contains(key)
- }
-
- override fun delete() {
- preferences.edit {
- remove(key)
- }
- }
-
- override fun defaultValue(): T {
- return defaultValue
- }
-
- override fun changes(): Flow {
- return keyFlow
- .filter { it == key || it == null }
- .onStart { emit("ignition") }
- .map { get() }
- .conflate()
- }
-
- override fun stateIn(scope: CoroutineScope): StateFlow {
- return changes().stateIn(scope, SharingStarted.Eagerly, get())
- }
-
- class StringPrimitive(
- preferences: SharedPreferences,
- keyFlow: MutableSharedFlow,
- key: String,
- defaultValue: String,
- ) : DataPreference(preferences, keyFlow, key, defaultValue) {
- override fun read(
- preferences: SharedPreferences,
- key: String,
- defaultValue: String,
- ): String {
- return preferences.getString(key, defaultValue) ?: defaultValue
- }
-
- override fun write(key: String, value: String): Editor.() -> Unit = {
- putString(key, value)
- }
- }
-
- class LongPrimitive(
- preferences: SharedPreferences,
- keyFlow: MutableSharedFlow,
- key: String,
- defaultValue: Long,
- ) : DataPreference(preferences, keyFlow, key, defaultValue) {
- override fun read(preferences: SharedPreferences, key: String, defaultValue: Long): Long {
- return preferences.getLong(key, defaultValue)
- }
-
- override fun write(key: String, value: Long): Editor.() -> Unit = {
- putLong(key, value)
- }
- }
-
- class IntPrimitive(
- preferences: SharedPreferences,
- keyFlow: MutableSharedFlow,
- key: String,
- defaultValue: Int,
- ) : DataPreference(preferences, keyFlow, key, defaultValue) {
- override fun read(preferences: SharedPreferences, key: String, defaultValue: Int): Int {
- return preferences.getInt(key, defaultValue)
- }
-
- override fun write(key: String, value: Int): Editor.() -> Unit = {
- putInt(key, value)
- }
- }
-
- class FloatPrimitive(
- preferences: SharedPreferences,
- keyFlow: MutableSharedFlow,
- key: String,
- defaultValue: Float,
- ) : DataPreference(preferences, keyFlow, key, defaultValue) {
- override fun read(preferences: SharedPreferences, key: String, defaultValue: Float): Float {
- return preferences.getFloat(key, defaultValue)
- }
-
- override fun write(key: String, value: Float): Editor.() -> Unit = {
- putFloat(key, value)
- }
- }
-
- class BooleanPrimitive(
- preferences: SharedPreferences,
- keyFlow: MutableSharedFlow,
- key: String,
- defaultValue: Boolean,
- ) : DataPreference(preferences, keyFlow, key, defaultValue) {
- override fun read(
- preferences: SharedPreferences,
- key: String,
- defaultValue: Boolean,
- ): Boolean {
- return preferences.getBoolean(key, defaultValue)
- }
-
- override fun write(key: String, value: Boolean): Editor.() -> Unit = {
- putBoolean(key, value)
- }
- }
-
- class StringSetPrimitive(
- preferences: SharedPreferences,
- keyFlow: MutableSharedFlow,
- key: String,
- defaultValue: Set,
- ) : DataPreference>(preferences, keyFlow, key, defaultValue) {
- override fun read(
- preferences: SharedPreferences,
- key: String,
- defaultValue: Set,
- ): Set {
- return preferences.getStringSet(key, defaultValue) ?: defaultValue
- }
-
- override fun write(key: String, value: Set): Editor.() -> Unit = {
- putStringSet(key, value)
- }
- }
-
- class ObjectAsString(
- preferences: SharedPreferences,
- keyFlow: MutableSharedFlow,
- key: String,
- defaultValue: T,
- private val serializer: (T) -> String,
- private val deserializer: (String) -> T,
- ) : DataPreference(preferences, keyFlow, key, defaultValue) {
- override fun read(preferences: SharedPreferences, key: String, defaultValue: T): T {
- return try {
- preferences.getString(key, null)?.let(deserializer) ?: defaultValue
- } catch (e: Exception) {
- defaultValue
- }
- }
-
- override fun write(key: String, value: T): Editor.() -> Unit = {
- putString(key, serializer(value))
- }
- }
-
- class ObjectAsInt(
- preferences: SharedPreferences,
- keyFlow: MutableSharedFlow,
- key: String,
- defaultValue: T,
- private val serializer: (T) -> Int,
- private val deserializer: (Int) -> T,
- ) : DataPreference(preferences, keyFlow, key, defaultValue) {
- override fun read(preferences: SharedPreferences, key: String, defaultValue: T): T {
- return try {
- if (preferences.contains(key)) preferences.getInt(key, 0).let(deserializer) else defaultValue
- } catch (e: Exception) {
- defaultValue
- }
- }
-
- override fun write(key: String, value: T): Editor.() -> Unit = {
- putInt(key, serializer(value))
- }
- }
-
- class ObjectSetAsStringSet(
- preferences: SharedPreferences,
- keyFlow: MutableSharedFlow,
- key: String,
- defaultValue: Set,
- private val serializer: (T) -> String,
- private val deserializer: (String) -> T?,
- ) : DataPreference>(preferences, keyFlow, key, defaultValue) {
- override fun read(preferences: SharedPreferences, key: String, defaultValue: Set): Set {
- return try {
- preferences.getStringSet(key, null)?.mapNotNull(deserializer)?.toSet() ?: defaultValue
- } catch (_: Exception) {
- defaultValue
- }
- }
-
- override fun write(key: String, value: Set): Editor.() -> Unit = {
- putStringSet(key, value.map(serializer).toSet())
- }
- }
-}
\ No newline at end of file
diff --git a/shared/src/androidMain/kotlin/com/mihon/common/preference/DataPreferenceStore.kt b/shared/src/androidMain/kotlin/com/mihon/common/preference/DataPreferenceStore.kt
deleted file mode 100644
index 4f197768d..000000000
--- a/shared/src/androidMain/kotlin/com/mihon/common/preference/DataPreferenceStore.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-package com.mihon.common.preference
-
-import android.content.Context
-import android.content.SharedPreferences
-import com.lagradost.cloudstream3.utils.AppUtils.parseJson
-import com.lagradost.cloudstream3.utils.AppUtils.toJsonLiteral
-import kotlinx.coroutines.flow.MutableSharedFlow
-
-/**
- * Same as AndroidPreferenceStore for DataStore but with some optimization to only see changes when values are
- * changed by this store. This is done to avoid having a global key listener where we might pollute
- * the setKey from other places.
- * */
-class DataPreferenceStore(private val sharedPreferences: SharedPreferences) : PreferenceStore {
- constructor(context: Context) : this(
- context.getSharedPreferences(
- "rebuild_preference",
- Context.MODE_PRIVATE
- )
- )
-
- companion object {
- private val keyFlow: MutableSharedFlow =
- MutableSharedFlow(extraBufferCapacity = 16)
- }
-
- override fun getString(key: String, defaultValue: String): PreferenceData {
- return DataPreference.StringPrimitive(sharedPreferences, keyFlow, key, defaultValue)
- }
-
- override fun getLong(key: String, defaultValue: Long): PreferenceData {
- return DataPreference.LongPrimitive(sharedPreferences, keyFlow, key, defaultValue)
- }
-
- override fun getInt(key: String, defaultValue: Int): PreferenceData {
- return DataPreference.IntPrimitive(sharedPreferences, keyFlow, key, defaultValue)
- }
-
- override fun getFloat(key: String, defaultValue: Float): PreferenceData {
- return DataPreference.FloatPrimitive(sharedPreferences, keyFlow, key, defaultValue)
- }
-
- override fun getBoolean(key: String, defaultValue: Boolean): PreferenceData {
- return DataPreference.BooleanPrimitive(sharedPreferences, keyFlow, key, defaultValue)
- }
-
- override fun getStringSet(key: String, defaultValue: Set): PreferenceData> {
- return DataPreference.StringSetPrimitive(sharedPreferences, keyFlow, key, defaultValue)
- }
-
- inline fun getJson(key: String, defaultValue: T): PreferenceData =
- getObjectFromString(key, defaultValue, serializer = { obj ->
- obj.toJsonLiteral()
- }, deserializer = { json ->
- parseJson(json)
- })
-
- override fun getObjectFromString(
- key: String,
- defaultValue: T,
- serializer: (T) -> String,
- deserializer: (String) -> T,
- ): PreferenceData {
- return DataPreference.ObjectAsString(
- preferences = sharedPreferences,
- keyFlow = keyFlow,
- key = key,
- defaultValue = defaultValue,
- serializer = serializer,
- deserializer = deserializer,
- )
- }
-
- override fun getObjectFromInt(
- key: String,
- defaultValue: T,
- serializer: (T) -> Int,
- deserializer: (Int) -> T,
- ): PreferenceData {
- return DataPreference.ObjectAsInt(
- preferences = sharedPreferences,
- keyFlow = keyFlow,
- key = key,
- defaultValue = defaultValue,
- serializer = serializer,
- deserializer = deserializer,
- )
- }
-
- override fun getObjectSetFromStringSet(
- key: String,
- defaultValue: Set,
- serializer: (T) -> String,
- deserializer: (String) -> T?,
- ): PreferenceData> {
- return DataPreference.ObjectSetAsStringSet(
- preferences = sharedPreferences,
- keyFlow = keyFlow,
- key = key,
- defaultValue = defaultValue,
- serializer = serializer,
- deserializer = deserializer,
- )
- }
-
- override fun getAll(): Map {
- return sharedPreferences.all ?: emptyMap()
- }
-}
diff --git a/shared/src/androidMain/kotlin/com/mihon/common/preference/StatePreferenceStore.kt b/shared/src/androidMain/kotlin/com/mihon/common/preference/StatePreferenceStore.kt
deleted file mode 100644
index f6346427a..000000000
--- a/shared/src/androidMain/kotlin/com/mihon/common/preference/StatePreferenceStore.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.mihon.common.preference
-
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.snapshotFlow
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-import kotlin.reflect.KProperty1
-
-/** Adds a store to modify a specific state field by field */
-data class StatePreferenceData(
- val state: MutableState,
- val get: (T) -> V,
- val set: T.(V) -> T,
-) : PreferenceData {
- override fun key(): String = ""
-
- override fun get(): V {
- return get(state.value)
- }
-
- override fun set(value: V) {
- state.value = set(state.value, value)
- }
-
- override fun isSet(): Boolean {
- throw NotImplementedError()
- }
-
- override fun delete() {
- throw NotImplementedError()
- }
-
- override fun defaultValue(): V {
- throw NotImplementedError()
- }
-
- override fun changes(): Flow =
- snapshotFlow { state.value }.map { s -> get(s) }.distinctUntilChanged()
-
- override fun stateIn(scope: CoroutineScope): StateFlow {
- return changes().stateIn(scope, SharingStarted.Eagerly, get())
- }
-}
-
-data class StatePreferenceStore(val state: MutableState) {
- fun field(
- property: KProperty1,
- update: T.(V) -> T,
- ): PreferenceData {
- return StatePreferenceData(state, property::get, update)
- }
-
- fun field(
- get: T.() -> V,
- set: T.(V) -> T,
- ): PreferenceData {
- return StatePreferenceData(state, get, set)
- }
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/composeResources/drawable/arrow_back.xml b/shared/src/commonMain/composeResources/drawable/arrow_back.xml
deleted file mode 100644
index 196b4c087..000000000
--- a/shared/src/commonMain/composeResources/drawable/arrow_back.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
diff --git a/shared/src/commonMain/composeResources/drawable/arrow_downward.xml b/shared/src/commonMain/composeResources/drawable/arrow_downward.xml
deleted file mode 100644
index 061a6db1b..000000000
--- a/shared/src/commonMain/composeResources/drawable/arrow_downward.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/shared/src/commonMain/composeResources/drawable/arrow_upward.xml b/shared/src/commonMain/composeResources/drawable/arrow_upward.xml
deleted file mode 100644
index fbf89a796..000000000
--- a/shared/src/commonMain/composeResources/drawable/arrow_upward.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/shared/src/commonMain/composeResources/drawable/cancel.xml b/shared/src/commonMain/composeResources/drawable/cancel.xml
deleted file mode 100644
index 570986990..000000000
--- a/shared/src/commonMain/composeResources/drawable/cancel.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/shared/src/commonMain/composeResources/drawable/check.xml b/shared/src/commonMain/composeResources/drawable/check.xml
deleted file mode 100644
index 6976b0df2..000000000
--- a/shared/src/commonMain/composeResources/drawable/check.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/shared/src/commonMain/composeResources/drawable/close.xml b/shared/src/commonMain/composeResources/drawable/close.xml
deleted file mode 100644
index 14beedf9b..000000000
--- a/shared/src/commonMain/composeResources/drawable/close.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/shared/src/commonMain/composeResources/drawable/default_icon.png b/shared/src/commonMain/composeResources/drawable/default_icon.png
deleted file mode 100644
index 394459001..000000000
Binary files a/shared/src/commonMain/composeResources/drawable/default_icon.png and /dev/null differ
diff --git a/shared/src/commonMain/composeResources/drawable/error.xml b/shared/src/commonMain/composeResources/drawable/error.xml
deleted file mode 100644
index b77c10cfa..000000000
--- a/shared/src/commonMain/composeResources/drawable/error.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/shared/src/commonMain/composeResources/drawable/info.xml b/shared/src/commonMain/composeResources/drawable/info.xml
deleted file mode 100644
index 32d30b88a..000000000
--- a/shared/src/commonMain/composeResources/drawable/info.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/shared/src/commonMain/composeResources/drawable/preview.xml b/shared/src/commonMain/composeResources/drawable/preview.xml
deleted file mode 100644
index d56fbde6e..000000000
--- a/shared/src/commonMain/composeResources/drawable/preview.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
diff --git a/shared/src/commonMain/composeResources/font/productsans_black.ttf b/shared/src/commonMain/composeResources/font/productsans_black.ttf
deleted file mode 100644
index 5864e2690..000000000
Binary files a/shared/src/commonMain/composeResources/font/productsans_black.ttf and /dev/null differ
diff --git a/shared/src/commonMain/composeResources/font/productsans_blackitalic.ttf b/shared/src/commonMain/composeResources/font/productsans_blackitalic.ttf
deleted file mode 100644
index 869037b78..000000000
Binary files a/shared/src/commonMain/composeResources/font/productsans_blackitalic.ttf and /dev/null differ
diff --git a/shared/src/commonMain/composeResources/font/productsans_bold.ttf b/shared/src/commonMain/composeResources/font/productsans_bold.ttf
deleted file mode 100644
index 96619df92..000000000
Binary files a/shared/src/commonMain/composeResources/font/productsans_bold.ttf and /dev/null differ
diff --git a/shared/src/commonMain/composeResources/font/productsans_bolditalic.ttf b/shared/src/commonMain/composeResources/font/productsans_bolditalic.ttf
deleted file mode 100644
index bdd22b046..000000000
Binary files a/shared/src/commonMain/composeResources/font/productsans_bolditalic.ttf and /dev/null differ
diff --git a/shared/src/commonMain/composeResources/font/productsans_italic.ttf b/shared/src/commonMain/composeResources/font/productsans_italic.ttf
deleted file mode 100644
index 5b44037be..000000000
Binary files a/shared/src/commonMain/composeResources/font/productsans_italic.ttf and /dev/null differ
diff --git a/shared/src/commonMain/composeResources/font/productsans_light.ttf b/shared/src/commonMain/composeResources/font/productsans_light.ttf
deleted file mode 100644
index 33de1c39f..000000000
Binary files a/shared/src/commonMain/composeResources/font/productsans_light.ttf and /dev/null differ
diff --git a/shared/src/commonMain/composeResources/font/productsans_lightitalic.ttf b/shared/src/commonMain/composeResources/font/productsans_lightitalic.ttf
deleted file mode 100644
index 00c76b8ec..000000000
Binary files a/shared/src/commonMain/composeResources/font/productsans_lightitalic.ttf and /dev/null differ
diff --git a/shared/src/commonMain/composeResources/font/productsans_medium.ttf b/shared/src/commonMain/composeResources/font/productsans_medium.ttf
deleted file mode 100644
index fd818d6f5..000000000
Binary files a/shared/src/commonMain/composeResources/font/productsans_medium.ttf and /dev/null differ
diff --git a/shared/src/commonMain/composeResources/font/productsans_mediumitalic.ttf b/shared/src/commonMain/composeResources/font/productsans_mediumitalic.ttf
deleted file mode 100644
index ec70cd384..000000000
Binary files a/shared/src/commonMain/composeResources/font/productsans_mediumitalic.ttf and /dev/null differ
diff --git a/shared/src/commonMain/composeResources/font/productsans_regular.ttf b/shared/src/commonMain/composeResources/font/productsans_regular.ttf
deleted file mode 100644
index e2c69c3fb..000000000
Binary files a/shared/src/commonMain/composeResources/font/productsans_regular.ttf and /dev/null differ
diff --git a/shared/src/commonMain/composeResources/font/productsans_thin.ttf b/shared/src/commonMain/composeResources/font/productsans_thin.ttf
deleted file mode 100644
index 5e046b532..000000000
Binary files a/shared/src/commonMain/composeResources/font/productsans_thin.ttf and /dev/null differ
diff --git a/shared/src/commonMain/composeResources/font/productsans_thinitalic.ttf b/shared/src/commonMain/composeResources/font/productsans_thinitalic.ttf
deleted file mode 100644
index 7b0495518..000000000
Binary files a/shared/src/commonMain/composeResources/font/productsans_thinitalic.ttf and /dev/null differ
diff --git a/shared/src/commonMain/composeResources/values/strings.xml b/shared/src/commonMain/composeResources/values/strings.xml
deleted file mode 100644
index e5eca5dd2..000000000
--- a/shared/src/commonMain/composeResources/values/strings.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
- CloudStream
- None
- No results found
- Ok
- Cancel
- Transparency
- Luminance
- Color
- Color Wheel
- Navigate up
- Lorem ipsum
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.kt
deleted file mode 100644
index 168b82590..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.kt
+++ /dev/null
@@ -1,205 +0,0 @@
-package com.lagradost.cloudstream4
-
-import androidx.compose.runtime.Composable
-import com.lagradost.cloudstream3.AllLanguagesName
-import com.lagradost.cloudstream3.DubStatus
-import com.lagradost.cloudstream3.SearchQuality
-import com.lagradost.cloudstream3.TvType
-import com.mihon.common.preference.PreferenceStore
-import com.mihon.common.preference.getEnumSet
-
-@Composable
-expect fun rememberAppSettings(): AppSettings
-
-/**
- * App settings for every setting we have, that way we can just inject AppSettings into a viewmodel,
- * or similar.
- *
- * We use an internal constructor to force the user to construct it using the plantform specific
- * initalizer
- * */
-class AppSettings internal constructor(
- preferences: PreferenceStore,
- // Todo add database key-value interface here, possibly using the same PreferenceStore interface
-) {
- val general = GeneralPreferences(preferences)
- val player = PlayerPreferences(preferences)
- val provider = ProviderPreferences(preferences)
- val ui = UIPreferences(preferences)
- val security = SecurityPreferences(preferences)
- val updates = UpdatePreferences(preferences)
- val backup = BackupPreferences(preferences)
- val plugins = PluginPreferences(preferences)
-}
-
-class PluginPreferences(preferences: PreferenceStore) {
- val autoUpdate = preferences.getBoolean("auto_update_plugins", true)
- val autoDownload = preferences.getInt("auto_download_plugins_key2", 0)
-}
-
-class BackupPreferences(preferences: PreferenceStore) {
- val frequency = preferences.getInt("automatic_backup_key", 0)
- val path = preferences.getString("backup_path_key")
- val visualPath = preferences.getString("backup_dir_key")
-}
-class UpdatePreferences(preferences: PreferenceStore) {
- val apkInstaller = preferences.getInt("apk_installer_key",1)
- val showAppUpdates = preferences.getBoolean("auto_update", true)
- // Node id for this update
- val skipUpdate = preferences.getString("skip_update_key", "")
-}
-
-class SecurityPreferences(preferences: PreferenceStore) {
- val biometrics = preferences.getBoolean(
- "biometric_key", false
- )
- val skipAccountSelection = preferences.getBoolean(
- "skip_startup_account_select_key", false
- )
-}
-
-class UIPreferences(preferences: PreferenceStore) {
- val primaryColor = preferences.getString(
- "primary_color_key", "Normal"
- )
- val theme = preferences.getString(
- "app_theme_key", "AmoledLight"
- )
- val layout = preferences.getInt(
- "app_layout_key", -1
- )
- val bottomTitle = preferences.getBoolean(
- "bottom_title_key", true
- )
- val advancedSearch = preferences.getBoolean(
- "advanced_search", true
- )
- val searchSuggestions = preferences.getBoolean(
- "search_suggestions_enabled", true
- )
- val kitsuPostersEnabled = preferences.getBoolean(
- "show_kitsu_posters_key", true
- )
- val trailersEnabled = preferences.getBoolean(
- "show_trailers_key", true
- )
- val castEnabled = preferences.getBoolean(
- "show_cast_in_details_key", true
- )
- val fillersEnabled = preferences.getBoolean(
- "show_fillers_key", false
- )
- val showMetadataOverlay = preferences.getBoolean(
- "show_player_metadata_key", true
- )
- val overscanDp = preferences.getInt(
- "overscan_key", 0
- )
- val posterSize = preferences.getInt(
- "poster_size_key", 0
- )
- val posterShowHd = preferences.getBoolean(
- "show_hd_key", true
- )
- val posterShowDub = preferences.getBoolean(
- "show_dub_key", true
- )
- val posterShowSub = preferences.getBoolean(
- "show_sub_key", true
- )
- val posterShowRating = preferences.getBoolean(
- "show_rating_key", true
- )
- val posterShowTitle = preferences.getBoolean(
- "show_title_key", true
- )
- val posterShowEpisode = preferences.getBoolean(
- "show_episode_text_key", true
- )
- val showClock = preferences.getBoolean(
- "tv_layout_clock_key", false
- )
-
- val randomButtonEnabled = preferences.getBoolean(
- "random_button_key", false
- )
-
- val confirmExit = preferences.getInt(
- "confirm_exit_key", -1
- )
-
- /** This had to be refactored to use enumSet because the old system is prone to bugs */
- val filterQuality = preferences.getEnumSet(
- "pref_filter_search_quality_key2", emptySet()
- )
-}
-
-class ProviderPreferences(preferences: PreferenceStore) {
- companion object {
- private val defaultPreferredMedia =
- TvType.entries.filter { it != TvType.NSFW }.map { it.ordinal.toString() }.toSet()
- private val defaultDub = DubStatus.entries.map { it.name }.toSet()
- }
-
- val preferredMedia = preferences.getStringSet(
- "prefer_media_type_key_2", defaultPreferredMedia
- )
-
- val extensionLanguages = preferences.getStringSet(
- "provider_lang_key", setOf(AllLanguagesName)
- )
-
- val displayDubSub = preferences.getStringSet(
- "display_sub_key", defaultDub
- )
-}
-
-class PlayerPreferences(preferences: PreferenceStore) {
- val episodeSync = preferences.getBoolean("episode_sync_enabled_key", true)
- val defaultPlayer = preferences.getString("player_default_key", "")
- val limitPlayerTitle = preferences.getInt("prefer_limit_title_key", 0)
- val hidePlayerControlNames = preferences.getBoolean("hide_player_control_names_key", false)
- val showName = preferences.getBoolean("show_name", true)
- val showResolution = preferences.getBoolean("show_resolution", true)
- val showMediaInfo = preferences.getBoolean("show_media_info", false)
- val pipEnabled = preferences.getBoolean("pip_enabled_key", true)
- val resizeEnabled = preferences.getBoolean("player_resize_enabled_key", true)
- val speedEnabled = preferences.getBoolean("playback_speed_enabled_key", false)
- val tiktokEnabled = preferences.getBoolean("speedup_key", false)
- val autoPlayEnabled = preferences.getBoolean("autoplay_next_key", true)
- val startPaused = preferences.getBoolean("start_paused_key", false)
- val skipOpEnabled = preferences.getBoolean("enable_skip_op_from_database", true)
- val autoRotateEnabled = preferences.getBoolean("auto_rotate_video_key", true)
- val rotateButtonEnabled = preferences.getBoolean("rotate_video_key", false)
- val previewBarEnabled = preferences.getBoolean("preview_seekbar_key", true)
- val softwareDecoding = preferences.getInt("software_decoding_key2", -1)
- val extraBrightnessEnabled = preferences.getBoolean("extra_brightness_key", false)
- val swipeHorizontalEnabled = preferences.getBoolean("swipe_enabled_key", true)
- val swipeVerticalEnabled = preferences.getBoolean("swipe_vertical_enabled_key", true)
- val doubleTapToSeekEnabled = preferences.getBoolean("double_tap_enabled_key", false)
- val doubleTapToPauseEnabled = preferences.getBoolean("double_tap_pause_enabled_key", false)
- val doubleTapTime = preferences.getInt("double_tap_seek_time_key2", 10)
- val bufferDiskMB = preferences.getInt("video_buffer_disk_key", 0)
- val bufferRamMB = preferences.getInt("video_buffer_size_key", 0)
- val bufferTimeSec = preferences.getInt("video_buffer_length_key", 0)
- val tvSeekOnTime = preferences.getInt("android_tv_interface_on_seek_key", 10)
- val tvSeekOffTime = preferences.getInt("android_tv_interface_off_seek_key", 10)
-}
-
-class GeneralPreferences(preferences: PreferenceStore) {
- val locale = preferences.getString("app_locale", "")
- val bananas = preferences.getInt("benene_count", 0)
- val parallelDownloads = preferences.getInt("download_parallel_key", 3)
- val concurrentConnections = preferences.getInt("download_concurrent_key", 3)
- val batterOptimization = preferences.getBoolean("battery_optimisation_key", false)
-
- /** Please note that it used R.array.dns_pref before, this was a bug and caused this setting to be language sensitive **/
- val dns = preferences.getInt("dns_key", 0)
-
- /** Very weird setKey based usage, just having this setting might not do it */
- val jsdelivrProxy = preferences.getBoolean("jsdelivr_proxy_key", false)
-
- /** This should honesty be refactored to a single setting */
- val downloadPath = preferences.getString("download_path_key", "")
- val downloadPathVisual = preferences.getString("download_path_key_visual", "")
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/ColorDialog.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/ColorDialog.kt
deleted file mode 100644
index c1abc206d..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/ColorDialog.kt
+++ /dev/null
@@ -1,375 +0,0 @@
-package com.lagradost.cloudstream4.compose
-
-import androidx.compose.foundation.Canvas
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.horizontalScroll
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.FlowRow
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.drawscope.DrawScope
-import androidx.compose.ui.hapticfeedback.HapticFeedbackType
-import androidx.compose.ui.platform.LocalHapticFeedback
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.DialogProperties
-import com.lagradost.cloudstream4.compose.colorpicker.DoubleColorPicker
-import com.lagradost.cloudstream4.compose.colorpicker.HsvColor
-import com.lagradost.cloudstream4.generated.resources.Res
-import com.lagradost.cloudstream4.generated.resources.cancel
-import com.lagradost.cloudstream4.generated.resources.color
-import com.lagradost.cloudstream4.generated.resources.color_wheel
-import com.lagradost.cloudstream4.generated.resources.luminance
-import com.lagradost.cloudstream4.generated.resources.ok
-import com.lagradost.cloudstream4.generated.resources.transparency
-import com.mihon.material.Slider
-import com.mihon.material.padding
-import org.jetbrains.compose.resources.stringResource
-import kotlin.math.abs
-import kotlin.math.max
-import kotlin.math.min
-import kotlin.math.roundToInt
-
-
-internal val colors = arrayOf(
- Color(0xFFF44336), // RED 500
- Color(0xFFE91E63), // PINK 500
- Color(0xFFFF2C93), // LIGHT PINK 500
- Color(0xFF9C27B0), // PURPLE 500
- Color(0xFF673AB7), // DEEP PURPLE 500
- Color(0xFF3F51B5), // INDIGO 500
- Color(0xFF2196F3), // BLUE 500
- Color(0xFF03A9F4), // LIGHT BLUE 500
- Color(0xFF00BCD4), // CYAN 500
- Color(0xFF009688), // TEAL 500
- Color(0xFF4CAF50), // GREEN 500
- Color(0xFF8BC34A), // LIGHT GREEN 500
- Color(0xFFCDDC39), // LIME 500
- Color(0xFFFFEB3B), // YELLOW 500
- Color(0xFFFFC107), // AMBER 500
- Color(0xFFFF9800), // ORANGE 500
- Color(0xFF795548), // BROWN 500
- Color(0xFF607D8B), // BLUE GREY 500
- Color(0xFF9E9E9E), // GREY 500
-)
-internal val colors2 = arrayOf(
- Color(0xFF795548), // BROWN 500
- Color(0xFF607D8B), // BLUE GREY 500
- Color(0xFF9E9E9E), // GREY 500
-)
-
-@Composable
-fun ColorDialog(
- title: String,
- color: Color,
- dismiss: () -> Unit,
- confirm: (Color) -> Unit,
-) {
- var alpha by remember { mutableFloatStateOf(color.alpha) }
- var selectedColor by remember { mutableStateOf(HsvColor(color)) }
- val renderedColor = selectedColor.toColor().copy(alpha = alpha)
- var colorWheel by remember { mutableStateOf(false) }
-
- AlertDialog(
- properties = DialogProperties(usePlatformDefaultWidth = false),
- containerColor = MaterialTheme.colorScheme.background,
- onDismissRequest = dismiss,
- title = {
- Row(verticalAlignment = Alignment.CenterVertically) {
- ColorCircle(
- color = renderedColor,
- )
- Spacer(modifier = Modifier.size(MaterialTheme.padding.medium))
- Text(text = title)
- }
- },
- text = {
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .verticalScroll(rememberScrollState())
- ) {
- Text(
- text = stringResource(Res.string.color),
- color = MaterialTheme.colorScheme.onBackground,
- modifier = Modifier.padding(MaterialTheme.padding.extraSmall),
- style = MaterialTheme.typography.titleMedium
- )
- if (colorWheel) {
- Row(
- horizontalArrangement = Arrangement.Center,
- modifier = Modifier.padding(10.dp)
- ) {
- Spacer(modifier = Modifier.weight(1.0f))
- DoubleColorPicker(
- color = { selectedColor },
- onColorChange = { selectedColor = it },
- modifier = Modifier.size(200.dp),
- ringStrokeWidth = 20.dp,
- )
- Spacer(modifier = Modifier.weight(1.0f))
- }
- } else {
- FlowRow {
- ColorCircle(
- color = Color.White.copy(alpha = alpha),
- ) {
- selectedColor = HsvColor(Color.White)
- }
-
- val hsl = floatArrayOf(0.0f, 1.0f, 0.5f)
- (0..18).forEach { hue ->
- hsl[0] = hue.toFloat() * 18f
- val color = hslToColor(hsl)
- ColorCircle(
- color = color.copy(alpha = alpha),
- ) {
- selectedColor = HsvColor(color)
- }
- }
- colors2.forEach { color ->
- ColorCircle(
- color = color.copy(alpha = alpha),
- ) {
- selectedColor = HsvColor(color)
- }
- }
- }
- Text(
- text = stringResource(Res.string.luminance),
- color = MaterialTheme.colorScheme.onBackground,
- modifier = Modifier.padding(MaterialTheme.padding.extraSmall),
- style = MaterialTheme.typography.titleMedium
- )
-
- // https://github.com/mhssn95/compose-color-picker/blob/main/colorPicker/src/main/java/io/mhssn/colorpicker/ext/drawExt.kt
- Row(modifier = Modifier.horizontalScroll(rememberScrollState())) {
- val hsl = floatArrayOf(0.0f, 0.0f, 0.0f)
- (0..10).forEach { luminance ->
- rbgToHSL(
- selectedColor.red,
- selectedColor.green,
- selectedColor.blue,
- hsl
- )
- hsl[2] = luminance.toFloat() * 0.1f
- val color = hslToColor(hsl)
- ColorCircle(
- color = color.copy(alpha = alpha),
- ) {
- selectedColor = HsvColor(color)
- }
- }
- }
- }
-
- Text(
- text = stringResource(Res.string.transparency),
- color = MaterialTheme.colorScheme.onBackground,
- modifier = Modifier.padding(MaterialTheme.padding.extraSmall),
- style = MaterialTheme.typography.titleMedium
- )
- val haptic = LocalHapticFeedback.current
- Slider(
- modifier = Modifier.focusOutline().padding(MaterialTheme.padding.extraSmall),
- value = (alpha * 10.0f).toInt(),
- onValueChange = f@{ newValue ->
- alpha = newValue.toFloat() * 0.1f
- haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
- },
- valueRange = 0..10,
- )
- }
- },
- confirmButton = {
- WhiteButton(
- text = stringResource(Res.string.ok),
- onClick = { confirm(renderedColor) })
- },
- dismissButton = {
- BlackButton(text = stringResource(Res.string.color_wheel), onClick = {
- colorWheel = !colorWheel
- })
-
- BlackButton(text = stringResource(Res.string.cancel), onClick = dismiss)
- })
-}
-
-@Composable
-fun ColorCircle(
- color: Color,
- modifier: Modifier = Modifier,
- size: Dp = 50.dp,
- clickable: (() -> Unit)? = null
-) {
- Canvas(
- modifier = modifier
- .focusOutline(shape = CircleShape)
- .padding(MaterialTheme.padding.extraSmall)
- .size(size)
- .border(
- 1.dp,
- if (color.alpha > 0.5f) MaterialTheme.colorScheme.onBackground else color.copy(
- alpha = 1.0f
- ),
- CircleShape
- )
- .circle()
- .clickable(enabled = clickable != null) {
- clickable?.invoke()
- }
- ) {
- drawTransparentBackground(10)
- drawRect(color)
- }
-}
-
-internal fun DrawScope.drawTransparentBackground(verticalBoxesSize: Int = 10) {
- val boxSize = size.height / verticalBoxesSize
- repeat((size.width / boxSize).roundToInt() + 1) { x ->
- repeat(verticalBoxesSize) { y ->
- drawRect(
- if ((y + x) % 2 == 0) {
- Color.LightGray
- } else {
- Color.White
- }, topLeft = Offset(x * boxSize, y * boxSize), size = Size(boxSize, boxSize)
- )
- }
- }
-}
-
-/**
- * Convert RGB components to HSL (hue-saturation-lightness).
- *
- * * outHsl[0] is Hue [0, 360)
- * * outHsl[1] is Saturation [0, 1]
- * * outHsl[2] is Lightness [0, 1]
- *
- *
- * @param rf red component value [0, 1]
- * @param gf green component value [0, 1]
- * @param bf blue component value [0, 1]
- * @param outHsl 3-element array which holds the resulting HSL components
- */
-fun rbgToHSL(
- rf: Float,
- gf: Float,
- bf: Float,
- outHsl: FloatArray
-) {
- val max = max(rf, max(gf, bf))
- val min = min(rf, min(gf, bf))
- val deltaMaxMin = max - min
-
- var h: Float
- val s: Float
- val l = (max + min) / 2f
-
- if (max == min) {
- // Monochromatic
- s = 0f
- h = s
- } else {
- if (max == rf) {
- h = ((gf - bf) / deltaMaxMin) % 6f
- } else if (max == gf) {
- h = ((bf - rf) / deltaMaxMin) + 2f
- } else {
- h = ((rf - gf) / deltaMaxMin) + 4f
- }
-
- s = deltaMaxMin / (1f - abs(2f * l - 1f))
- }
-
- h = (h * 60f) % 360f
- if (h < 0) {
- h += 360f
- }
-
- outHsl[0] = h.coerceIn(0f, 360f)
- outHsl[1] = s.coerceIn(0f, 1f)
- outHsl[2] = l.coerceIn(0f, 1f)
-}
-
-fun hslToColor(hsl: FloatArray): Color {
- val h = hsl[0]
- val s = hsl[1]
- val l = hsl[2]
-
- val c = (1f - abs(2 * l - 1f)) * s
- val m = l - 0.5f * c
- val x = c * (1f - abs((h / 60f % 2f) - 1f))
-
- val hueSegment = h.toInt() / 60
-
- var r = 0
- var g = 0
- var b = 0
-
- when (hueSegment) {
- 0 -> {
- r = (255 * (c + m)).roundToInt()
- g = (255 * (x + m)).roundToInt()
- b = (255 * m).roundToInt()
- }
-
- 1 -> {
- r = (255 * (x + m)).roundToInt()
- g = (255 * (c + m)).roundToInt()
- b = (255 * m).roundToInt()
- }
-
- 2 -> {
- r = (255 * m).roundToInt()
- g = (255 * (c + m)).roundToInt()
- b = (255 * (x + m)).roundToInt()
- }
-
- 3 -> {
- r = (255 * m).roundToInt()
- g = (255 * (x + m)).roundToInt()
- b = (255 * (c + m)).roundToInt()
- }
-
- 4 -> {
- r = (255 * (x + m)).roundToInt()
- g = (255 * m).roundToInt()
- b = (255 * (c + m)).roundToInt()
- }
-
- 5, 6 -> {
- r = (255 * (c + m)).roundToInt()
- g = (255 * m).roundToInt()
- b = (255 * (x + m)).roundToInt()
- }
- }
-
- r = r.coerceIn(0, 255)
- g = g.coerceIn(0, 255)
- b = b.coerceIn(0, 255)
-
- return Color(r, g, b)
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt
deleted file mode 100644
index 6a363909f..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Colors.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.lagradost.cloudstream4.compose
-
-import androidx.compose.material3.ButtonColors
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.ReadOnlyComposable
-
-object Colors {
- internal val blackButton
- @Composable @ReadOnlyComposable get() = ButtonColors(
- containerColor = MaterialTheme.colorScheme.surfaceVariant,
- contentColor = MaterialTheme.colorScheme.onBackground,
- disabledContainerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f),
- disabledContentColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.9f)
- )
-
- internal val whiteButton
- @Composable @ReadOnlyComposable get() = ButtonColors(
- containerColor = MaterialTheme.colorScheme.onBackground,
- contentColor = MaterialTheme.colorScheme.surfaceVariant,
- disabledContainerColor = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.9f),
- disabledContentColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f)
- )
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt
deleted file mode 100644
index 05948e457..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Dialog.kt
+++ /dev/null
@@ -1,480 +0,0 @@
-package com.lagradost.cloudstream4.compose
-
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.AlertDialogDefaults
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonColors
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.toMutableStateList
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory.component1
-import androidx.compose.ui.focus.FocusRequester.Companion.FocusRequesterFactory.component2
-import androidx.compose.ui.focus.focusProperties
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.graphics.painter.Painter
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.DialogProperties
-import com.lagradost.cloudstream4.generated.resources.Res
-import com.lagradost.cloudstream4.generated.resources.check
-import com.mihon.material.padding
-import org.jetbrains.compose.resources.painterResource
-
-@Composable
-fun ActionDialog(
- title: String,
- text: String,
- icon: Painter? = null,
-
- confirmText: String,
- dismissText: String,
-
- dismiss: () -> Unit,
- confirm: () -> Unit
-) {
- AlertDialog(
- containerColor = MaterialTheme.colorScheme.background,
- onDismissRequest = dismiss,
- title = {
- if (icon == null) {
- Text(text = title)
- } else {
- Row(verticalAlignment = Alignment.CenterVertically) {
- Icon(painter = icon, modifier = Modifier.size(36.dp), contentDescription = null)
- Spacer(modifier = Modifier.size(MaterialTheme.padding.medium))
- Text(text = title)
- }
- }
- },
- text = { Text(text = text) },
- confirmButton = {
- WhiteButton(text = confirmText, onClick = confirm)
- },
- dismissButton = {
- BlackButton(text = dismissText, onClick = dismiss)
- }
- )
-}
-
-@Composable
-fun WhiteButton(
- text: String,
- modifier: Modifier = Modifier,
- onClick: () -> Unit,
-) {
- BaseButton(
- text = text,
- onClick = onClick,
- buttonColors = Colors.whiteButton,
- modifier = modifier
- )
-}
-
-@Composable
-fun BlackButton(
- text: String,
- modifier: Modifier = Modifier,
- onClick: () -> Unit,
-) {
- BaseButton(
- text = text,
- onClick = onClick,
- buttonColors = Colors.blackButton,
- modifier = modifier
- )
-}
-
-@Composable
-fun BaseButton(
- text: String, onClick: () -> Unit, buttonColors: ButtonColors,
- modifier: Modifier = Modifier,
-) {
- var hasFocus by remember { mutableStateOf(false) }
- val canHaveFocus = LocalFocusOutlineDefault.current
- Button(
- onClick = onClick,
- colors = buttonColors,
- modifier = modifier.onFocusChanged { newFocus ->
- hasFocus = canHaveFocus && newFocus.hasFocus
- },
- border = if (hasFocus) BorderStroke(1.dp, MaterialTheme.colorScheme.onBackground) else null
- ) {
- AnimatedVisibility(hasFocus) {
- Icon(
- painter = painterResource(Res.drawable.check),
- modifier = Modifier.padding(end = 5.dp),
- contentDescription = null
- )
- }
- Text(text = text)
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun SingleSelectDialog(
- modifier: Modifier = Modifier,
- shape: Shape = AlertDialogDefaults.shape,
- containerColor: Color = MaterialTheme.colorScheme.background,
- iconContentColor: Color = AlertDialogDefaults.iconContentColor,
- tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
- icon: @Composable (() -> Unit)? = null,
- title: String? = null,
- properties: DialogProperties = DialogProperties(),
- /* visual */
- entries: List,
- /* Selected keys used in entries */
- selectedIndex: Int,
- confirmText: String? = null,
- dismissText: String? = null,
- dismiss: () -> Unit,
- confirm: (Int) -> Unit
-) {
- SingleSelectDialog(
- modifier = modifier,
- shape = shape,
- containerColor = containerColor,
- iconContentColor = iconContentColor,
- tonalElevation = tonalElevation,
- icon = icon,
- title = title,
- properties = properties,
- entries = entries.mapIndexed { index, string -> index to string }.associate { it },
- selectedKey = selectedIndex,
- confirmText = confirmText,
- dismissText = dismissText,
- dismiss = dismiss,
- confirm = confirm,
- )
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun SingleSelectDialog(
- modifier: Modifier = Modifier,
- shape: Shape = AlertDialogDefaults.shape,
- containerColor: Color = MaterialTheme.colorScheme.background,
- iconContentColor: Color = AlertDialogDefaults.iconContentColor,
- tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
- icon: @Composable (() -> Unit)? = null,
- title: String? = null,
- properties: DialogProperties = DialogProperties(),
- /* key - visual */
- entries: Map,
- /* Selected keys used in entries */
- selectedKey: T,
- confirmText: String? = null,
- dismissText: String? = null,
- dismiss: () -> Unit,
- confirm: (T) -> Unit,
- iconProvider: (@Composable (key: T, value: String) -> Unit)? = null
-) {
- var selected by remember { mutableStateOf(selectedKey) }
- val selectedPainter = painterResource(Res.drawable.check)
- val (dismissFocus, confirmFocus) = remember { FocusRequester.createRefs() }
-
- AlertDialog(
- properties = properties,
- tonalElevation = tonalElevation,
- modifier = modifier,
- icon = icon,
- shape = shape,
- iconContentColor = iconContentColor,
- containerColor = containerColor,
- onDismissRequest = dismiss,
- title = title?.let { { Text(text = it) } },
- text = {
- LazyColumn {
- entries.forEach { (key, value) ->
- item(key = key) {
- val isSelected = selected == key
- SingleSelectionItem(
- isSelected = isSelected,
- key = key,
- text = value,
- iconProvider = iconProvider,
- selectedPainter = selectedPainter,
- modifier = Modifier.focusProperties {
- start = dismissFocus
- end = confirmFocus
- }) {
- if (confirmText == null) {
- confirm(key)
- } else {
- selected = key
- }
- }
- }
- }
- }
- },
- confirmButton = {
- if (confirmText != null) {
- WhiteButton(
- text = confirmText,
- onClick = { confirm(selected) },
- modifier = Modifier.focusRequester(confirmFocus)
- )
- }
- },
- dismissButton = {
- if (dismissText != null) {
- BlackButton(
- text = dismissText,
- onClick = dismiss,
- modifier = Modifier.focusRequester(dismissFocus)
- )
- }
- }
- )
-}
-
-/*
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun SinglePairSelectDialog(
- modifier: Modifier = Modifier,
- shape: Shape = AlertDialogDefaults.shape,
- containerColor: Color = colors.background,
- iconContentColor: Color = AlertDialogDefaults.iconContentColor,
- tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
- icon: @Composable (() -> Unit)? = null,
- title: String? = null,
- properties: DialogProperties = DialogProperties(),
- /* key - visual */
- entries: Map, String>,
- /* Selected keys used in entries */
- selectedKey: T,
- confirmText: String? = null,
- dismissText: String? = null,
- dismiss: () -> Unit,
- confirm: (T) -> Unit,
- iconProvider: (@Composable (key: T, value: String) -> Unit)? = null
-) {
- val selected = remember { mutableStateOf(selectedKey) }
-
- AlertDialog(
- properties = properties,
- tonalElevation = tonalElevation,
- modifier = modifier,
- icon = icon,
- shape = shape,
- iconContentColor = iconContentColor,
- containerColor = containerColor,
- onDismissRequest = dismiss,
- title = title?.let { { Text(text = it) } },
- text = {
- LazyColumn {
- entries.forEach { (keyPair, value) ->
- item(key = keyPair) {
- val (a, b) = keyPair
- val isSelected = selected.value == a || selected.value == b
-
- val painter = if (a == b) {
- R.drawable.ic_baseline_check_24_listview
- } else if(selected.value == a) {
- R.drawable.ic_baseline_arrow_downward_24
- } else {
- R.drawable.ic_baseline_arrow_upward_24
- }
-
- val nextKey = if (selected.value != a) a else b
- SingleSelectionItem(
- isSelected = isSelected,
- key = nextKey,
- text = value,
- iconProvider = iconProvider,
- selectIcon = painter
- ) {
- if (confirmText == null) {
- confirm(nextKey)
- } else {
- selected.value = nextKey
- }
- }
- }
- }
- }
- },
- confirmButton = {
- if (confirmText != null) {
- Button(
- onClick = {
- confirm(selected.value)
- },
- colors = Colors.whiteButton
- ) { Text(text = confirmText) }
- }
- },
- dismissButton = {
- if (dismissText != null) {
- Button(
- onClick = dismiss, colors = Colors.blackButton
- ) { Text(text = dismissText) }
- }
- }
- )
-}*/
-
-@Composable
-fun SingleSelectionItem(
- isSelected: Boolean,
- key: T,
- text: String,
- modifier: Modifier = Modifier,
- iconProvider: (@Composable (key: T, value: String) -> Unit)? = null,
- selectedPainter: Painter,
- onClick: () -> Unit,
-) {
- Row(
- modifier = modifier
- .fillMaxWidth()
- .rounded()
- .clickable(
- onClick = onClick,
- ),
- verticalAlignment = Alignment.CenterVertically
- ) {
- if (isSelected) {
- Icon(
- painter = selectedPainter,
- contentDescription = null,
- modifier = Modifier.size(24.dp),
- tint = MaterialTheme.colorScheme.onBackground
- )
- iconProvider?.invoke(key, text)
- Text(
- text = text,
- style = MaterialTheme.typography.bodyLarge,
- color = MaterialTheme.colorScheme.onBackground,
- modifier = Modifier.padding(15.dp)
- )
- } else {
- Spacer(modifier = Modifier.size(24.dp))
- iconProvider?.invoke(key, text)
- Text(
- text = text,
- style = MaterialTheme.typography.bodyLarge,
- color = MaterialTheme.colorScheme.onSurfaceVariant,
- modifier = Modifier.padding(15.dp)
- )
- }
- }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun MultiSelectDialog(
- modifier: Modifier = Modifier,
- shape: Shape = AlertDialogDefaults.shape,
- containerColor: Color = MaterialTheme.colorScheme.background,
- iconContentColor: Color = AlertDialogDefaults.iconContentColor,
- tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
- icon: @Composable (() -> Unit)? = null,
- title: String? = null,
- properties: DialogProperties = DialogProperties(),
- /* key - visual */
- entries: Map,
- /* Selected keys used in entries */
- selectedKeys: Set,
- confirmText: String? = null,
- dismissText: String? = null,
- dismiss: () -> Unit,
- confirm: (Set) -> Unit,
- iconProvider: (@Composable (key: T, value: String) -> Unit)? = null
-) {
- val selected = remember {
- entries.keys
- .filter { selectedKeys.contains(it) }
- .toMutableStateList()
- }
-
- val selectedPainter = painterResource(Res.drawable.check)
- val (dismissFocus, confirmFocus) = remember { FocusRequester.createRefs() }
-
- AlertDialog(
- properties = properties,
- tonalElevation = tonalElevation,
- modifier = modifier,
- icon = icon,
- shape = shape,
- iconContentColor = iconContentColor,
- containerColor = containerColor,
- onDismissRequest = {
- if (confirmText == null) {
- confirm(selected.toSet())
- } else {
- dismiss()
- }
- },
- title = title?.let { { Text(text = it) } },
- text = {
- LazyColumn {
- entries.forEach { (key, value) ->
- item(key = key) {
- val isSelected = selected.contains(key)
- SingleSelectionItem(
- isSelected = isSelected,
- key = key,
- text = value,
- iconProvider = iconProvider,
- selectedPainter = selectedPainter,
- modifier = Modifier.focusProperties {
- start = dismissFocus
- end = confirmFocus
- }
- ) {
- if (isSelected) {
- selected.remove(key)
- } else {
- selected.add(key)
- }
- }
- }
- }
- }
- },
- confirmButton = {
- if (confirmText != null) {
- WhiteButton(
- onClick = {
- confirm(selected.toSet())
- },
- text = confirmText,
- modifier = Modifier.focusRequester(confirmFocus)
- )
- }
- },
- dismissButton = {
- if (dismissText != null) {
- BlackButton(
- onClick = dismiss,
- text = dismissText,
- modifier = Modifier.focusRequester(dismissFocus)
- )
- }
- }
- )
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Modifiers.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Modifiers.kt
deleted file mode 100644
index 140c89b76..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Modifiers.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.lagradost.cloudstream4.compose
-
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.ProvidableCompositionLocal
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-
-/*
-@Composable
-fun Modifier.ripple(
- interactionSource: MutableInteractionSource,
- bounded: Boolean = true,
-): Modifier = indication(
- interactionSource = interactionSource,
- indication = ripple(bounded = bounded, color = MaterialTheme.colorScheme.onBackground),
-)*/
-
-// no dimens.xml allowed in compose :/
-fun RoundedShape() = RoundedCornerShape(10.dp)
-
-@Composable
-fun Modifier.rounded(): Modifier =
- clip(RoundedShape())
-
-
-@Composable
-fun Modifier.circle(): Modifier =
- clip(CircleShape)
-
-@Composable
-fun Modifier.circleBorder(size: Dp): Modifier =
- this.size(size)
- .border(
- 2.dp,
- MaterialTheme.colorScheme.onBackground.copy(alpha = 0.2f),
- CircleShape
- )
- .circle()
-
-
-/**
- * If we should have outlines on items when focused by default, this should be false for desktop
- * as we do not use a dpad for that
- * */
-val LocalFocusOutlineDefault: ProvidableCompositionLocal =
- staticCompositionLocalOf { true }
-
-@Composable
-fun Modifier.focusOutline(
- enabled: Boolean = LocalFocusOutlineDefault.current,
- shape: Shape = RoundedShape()
-): Modifier {
- if (!enabled) return this
-
- var hasFocus by remember { mutableStateOf(false) }
-
- return this.onFocusChanged { newFocus ->
- hasFocus = newFocus.isFocused
- }.whiteOutline(hasFocus = hasFocus, shape = shape)
-}
-
-@Composable
-fun Modifier.whiteOutline(hasFocus: Boolean, shape: Shape = RoundedShape()): Modifier {
- return if (hasFocus) {
- this.border(
- width = 1.dp,
- color = MaterialTheme.colorScheme.onBackground,
- shape = shape
- )
- } else {
- this
- }
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Screen.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Screen.kt
deleted file mode 100644
index a99b623e8..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Screen.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.lagradost.cloudstream4.compose
-import androidx.compose.runtime.Composable
-
-// https://github.com/adrielcafe/voyager
-interface Screen {
- @Composable
- fun Content()
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Viewmodel.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Viewmodel.kt
deleted file mode 100644
index 1320fcf8e..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Viewmodel.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-package com.lagradost.cloudstream4.compose
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.compose.LocalLifecycleOwner
-import androidx.lifecycle.repeatOnLifecycle
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.currentCoroutineContext
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.FlowCollector
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.debounce
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.receiveAsFlow
-import kotlinx.coroutines.flow.update
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
-import kotlinx.coroutines.withContext
-import kotlin.time.Duration.Companion.milliseconds
-
-/**
- * Use with the DefaultStateContainer
- *
- * `X : ViewModel(), StateContainer by DefaultStateContainer(XState)`
- */
-interface StateContainer {
- val state: StateFlow
- fun updateState(reducer: State.() -> State)
-}
-
-/**
- * Use with the DefaultEffectContainer
- *
- * `X : ViewModel(), EffectContainer by DefaultEffectContainer()`
- */
-interface EffectContainer {
- val effect: Flow
- suspend fun postEffect(builder: () -> Effect)
-}
-
-/**
- * Use an interface on the viewmodel directly
- *
- * `X : ViewModel(), ActionHandler`
- */
-interface ActionHandler {
- fun onAction(action: Action)
-}
-
-/**
- * The default implementation of a state container to allow by delegate.
- *
- * All state should be updated with updateState + copy to allow atomic operations
- * */
-class DefaultStateContainer(initialState: State) : StateContainer {
- private val _state = MutableStateFlow(initialState)
- override val state: StateFlow = _state.asStateFlow()
-
- /** Do not do expensive operations in updateState, as this can get rerun twice or more if we have concurrent writers */
- override fun updateState(reducer: State.() -> State) {
- _state.update(reducer)
- }
-}
-
-/**
- * The default implementation of an effect container to allow by delegate
- * */
-class DefaultEffectContainer : EffectContainer {
- private val _effect = Channel() // To ensure events are sent
- override val effect: Flow = _effect.receiveAsFlow()
-
- override suspend fun postEffect(builder: () -> Effect) {
- _effect.send(builder())
- }
-}
-
-/**
- * Observe the viewmodel EffectContainer in the UI on the Main thread for e.g. toasts
- * */
-@Composable
-fun ObserveEffect(flow: Flow, onEvent: (T) -> Unit) {
- val lifecycleOwner = LocalLifecycleOwner.current
- LaunchedEffect(flow, lifecycleOwner.lifecycle) {
- lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- withContext(Dispatchers.Main.immediate) { // To ensure events are sent
- flow.collect(onEvent)
- }
- }
- }
-}
-
-/** Single active job, replacing the old job by cancellation if launched again */
-data class SingleActiveQuery(
- val dispatcher: CoroutineDispatcher,
- private var job: Job? = null,
- private val mutex: Mutex = Mutex(),
-) {
- suspend fun cancel() {
- mutex.withLock {
- job?.cancel()
- job?.join()
- job = null
- }
- }
-
- suspend fun launch(block: suspend CoroutineScope.() -> Unit) {
- val currentScope = CoroutineScope(currentCoroutineContext())
- val obj: suspend CoroutineScope.() -> Unit = {
- try {
- withContext(dispatcher) {
- block()
- }
- } catch (_: Throwable) {
- //logError(t)
- }
- }
- mutex.withLock {
- job?.cancel()
- job?.join()
- job = currentScope.launch(block = obj)
- }
- }
-}
-
-/** Debounce Query to handle e.g. user search without spamming the endpoint */
-data class DebounceQuery(
- private val pipe: MutableSharedFlow = MutableSharedFlow(
- extraBufferCapacity = 64,
- replay = 64
- )
-) {
- @OptIn(FlowPreview::class)
- suspend fun launch(collector: FlowCollector) {
- pipe.debounce(200L.milliseconds).distinctUntilChanged()
- .flowOn(Dispatchers.Default)
- .collect(collector)
- }
-
- suspend fun emit(query: String) {
- this.pipe.emit(query)
- }
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/CircularColorPicker.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/CircularColorPicker.kt
deleted file mode 100644
index bdae7c0aa..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/CircularColorPicker.kt
+++ /dev/null
@@ -1,212 +0,0 @@
-package com.lagradost.cloudstream4.compose.colorpicker
-
-import androidx.compose.foundation.gestures.*
-import androidx.compose.foundation.interaction.DragInteraction
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.size
-import androidx.compose.runtime.*
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithCache
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.center
-import androidx.compose.ui.unit.toOffset
-import kotlinx.coroutines.launch
-import kotlin.math.*
-
-/**
- * Circular color picker that allows the user to select a hue by dragging a thumb around the circle.
- * The color is represented in HSV color space with a fixed value.
- *
- * @param color The current color
- * @param onColorChange Callback that is called when the color changes
- * @param modifier The modifier to be applied to the color picker
- * @param interactionSource The interaction source for the color picker
- * @param onColorChangeFinished Callback that is called when the user finishes changing the color
- * @param thumb Composable that is used to draw the thumb
- *
- * @see RingColorPicker
- * @see SquareColorPicker
- */
-@Composable
-fun CircularColorPicker(
- color: () -> HsvColor,
- onColorChange: (HsvColor) -> Unit,
- modifier: Modifier = Modifier,
- interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
- onColorChangeFinished: () -> Unit = {},
- thumb: @Composable () -> Unit = {
- ColorPickerDefaults.Thumb(color().toColor(), interactionSource)
- }
-) {
- CircularColorPicker(
- hue = { color().hue },
- saturation = { color().saturation },
- value = { color().value },
- onColorChange = { h, s -> onColorChange(color().copy(hue = h, saturation = s)) },
- modifier = modifier,
- interactionSource = interactionSource,
- onColorChangeFinished = onColorChangeFinished,
- thumb = thumb
- )
-}
-
-/**
- * Circular color picker that allows the user to select a hue by dragging a thumb around the circle.
- * The color is represented in HSV color space with a fixed value.
- *
- * @param hue The hue of the color
- * @param saturation The saturation of the color
- * @param value The value of the color
- * @param onColorChange Callback that is called when the color changes
- * @param modifier The modifier to be applied to the color picker
- * @param interactionSource The interaction source for the color picker
- * @param onColorChangeFinished Callback that is called when the user finishes changing the color
- * @param thumb Composable that is used to draw the thumb
- *
- * @see RingColorPicker
- * @see SquareColorPicker
- */
-@Composable
-fun CircularColorPicker(
- hue: () -> Float,
- saturation: () -> Float,
- modifier: Modifier = Modifier,
- value: () -> Float = { 1f },
- onColorChange: (hue: Float, saturation: Float) -> Unit,
- interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
- onColorChangeFinished: () -> Unit = {},
- thumb: @Composable () -> Unit = {
- ColorPickerDefaults.Thumb(Color.hsv(hue(), saturation(), value()), interactionSource)
- }
-) {
- val scope = rememberCoroutineScope()
- var radius by remember { mutableFloatStateOf(0f) }
-
- val currentOnColorChange by rememberUpdatedState(onColorChange)
- val currentOnColorChangeFinished by rememberUpdatedState(onColorChangeFinished)
-
- Box(
- modifier = modifier
- .size(ColorPickerDefaults.ComponentSize)
- .onSizeChanged {
- radius = it.width / 2f
- }
- .pointerInput(Unit) {
- awaitEachGesture {
- val down = awaitFirstDown()
- val downPosition = down.position
- val center = size.center.toOffset()
-
- // Check if initial touch is within the circle
- if ((downPosition - center).getDistanceSquared() > radius * radius) return@awaitEachGesture
-
- // Handle initial tap
- updateColorFromPosition(downPosition, center, radius, currentOnColorChange)
-
- // Start drag interaction
- val interaction = DragInteraction.Start()
- scope.launch {
- interactionSource.emit(interaction)
- }
-
- var change = awaitTouchSlopOrCancellation(down.id) { change, _ ->
- change.consume()
- val adjustedPosition = clampPositionToRadius(change.position, center, radius)
- updateColorFromPosition(adjustedPosition, center, radius, currentOnColorChange)
- }
-
- // Continue dragging
- while (change != null && change.pressed) {
- change.consume()
- val adjustedPosition = clampPositionToRadius(change.position, center, radius)
- updateColorFromPosition(adjustedPosition, center, radius, currentOnColorChange)
- change = awaitDragOrCancellation(change.id)
- }
-
- scope.launch {
- interactionSource.emit(DragInteraction.Stop(interaction))
- }
-
- currentOnColorChangeFinished()
- }
- }
- .drawWithCache {
- val v = value()
- val hueBrush = Brush.sweepGradient(
- colors = List(7) { i ->
- Color.hsv(
- hue = i * 60f,
- saturation = 1f,
- value = v
- )
- }
- )
- val saturationBrush = Brush.radialGradient(
- listOf(Color.hsv(0f, 0f, v), Color.Transparent)
- )
-
- onDrawBehind {
- drawCircle(hueBrush)
- drawCircle(saturationBrush)
- }
- }
- ) {
- Box(
- modifier = Modifier.offset {
- val angle = hue() * DEG_TO_RAD
- val distance = saturation() * radius
-
- IntOffset(
- x = (radius + distance * cos(angle)).roundToInt(),
- y = (radius + distance * sin(angle)).roundToInt()
- )
- }
- ) {
- thumb()
- }
- }
-}
-
-/**
- * Get the color for a given position in the circular color picker.
- *
- * @return A pair of hue and saturation values, or null if the position is outside the circle.
- */
-private inline fun updateColorFromPosition(
- position: Offset,
- center: Offset,
- radius: Float,
- onResult: (hue: Float, saturation: Float) -> Unit
-) {
- val offset = position - center
-
- val degrees = atan2(offset.y, offset.x) * RAD_TO_DEG
- val centerAngle = (degrees + 360) % 360
- val distance = offset.getDistance()
- val saturation = (distance / radius).coerceIn(0f, 1f)
-
- onResult(centerAngle, saturation)
-}
-
-/**
- * Clamp the position to the radius of the color picker circle.
- *
- * @return The clamped position that lies within the circle of the given radius.
- */
-private fun clampPositionToRadius(position: Offset, center: Offset, radius: Float): Offset {
- val offset = position - center
-
- // If the position is already within the radius, return it as is
- if (offset.getDistanceSquared() <= radius * radius) return position
-
- // Otherwise, clamp the position to the edge of the circle
- val scale = radius / offset.getDistance()
- return center + (offset * scale)
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/ColorPickerDefaults.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/ColorPickerDefaults.kt
deleted file mode 100644
index 9cc25a0aa..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/ColorPickerDefaults.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.lagradost.cloudstream4.compose.colorpicker
-
-import androidx.compose.animation.core.animateDpAsState
-import androidx.compose.foundation.Canvas
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.interaction.collectIsDraggedAsState
-import androidx.compose.foundation.interaction.collectIsPressedAsState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.drawscope.Stroke
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import kotlin.math.PI
-
-internal const val DEG_TO_RAD = (PI / 180).toFloat()
-internal const val RAD_TO_DEG = (180 / PI).toFloat()
-
-internal val ThumbRadiusPressed = 14.dp
-internal val ThumbRadius = 10.dp
-
-object ColorPickerDefaults {
- /**
- * The default size applied to color pickers. Note that you can override it by applying
- * Modifier.size directly on a picker component.
- */
- val ComponentSize: Dp = 128.dp
-
- /**
- * Default implementation of the thumb component for the color pickers.
- *
- * @param color The color of the thumb
- * @param interactionSource The interaction source for the thumb
- * @param modifier The modifier for the thumb
- */
- @Composable
- fun Thumb(color: Color, interactionSource: MutableInteractionSource, modifier: Modifier = Modifier) {
- val isPressed by interactionSource.collectIsPressedAsState()
- val isDragged by interactionSource.collectIsDraggedAsState()
- val radius by animateDpAsState(
- targetValue = if (isPressed || isDragged) ThumbRadiusPressed else ThumbRadius
- )
-
- Canvas(modifier = modifier) {
- drawCircle(color, radius = radius.toPx())
-
- // Draw the border
- drawCircle(
- color = color.contrastingColor,
- radius = radius.toPx(),
- alpha = 0.5f,
- style = Stroke(1.dp.toPx())
- )
- }
- }
-}
-
-internal val Color.contrastingColor: Color
- get() = if (this.isDark()) Color.White else Color.Black
-
-internal fun Color.isDark(): Boolean {
- return 0.2126 * this.red + 0.7152 * this.green + 0.0722 * this.blue < .5f
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/DoubleColorPicker.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/DoubleColorPicker.kt
deleted file mode 100644
index e5b07d7b1..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/DoubleColorPicker.kt
+++ /dev/null
@@ -1,208 +0,0 @@
-package com.lagradost.cloudstream4.compose.colorpicker
-
-import androidx.compose.foundation.Canvas
-import androidx.compose.foundation.gestures.awaitDragOrCancellation
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.gestures.awaitTouchSlopOrCancellation
-import androidx.compose.foundation.interaction.DragInteraction
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.runtime.*
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.*
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.dp
-import kotlinx.coroutines.launch
-import kotlin.math.atan2
-import kotlin.math.cos
-import kotlin.math.roundToInt
-import kotlin.math.sin
-import kotlin.math.sqrt
-
-@Composable
-fun DoubleColorPicker(
- color: () -> HsvColor,
- onColorChange: (color: HsvColor) -> Unit,
- modifier: Modifier = Modifier,
- ringStrokeWidth: Dp = 16.dp,
- innerPadding: Dp = 50.dp,
- ringInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() },
- ringThumb: @Composable () -> Unit = {
- ColorPickerDefaults.Thumb(Color.hsv(color().hue, 1f, 1f), ringInteractionSource)
- },
- innerInteractionSource: MutableInteractionSource = remember { MutableInteractionSource() },
- innerThumb: @Composable () -> Unit = {
- ColorPickerDefaults.Thumb(color().toColor(), innerInteractionSource)
- },
-) {
- Box(modifier) {
- RingColorPicker(
- interactionSource = ringInteractionSource,
- thumb = ringThumb,
- ringStrokeWidth = ringStrokeWidth,
- color = color,
- onColorChange = onColorChange,
- modifier = Modifier.fillMaxSize()
- )
- Box(Modifier.fillMaxSize().padding(innerPadding)) {
- CircularSquareColorPicker(
- interactionSource = innerInteractionSource,
- thumb = innerThumb,
- color = color,
- onColorChange = onColorChange,
- modifier = Modifier.fillMaxSize()
- )
- }
- }
-}
-
-/**
- * The standard circle color picker that allows the user to select a color by dragging a thumb around the color space.
- *
- * The color is represented in HSV color space with a fixed hue. The saturation and value can be controlled by
- * dragging the thumb.
- *
- * @param color The current color
- * @param onColorChange Callback that is called when the color changes
- * @param modifier The modifier to be applied to the color picker
- * @param interactionSource The interaction source for the color picker
- * @param thumb Composable that is used to draw the thumb
- * @param shape The shape of the color picker, note that the corner radius should be kept small,
- * to prevent the thumb from visually appearing outside the color picker
- * @param onColorChangeFinished Callback that is called when the user finishes changing the color
- *
- * @see CircularColorPicker
- * @see RingColorPicker
- */
-@Composable
-fun CircularSquareColorPicker(
- color: () -> HsvColor,
- onColorChange: (color: HsvColor) -> Unit,
- modifier: Modifier = Modifier,
- interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
- thumb: @Composable () -> Unit = {
- ColorPickerDefaults.Thumb(color().toColor(), interactionSource)
- },
- onColorChangeFinished: () -> Unit = {}
-) {
- val scope = rememberCoroutineScope()
- var size by remember { mutableStateOf(IntSize.Zero) }
-
- val currentColor by rememberUpdatedState(color)
- val currentOnColorChange by rememberUpdatedState(onColorChange)
- val currentOnColorChangeFinished by rememberUpdatedState(onColorChangeFinished)
-
- Box {
- Canvas(
- modifier = modifier
- .size(ColorPickerDefaults.ComponentSize)
- .onSizeChanged { size = it }
- .clip(CircleShape)
- .pointerInput(Unit) {
- awaitEachGesture {
- val down = awaitFirstDown()
-
- hsvColorForPosition(down.position, size).let { (s, v) ->
- currentOnColorChange(currentColor().copy(saturation = s, value = v))
- }
-
- // Start drag interaction
- val interaction = DragInteraction.Start()
- scope.launch {
- interactionSource.emit(interaction)
- }
-
- var change = awaitTouchSlopOrCancellation(down.id) { change, _ ->
- change.consume()
- hsvColorForPosition(change.position, size).let { (s, v) ->
- currentOnColorChange(currentColor().copy(saturation = s, value = v))
- }
- }
-
- // Continue dragging
- while (change != null && change.pressed) {
- change.consume()
- hsvColorForPosition(change.position, size).let { (s, v) ->
- currentOnColorChange(currentColor().copy(saturation = s, value = v))
- }
- change = awaitDragOrCancellation(change.id)
- }
-
- scope.launch {
- interactionSource.emit(DragInteraction.Stop(interaction))
- }
-
- currentOnColorChangeFinished()
- }
- }
- .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
- ) {
- val saturationBrush = Brush.verticalGradient(listOf(Color.Transparent, Color.Black))
- val hueBrush = Brush.horizontalGradient(
- listOf(
- Color.Transparent,
- Color.hsv(currentColor().hue, 1f, 1f)
- )
- )
-
- drawRect(Color.White)
- drawRect(hueBrush)
- drawRect(saturationBrush)
- }
-
- Box(
- modifier = Modifier.offset {
- val x = -1.0 + currentColor().saturation * 2.0
- val y = 1.0 - currentColor().value * 2.0
-
- // https://squircular.blogspot.com/2015/09/mapping-circle-to-square.html
- val u = x * sqrt(1 - y * y * 0.5)
- val v = y * sqrt(1 - x * x * 0.5)
-
- IntOffset(
- x = (u * size.width * 0.5 + size.width * 0.5).roundToInt(),//(currentColor().saturation * size.width).roundToInt(),
- y = (v * size.height * 0.5 + size.height * 0.5).roundToInt(), //(size.height - currentColor().value * size.height).roundToInt()
- )
- }
- ) {
- thumb()
- }
- }
-}
-
-private fun hsvColorForPosition(position: Offset, size: IntSize): Pair {
- val clampedX = position.x / size.width
- val clampedY = position.y / size.height
-
- val x = -1.0 + clampedX * 2.0
- val y = 1.0 - clampedY * 2.0
-
- // coerce here to prevent drag from feeling like a square
- val radius = sqrt(x * x + y * y).coerceIn(0.0, 1.0)
- val angle = atan2(y, x)
-
- val u = cos(angle) * radius
- val v = sin(angle) * radius
-
- val u2 = u * u
- val v2 = v * v
- val sqrt22 = 2.0 * sqrt(2.0)
-
- // https://squircular.blogspot.com/2015/09/mapping-circle-to-square.html
- val xOut = 0.5 * sqrt(2 + sqrt22 * u + u2 - v2) - 0.5 * sqrt(2 - sqrt22 * u + u2 - v2)
- val yOut = 0.5 * sqrt(2 + sqrt22 * v - u2 + v2) - 0.5 * sqrt(2 - sqrt22 * v - u2 + v2)
-
- return (xOut * 0.5 + 0.5).toFloat() to (yOut * 0.5 + 0.5).toFloat()
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/HsvColor.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/HsvColor.kt
deleted file mode 100644
index 0fcbbda99..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/HsvColor.kt
+++ /dev/null
@@ -1,170 +0,0 @@
-package com.lagradost.cloudstream4.compose.colorpicker
-
-import androidx.compose.runtime.Immutable
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.saveable.Saver
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.colorspace.ColorSpaces
-import androidx.compose.ui.graphics.toArgb
-import kotlin.jvm.JvmInline
-import kotlin.math.max
-
-/**
- * Represents a color in the HSV (Hue, Saturation, Value) color space.
- *
- * This class provides properties to access the hue, saturation, and value of the color,
- * as well as methods to convert to and from RGB color space.
- */
-@Immutable
-@JvmInline
-value class HsvColor private constructor(val packedValue: ULong) {
- /**
- * The hue of the color, in degrees (0-360).
- */
- @Stable
- val hue: Float
- get() = (packedValue shr 40 and 0xFFFFu).toFloat() / 100f
-
- /**
- * The saturation of the color (0-1).
- */
- @Stable
- val saturation: Float
- get() = (packedValue shr 20 and 0xFFFFFu).toFloat() / 1000000f
-
- /**
- * The value (brightness) of the color (0-1).
- */
- @Stable
- val value: Float
- get() = (packedValue and 0xFFFFFu).toFloat() / 1000000f
-
- /**
- * The red component of the color (0-1).
- */
- @Stable
- val red: Float
- get() = hsvToRgbComponent(5, hue, saturation, value)
-
- /**
- * The green component of the color (0-1).
- */
- @Stable
- val green: Float
- get() = hsvToRgbComponent(3, hue, saturation, value)
-
- /**
- * The blue component of the color (0-1).
- */
- @Stable
- val blue: Float
- get() = hsvToRgbComponent(1, hue, saturation, value)
-
- /**
- * Creates an HsvColor from the given hue, saturation, and value.
- */
- constructor(hue: Float, saturation: Float, value: Float) : this(
- ((hue * 100).toULong() and 0xFFFFu shl 40) or
- ((saturation * 1000000).toULong() and 0xFFFFFu shl 20) or
- ((value * 1000000).toULong() and 0xFFFFFu)
- )
-
- /**
- * Converts the HSV color to a Color object.
- *
- * **Note**: HSV to RGB conversion is a lossy process, so the resulting color may not be exactly the same as the original HSV color.
- * When red, green and blue are equal, hue will be 0 and saturation will be 0.
- */
- fun toColor(): Color = Color.hsv(hue, saturation, value)
-
- @Stable
- operator fun component1(): Float = hue
-
- @Stable
- operator fun component2(): Float = saturation
-
- @Stable
- operator fun component3(): Float = value
-
- /**
- * Copies the existing color, changing only the provided values.
- */
- @Stable
- fun copy(
- hue: Float = this.hue,
- saturation: Float = this.saturation,
- value: Float = this.value
- ): HsvColor {
- return HsvColor(hue, saturation, value)
- }
-
- /**
- * Returns a string representation of the color in HSV format.
- */
- @Stable
- override fun toString(): String {
- return "HsvColor(hue=$hue, saturation=$saturation, value=$value)"
- }
-
- companion object {
- val Saver: Saver = Saver(
- save = { it.packedValue.toLong() },
- restore = { HsvColor(it.toULong()) }
- )
-
- private fun hsvToRgbComponent(n: Int, h: Float, s: Float, v: Float): Float {
- val k = (n.toFloat() + h / 60f) % 6f
- return v - (v * s * max(0f, minOf(k, 4 - k, 1f)))
- }
- }
-}
-
-/**
- * Creates a new [HsvColor] instance from a [Color].
- *
- * @param color The Color to create an HsvColor from.
- * @return A non-null instance of [HsvColor]
- */
-@Stable
-fun HsvColor(color: Color): HsvColor = HsvColor(color.toArgb())
-
-/**
- * Creates a new [Color] instance from an ARGB color int.
- *
- * @param color The ARGB color int to create a Color from.
- * @return A non-null instance of [HsvColor]
- */
-@Stable
-fun HsvColor(color: Int): HsvColor = HsvColor(color.toLong())
-
-/**
- * Creates a new [Color] instance from an ARGB color long.
- * The resulting color is in the [sRGB][ColorSpaces.Srgb]
- * color space.
- *
- * @param color The ARGB color long to create a Color from.
- * @return A non-null instance of [HsvColor]
- */
-@Stable
-fun HsvColor(color: Long): HsvColor {
- val r = ((color shr 16) and 0xFF).toFloat() / 255f
- val g = ((color shr 8) and 0xFF).toFloat() / 255f
- val b = (color and 0xFF).toFloat() / 255f
-
- val max = maxOf(r, g, b)
- val min = minOf(r, g, b)
- val delta = max - min
-
- val hue = when {
- delta == 0f -> 0f
- max == r -> ((g - b) / delta) % 6
- max == g -> ((b - r) / delta) + 2
- max == b -> ((r - g) / delta) + 4
- else -> 0f
- } * 60f
-
- val positiveHue = if (hue < 0f) hue + 360f else hue
- val saturation = if (max == 0f) 0f else 1 - min / max
-
- return HsvColor(positiveHue, saturation, max)
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/LICENSE b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/LICENSE
deleted file mode 100644
index 33e326d05..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2025 zt64
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/README.md b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/README.md
deleted file mode 100644
index a57df0ca2..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Taken from https://github.com/zt64/compose-pipette/tree/main
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/RingColorPicker.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/RingColorPicker.kt
deleted file mode 100644
index 602bd3ace..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/RingColorPicker.kt
+++ /dev/null
@@ -1,200 +0,0 @@
-package com.lagradost.cloudstream4.compose.colorpicker
-
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.gestures.*
-import androidx.compose.foundation.interaction.DragInteraction
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.progressSemantics
-import androidx.compose.runtime.*
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithCache
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.drawscope.Stroke
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.unit.*
-import kotlinx.coroutines.launch
-import kotlin.math.*
-
-/**
- * A ring color picker that allows the user to select a hue by rotating a handle around the ring. The ring is
- * a continuous gradient of colors from red to red.
- *
- * To be able to also control the saturation, use the [CircularColorPicker] composable.
- *
- * @param color The current color
- * @param onColorChange Callback that is called when the color changes
- * @param modifier The modifier to be applied to the color picker
- * @param interactionSource The interaction source for the color picker
- * @param ringStrokeWidth The width of the ring
- * @param thumb The composable that is used to draw the thumb
- * @param onColorChangeFinished Callback that is called when the user finishes changing the color
- *
- * @see CircularColorPicker
- * @see SquareColorPicker
- */
-@OptIn(ExperimentalFoundationApi::class)
-@Composable
-fun RingColorPicker(
- color: () -> HsvColor,
- onColorChange: (HsvColor) -> Unit,
- modifier: Modifier = Modifier,
- interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
- ringStrokeWidth: Dp = 16.dp,
- thumb: @Composable () -> Unit = {
- ColorPickerDefaults.Thumb(Color.hsv(color().hue, 1f, 1f), interactionSource)
- },
- onColorChangeFinished: () -> Unit = {}
-) {
- RingColorPicker(
- hue = { color().hue },
- onHueChange = { hue -> onColorChange(color().copy(hue = hue)) },
- modifier = modifier,
- interactionSource = interactionSource,
- ringStrokeWidth = ringStrokeWidth,
- thumb = thumb,
- onColorChangeFinished = onColorChangeFinished
- )
-}
-
-/**
- * A ring color picker that allows the user to select a hue by rotating a handle around the ring. The ring is
- * a continuous gradient of colors from red to red.
- *
- * To be able to also control the saturation, use the [CircularColorPicker] composable.
- *
- * @param hue The hue of the color
- * @param saturation The saturation of the color
- * @param value The value of the color
- * @param onHueChange Callback that is called when the hue changes
- * @param modifier The modifier to be applied to the color picker
- * @param interactionSource The interaction source for the color picker
- * @param ringStrokeWidth The width of the ring
- * @param thumb The composable that is used to draw the thumb
- * @param onColorChangeFinished Callback that is called when the user finishes changing the color
- *
- * @see CircularColorPicker
- * @see SquareColorPicker
- */
-@Composable
-fun RingColorPicker(
- hue: () -> Float,
- modifier: Modifier = Modifier,
- saturation: () -> Float = { 1f },
- value: () -> Float = { 1f },
- onHueChange: (Float) -> Unit,
- interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
- ringStrokeWidth: Dp = 16.dp,
- thumb: @Composable () -> Unit = {
- ColorPickerDefaults.Thumb(Color.hsv(hue(), saturation(), value()), interactionSource)
- },
- onColorChangeFinished: () -> Unit = {}
-) {
- val scope = rememberCoroutineScope()
- var radius by remember { mutableFloatStateOf(0f) }
- var center by remember { mutableStateOf(Offset.Zero) }
- val strokeWidth = with(LocalDensity.current) { ringStrokeWidth.toPx() }
-
- val currentOnHueChange by rememberUpdatedState(onHueChange)
- val currentOnColorChangeFinished by rememberUpdatedState(onColorChangeFinished)
-
- Box(
- modifier = modifier
- .size(ColorPickerDefaults.ComponentSize)
- .onSizeChanged {
- radius = (it.width - strokeWidth) / 2f
- center = Offset(it.width / 2f, it.height / 2f)
- }
- .pointerInput(Unit) {
- awaitEachGesture {
- val down = awaitFirstDown()
- val downPosition = down.position
- val center = size.center.toOffset()
-
- val distanceSquared = (downPosition - center).getDistanceSquared()
- val halfStroke = strokeWidth / 2f
- val innerRadiusSquared = (radius - halfStroke) * (radius - halfStroke)
- val outerRadiusSquared = (radius + halfStroke) * (radius + halfStroke)
-
- if (distanceSquared !in innerRadiusSquared..outerRadiusSquared) return@awaitEachGesture
-
- // Handle initial tap
- currentOnHueChange(hueForPosition(downPosition, center))
-
- val interaction = DragInteraction.Start()
- scope.launch {
- interactionSource.emit(interaction)
- }
-
- var change = awaitTouchSlopOrCancellation(down.id) { change, _ ->
- change.consume()
- currentOnHueChange(hueForPosition(change.position, center))
- }
-
- while (change != null && change.pressed) {
- change.consume()
- currentOnHueChange(hueForPosition(change.position, center))
- change = awaitDragOrCancellation(change.id)
- }
-
- scope.launch {
- interactionSource.emit(DragInteraction.Stop(interaction))
- }
-
- currentOnColorChangeFinished()
- }
- }
- .drawWithCache {
- val brush = Brush.sweepGradient(
- List(7) {
- Color.hsv(
- hue = (it * 60).toFloat(),
- saturation = saturation(),
- value = value()
- )
- }
- )
-
- onDrawBehind {
- drawCircle(
- brush = brush,
- radius = size.minDimension / 2 - strokeWidth / 2f,
- style = Stroke(strokeWidth)
- )
- }
- }
- .progressSemantics(
- value = hue(),
- valueRange = 0f..360f,
- steps = 360
- )
- ) {
- Box(
- modifier = Modifier.offset {
- val rad = hue() * DEG_TO_RAD
- val x = center.x + radius * cos(rad)
- val y = center.y + radius * sin(rad)
-
- IntOffset(x.roundToInt(), y.roundToInt())
- }
- ) {
- thumb()
- }
- }
-}
-
-private fun hueForPosition(position: Offset, center: Offset): Float {
- val (dx, dy) = position - center
- val theta = atan2(dy, dx)
- var angle = theta * RAD_TO_DEG
-
- if (angle < 0) angle += 360f
-
- return angle
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/SquareColorPicker.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/SquareColorPicker.kt
deleted file mode 100644
index f409d4201..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/colorpicker/SquareColorPicker.kt
+++ /dev/null
@@ -1,141 +0,0 @@
-package com.lagradost.cloudstream4.compose.colorpicker
-
-import androidx.compose.foundation.Canvas
-import androidx.compose.foundation.gestures.awaitDragOrCancellation
-import androidx.compose.foundation.gestures.awaitEachGesture
-import androidx.compose.foundation.gestures.awaitFirstDown
-import androidx.compose.foundation.gestures.awaitTouchSlopOrCancellation
-import androidx.compose.foundation.interaction.DragInteraction
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.size
-import androidx.compose.runtime.*
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.*
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.layout.onSizeChanged
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.IntSize
-import kotlinx.coroutines.launch
-import kotlin.math.roundToInt
-
-/**
- * The standard square color picker that allows the user to select a color by dragging a thumb around the color space.
- *
- * The color is represented in HSV color space with a fixed hue. The saturation and value can be controlled by
- * dragging the thumb.
- *
- * @param color The current color
- * @param onColorChange Callback that is called when the color changes
- * @param modifier The modifier to be applied to the color picker
- * @param interactionSource The interaction source for the color picker
- * @param thumb Composable that is used to draw the thumb
- * @param shape The shape of the color picker, note that the corner radius should be kept small,
- * to prevent the thumb from visually appearing outside the color picker
- * @param onColorChangeFinished Callback that is called when the user finishes changing the color
- *
- * @see CircularColorPicker
- * @see RingColorPicker
- */
-@Composable
-fun SquareColorPicker(
- color: () -> HsvColor,
- onColorChange: (color: HsvColor) -> Unit,
- modifier: Modifier = Modifier,
- interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
- thumb: @Composable () -> Unit = {
- ColorPickerDefaults.Thumb(color().toColor(), interactionSource)
- },
- shape: Shape = RectangleShape,
- onColorChangeFinished: () -> Unit = {}
-) {
- val scope = rememberCoroutineScope()
- var size by remember { mutableStateOf(IntSize.Zero) }
-
- val currentColor by rememberUpdatedState(color)
- val currentOnColorChange by rememberUpdatedState(onColorChange)
- val currentOnColorChangeFinished by rememberUpdatedState(onColorChangeFinished)
-
- Box {
- Canvas(
- modifier = modifier
- .size(ColorPickerDefaults.ComponentSize)
- .onSizeChanged { size = it }
- .pointerInput(Unit) {
- awaitEachGesture {
- val down = awaitFirstDown()
-
- hsvColorForPosition(down.position, size).let { (s, v) ->
- currentOnColorChange(currentColor().copy(saturation = s, value = v))
- }
-
- // Start drag interaction
- val interaction = DragInteraction.Start()
- scope.launch {
- interactionSource.emit(interaction)
- }
-
- var change = awaitTouchSlopOrCancellation(down.id) { change, _ ->
- change.consume()
- hsvColorForPosition(change.position, size).let { (s, v) ->
- currentOnColorChange(currentColor().copy(saturation = s, value = v))
- }
- }
-
- // Continue dragging
- while (change != null && change.pressed) {
- change.consume()
- hsvColorForPosition(change.position, size).let { (s, v) ->
- currentOnColorChange(currentColor().copy(saturation = s, value = v))
- }
- change = awaitDragOrCancellation(change.id)
- }
-
- scope.launch {
- interactionSource.emit(DragInteraction.Stop(interaction))
- }
-
- currentOnColorChangeFinished()
- }
- }
- .clip(shape)
- .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
- ) {
- val saturationBrush = Brush.verticalGradient(listOf(Color.Transparent, Color.Black))
- val hueBrush = Brush.horizontalGradient(
- listOf(
- Color.Transparent,
- Color.hsv(currentColor().hue, 1f, 1f)
- )
- )
-
- drawRect(Color.White)
- drawRect(hueBrush)
- drawRect(saturationBrush)
- }
-
- Box(
- modifier = Modifier.offset {
- IntOffset(
- x = (currentColor().saturation * size.width).roundToInt(),
- y = (size.height - currentColor().value * size.height).roundToInt()
- )
- }
- ) {
- thumb()
- }
- }
-}
-
-private fun hsvColorForPosition(position: Offset, size: IntSize): Pair {
- val clampedX = position.x.coerceIn(0f, size.width.toFloat())
- val clampedY = position.y.coerceIn(0f, size.height.toFloat())
-
- val saturation = clampedX / size.width
- val value = 1f - (clampedY / size.height)
-
- return Pair(saturation, value)
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.kt
deleted file mode 100644
index 72da2f789..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.lagradost.cloudstream4.theme
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.ui.graphics.Color
-
-internal expect object DeviceTheme {
- @Composable
- @ReadOnlyComposable
- fun resolveDynamicTheme(): CloudStreamColorScheme
- @Composable
- @ReadOnlyComposable
- fun resolveDynamicPrimaryColor(): Color
- @Composable
- @ReadOnlyComposable
- fun resolveDynamicSecondaryColor(): Color
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Font.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Font.kt
deleted file mode 100644
index c81519e6d..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Font.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.lagradost.cloudstream4.theme
-
-import androidx.compose.material3.Typography
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.em
-import com.lagradost.cloudstream4.generated.resources.Res
-import com.lagradost.cloudstream4.generated.resources.productsans_black
-import com.lagradost.cloudstream4.generated.resources.productsans_blackitalic
-import com.lagradost.cloudstream4.generated.resources.productsans_bold
-import com.lagradost.cloudstream4.generated.resources.productsans_bolditalic
-import com.lagradost.cloudstream4.generated.resources.productsans_italic
-import com.lagradost.cloudstream4.generated.resources.productsans_light
-import com.lagradost.cloudstream4.generated.resources.productsans_lightitalic
-import com.lagradost.cloudstream4.generated.resources.productsans_medium
-import com.lagradost.cloudstream4.generated.resources.productsans_mediumitalic
-import com.lagradost.cloudstream4.generated.resources.productsans_regular
-import com.lagradost.cloudstream4.generated.resources.productsans_thin
-import com.lagradost.cloudstream4.generated.resources.productsans_thinitalic
-import org.jetbrains.compose.resources.Font
-
-object AppFont {
- val googleSans @Composable get() = FontFamily(
- Font(Res.font.productsans_thin, weight = FontWeight.W100, style = FontStyle.Normal),
- Font(Res.font.productsans_thinitalic, weight = FontWeight.W100, style = FontStyle.Italic),
-
- Font(Res.font.productsans_light, weight = FontWeight.W300, style = FontStyle.Normal),
- Font(Res.font.productsans_lightitalic, weight = FontWeight.W300, style = FontStyle.Italic),
-
- Font(Res.font.productsans_regular, weight = FontWeight.W400, style = FontStyle.Normal),
- Font(Res.font.productsans_italic, weight = FontWeight.W400, style = FontStyle.Italic),
-
- Font(Res.font.productsans_medium, weight = FontWeight.W500, style = FontStyle.Normal),
- Font(Res.font.productsans_mediumitalic, weight = FontWeight.W500, style = FontStyle.Italic),
-
- Font(Res.font.productsans_bold, weight = FontWeight.W700, style = FontStyle.Normal),
- Font(Res.font.productsans_bolditalic, weight = FontWeight.W700, style = FontStyle.Italic),
-
- Font(Res.font.productsans_black, weight = FontWeight.W900, style = FontStyle.Normal),
- Font(Res.font.productsans_blackitalic, weight = FontWeight.W900, style = FontStyle.Italic))
-
- private val defaultTypography = androidx.compose.material3.Typography()
- val typography @Composable get() =
- googleSans.let { fontFamily ->
- val lineHeight = 1.3.em
- Typography(
- displayLarge = defaultTypography.displayLarge.copy(fontFamily = fontFamily, lineHeight = lineHeight),
- displayMedium = defaultTypography.displayMedium.copy(fontFamily = fontFamily, lineHeight = lineHeight),
- displaySmall = defaultTypography.displaySmall.copy(fontFamily = fontFamily, lineHeight = lineHeight),
-
- headlineLarge = defaultTypography.headlineLarge.copy(fontFamily = fontFamily, lineHeight = lineHeight),
- headlineMedium = defaultTypography.headlineMedium.copy(fontFamily = fontFamily, lineHeight = lineHeight),
- headlineSmall = defaultTypography.headlineSmall.copy(fontFamily = fontFamily, lineHeight = lineHeight),
-
- titleLarge = defaultTypography.titleLarge.copy(fontFamily = fontFamily, lineHeight = lineHeight),
- titleMedium = defaultTypography.titleMedium.copy(fontFamily = fontFamily, lineHeight = lineHeight),
- titleSmall = defaultTypography.titleSmall.copy(fontFamily = fontFamily, lineHeight = lineHeight),
-
- bodyLarge = defaultTypography.bodyLarge.copy(fontFamily = fontFamily, lineHeight = lineHeight),
- bodyMedium = defaultTypography.bodyMedium.copy(fontFamily = fontFamily, lineHeight = lineHeight),
- bodySmall = defaultTypography.bodySmall.copy(fontFamily = fontFamily, lineHeight = lineHeight),
-
- labelLarge = defaultTypography.labelLarge.copy(fontFamily = fontFamily, lineHeight = lineHeight),
- labelMedium = defaultTypography.labelMedium.copy(fontFamily = fontFamily, lineHeight = lineHeight),
- labelSmall = defaultTypography.labelSmall.copy(fontFamily = fontFamily, lineHeight = lineHeight)
- )
- }
-}
-
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Palette.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Palette.kt
deleted file mode 100644
index c6332808f..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Palette.kt
+++ /dev/null
@@ -1,84 +0,0 @@
-package com.lagradost.cloudstream4.theme
-
-import androidx.compose.ui.graphics.Color
-
-enum class CloudStreamPrimaryColor(val color: Color) {
- NORMAL(Color(0xFF3D50FA)),
- BLUE(Color(0xFF5664B7)),
- PURPLE(Color(0xFF6200EA)),
- GREEN(Color(0xFF00BFA5)),
- GREEN_APPLE(Color(0xFF48E484)),
- RED(Color(0xFFD50000)),
- BANANA(Color(0xFFE4D448)),
- PARTY(Color(0xFFEA596E)),
- PINK(Color(0xFFFF1493)),
- CARNATION_PINK(Color(0xFFBD5DA5)),
- MAROON(Color(0xFF451010)),
- DARK_GREEN(Color(0xFF004500)),
- NAVY_BLUE(Color(0xFF000080)),
- GREY(Color(0xFF515151)),
- WHITE(Color(0xFFFFFFFF)),
- BROWN(Color(0xFF622C00)),
- ORANGE(Color(0xFFCE8500)),
- DANDELION_YELLOW(Color(0xFFF5BB00)),
- COOL_BLUE(Color(0xFF408CAC)),
- LAVENDER(Color(0xFF6F55AF)),
- DYNAMIC(Color(0xFF3D50FA)),
- DYNAMIC_TWO(Color(0xFF3D50FA)),
-}
-
-
-internal object CloudStreamPalette {
- // Default dark (AppTheme / Black)
- val Primary = Color(0xFF3D50FA)
- val PrimaryDark = Color(0xFF3700B3)
- val Ongoing = Color(0xFFF53B66)
-
- val DarkPrimaryGrayBg = Color(0xFF2B2C30)
- val DarkBlackBg = Color(0xFF111111)
- val DarkIconGrayBg = Color(0xFF1C1C20)
- val DarkBoxItemBg = Color(0xFF161616)
- val DarkText = Color(0xFFE9EAEE)
- val DarkGrayText = Color(0xFF9BA0A4)
- val DarkIcon = Color(0xFF9BA0A6)
-
- // Amoled
- val AmoledBlack = Color(0xFF000000)
- val AmoledNearBlack = Color(0xFF121212)
-
- // Light
- val LightPrimaryGrayBg = Color(0xFFF1F1F1)
- val LightBlackBg = Color(0xFFFFFFFF)
- val LightIconGrayBg = Color(0xFFEEEEEE)
- val LightBoxItemBg = Color(0xFFEEEEEE)
- val LightText = Color(0xFF202125)
- val LightGrayText = Color(0xFF5F6267)
- val LightIcon = Color(0xFF5F6267)
-
- // Dracula
- val DraculaPrimaryGrayBg = Color(0xFF414450)
- val DraculaBlackBg = Color(0xFF282A36)
- val DraculaIconGrayBg = Color(0xFF44475A)
- val DraculaBoxItemBg = Color(0xFF373844)
- val DraculaText = Color(0xFFF8F8F2)
- val DraculaGrayText = Color(0xFF6272A4)
- val DraculaIcon = Color(0xFF6272A4)
-
- // Lavender Dreams
- val LavenderPrimaryGrayBg = Color(0xFFF7EEFC)
- val LavenderBlackBg = Color(0xFFFDF0FB)
- val LavenderIconGrayBg = Color(0xFFB794F6)
- val LavenderBoxItemBg = Color(0xFFF8F5FF)
- val LavenderText = Color(0xFF2D1B47)
- val LavenderGrayText = Color(0xFF9AB3FF)
- val LavenderIcon = Color(0xFF7C3AED)
-
- // Silent Blue
- val SilentBluePrimaryGrayBg = Color(0xFF282F49)
- val SilentBlueBlackBg = Color(0xFF151A30)
- val SilentBlueIconGrayBg = Color(0xFF3A446A)
- val SilentBlueBoxItemBg = Color(0xFF3A446A)
- val SilentBlueText = Color(0xFFE0E1F3)
- val SilentBlueGrayText = Color(0xFF7B83B0)
- val SilentBlueIcon = Color(0xFF7B83B0)
-}
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Scheme.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Scheme.kt
deleted file mode 100644
index d51c75d08..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Scheme.kt
+++ /dev/null
@@ -1,162 +0,0 @@
-package com.lagradost.cloudstream4.theme
-
-import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.graphics.Color
-
-/**
- * Maps to the XML custom attrs declared in attrs.xml:
- * TODO: Remove this comment when we migrate fully
- * and attrs.xml will no longer be used at all.
- *
- * | XML ?attr | Here |
- * |--------------------------|--------------------|
- * | primaryBlackBackground | [background] |
- * | primaryGrayBackground | [surfaceVariant] |
- * | iconGrayBackground | [surface] |
- * | boxItemBackground | [surfaceContainer] |
- * | textColor | [onBackground] |
- * | grayTextColor | [onSurfaceVariant] |
- * | iconColor | [icon] |
- * | colorPrimary | [primary] |
- * | colorOngoing | [ongoing] |
- *
- * All fields are [MutableState] so Compose recomposes automatically
- * if the scheme is swapped at runtime (e.g. user changes theme without restart).
- */
-@Stable
-class CloudStreamColorScheme(
- background: Color,
- surfaceVariant: Color,
- surface: Color,
- surfaceContainer: Color,
- onBackground: Color,
- onSurfaceVariant: Color,
- icon: Color,
- primary: Color,
- ongoing: Color,
- isLight: Boolean,
-) {
- /** primaryBlackBackground */
- var background by mutableStateOf(background)
-
- /** primaryGrayBackground */
- var surfaceVariant by mutableStateOf(surfaceVariant)
-
- /** iconGrayBackground */
- var surface by mutableStateOf(surface)
-
- /** boxItemBackground */
- var surfaceContainer by mutableStateOf(surfaceContainer)
-
- /** textColor */
- var onBackground by mutableStateOf(onBackground)
-
- /** grayTextColor */
- var onSurfaceVariant by mutableStateOf(onSurfaceVariant)
-
- /** iconColor */
- var icon by mutableStateOf(icon)
-
- /** colorPrimary */
- var primary by mutableStateOf(primary)
-
- /** colorOngoing */
- var ongoing by mutableStateOf(ongoing)
- var isLight by mutableStateOf(isLight)
-
- fun copy(
- background: Color = this.background,
- surfaceVariant: Color = this.surfaceVariant,
- surface: Color = this.surface,
- surfaceContainer: Color = this.surfaceContainer,
- onBackground: Color = this.onBackground,
- onSurfaceVariant: Color = this.onSurfaceVariant,
- icon: Color = this.icon,
- primary: Color = this.primary,
- ongoing: Color = this.ongoing,
- isLight: Boolean = this.isLight,
- ) = CloudStreamColorScheme(
- background, surfaceVariant, surface, surfaceContainer,
- onBackground, onSurfaceVariant, icon, primary, ongoing, isLight,
- )
-}
-
-internal fun darkScheme() = CloudStreamColorScheme(
- background = CloudStreamPalette.DarkBlackBg,
- surfaceVariant = CloudStreamPalette.DarkPrimaryGrayBg,
- surface = CloudStreamPalette.DarkIconGrayBg,
- surfaceContainer = CloudStreamPalette.DarkBoxItemBg,
- onBackground = CloudStreamPalette.DarkText,
- onSurfaceVariant = CloudStreamPalette.DarkGrayText,
- icon = CloudStreamPalette.DarkIcon,
- primary = CloudStreamPalette.Primary,
- ongoing = CloudStreamPalette.Ongoing,
- isLight = false,
-)
-
-
-internal fun amoledScheme() = darkScheme().copy(
- background = CloudStreamPalette.AmoledBlack,
- surface = CloudStreamPalette.AmoledBlack,
- surfaceVariant = CloudStreamPalette.AmoledBlack,
- surfaceContainer = CloudStreamPalette.AmoledBlack,
-)
-
-internal fun amoledLightScheme() = amoledScheme().copy(
- surfaceVariant = CloudStreamPalette.AmoledNearBlack,
-)
-
-internal fun lightScheme() = CloudStreamColorScheme(
- background = CloudStreamPalette.LightBlackBg,
- surfaceVariant = CloudStreamPalette.LightPrimaryGrayBg,
- surface = CloudStreamPalette.LightIconGrayBg,
- surfaceContainer = CloudStreamPalette.LightBoxItemBg,
- onBackground = CloudStreamPalette.LightText,
- onSurfaceVariant = CloudStreamPalette.LightGrayText,
- icon = CloudStreamPalette.LightIcon,
- primary = CloudStreamPalette.Primary,
- ongoing = CloudStreamPalette.Ongoing,
- isLight = true,
-)
-
-internal fun draculaScheme() = CloudStreamColorScheme(
- background = CloudStreamPalette.DraculaBlackBg,
- surfaceVariant = CloudStreamPalette.DraculaPrimaryGrayBg,
- surface = CloudStreamPalette.DraculaIconGrayBg,
- surfaceContainer = CloudStreamPalette.DraculaBoxItemBg,
- onBackground = CloudStreamPalette.DraculaText,
- onSurfaceVariant = CloudStreamPalette.DraculaGrayText,
- icon = CloudStreamPalette.DraculaIcon,
- primary = CloudStreamPalette.Primary,
- ongoing = CloudStreamPalette.Ongoing,
- isLight = false,
-)
-
-internal fun lavenderScheme() = CloudStreamColorScheme(
- background = CloudStreamPalette.LavenderBlackBg,
- surfaceVariant = CloudStreamPalette.LavenderPrimaryGrayBg,
- surface = CloudStreamPalette.LavenderIconGrayBg,
- surfaceContainer = CloudStreamPalette.LavenderBoxItemBg,
- onBackground = CloudStreamPalette.LavenderText,
- onSurfaceVariant = CloudStreamPalette.LavenderGrayText,
- icon = CloudStreamPalette.LavenderIcon,
- primary = CloudStreamPalette.Primary,
- ongoing = CloudStreamPalette.Ongoing,
- isLight = true,
-)
-
-internal fun silentBlueScheme() = CloudStreamColorScheme(
- background = CloudStreamPalette.SilentBlueBlackBg,
- surfaceVariant = CloudStreamPalette.SilentBluePrimaryGrayBg,
- surface = CloudStreamPalette.SilentBlueIconGrayBg,
- surfaceContainer = CloudStreamPalette.SilentBlueBoxItemBg,
- onBackground = CloudStreamPalette.SilentBlueText,
- onSurfaceVariant = CloudStreamPalette.SilentBlueGrayText,
- icon = CloudStreamPalette.SilentBlueIcon,
- primary = CloudStreamPalette.Primary,
- ongoing = CloudStreamPalette.Ongoing,
- isLight = false,
-)
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Theme.kt b/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Theme.kt
deleted file mode 100644
index 196d8d6a1..000000000
--- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/theme/Theme.kt
+++ /dev/null
@@ -1,133 +0,0 @@
-package com.lagradost.cloudstream4.theme
-
-import androidx.compose.animation.core.InfiniteTransition
-import androidx.compose.animation.core.rememberInfiniteTransition
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.darkColorScheme
-import androidx.compose.material3.lightColorScheme
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.ReadOnlyComposable
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.ui.graphics.Color
-
-enum class CloudStreamThemeMode {
- /** "Black" standard dark, #111111 backgrounds */
- Dark,
-
- /** "Amoled" / "AmoledLight" pure black (#000000) */
- Amoled,
-
- /** "AmoledLight" pure black (#000000) */
- AmoledLight,
-
- /** "Light" white/gray backgrounds, dark text */
- Light,
-
- /** "Dracula" */
- Dracula,
-
- /** "Lavender" */
- Lavender,
-
- /** "SilentBlue" */
- SilentBlue,
-
- /** "System" resolved on each platform via [isSystemInDarkTheme] */
- FollowSystem,
-
- /**
- * Uses platform dynamic color system, Material You on Android 12+,
- * falls back to [Dark] on unsupported platforms.
- */
- Dynamic,
-}
-
-@Composable
-fun modeToTheme(mode : CloudStreamThemeMode, primaryColor: CloudStreamPrimaryColor) : CloudStreamColorScheme {
- val dynamicTheme = DeviceTheme.resolveDynamicTheme()
- val dynamicPrimary = DeviceTheme.resolveDynamicPrimaryColor()
- val dynamicSecondary = DeviceTheme.resolveDynamicSecondaryColor()
- val systemDark = isSystemInDarkTheme()
- val color = remember(mode, primaryColor, systemDark, dynamicTheme, dynamicPrimary, dynamicSecondary) {
- val base = when (mode) {
- CloudStreamThemeMode.Dark -> darkScheme()
- CloudStreamThemeMode.Amoled -> amoledScheme()
- CloudStreamThemeMode.AmoledLight -> amoledLightScheme()
- CloudStreamThemeMode.Light -> lightScheme()
- CloudStreamThemeMode.Dracula -> draculaScheme()
- CloudStreamThemeMode.Lavender -> lavenderScheme()
- CloudStreamThemeMode.SilentBlue -> silentBlueScheme()
- CloudStreamThemeMode.FollowSystem -> if (systemDark) darkScheme() else lightScheme()
- CloudStreamThemeMode.Dynamic -> dynamicTheme
- }
- when {
- mode == CloudStreamThemeMode.Dynamic -> base
- primaryColor == CloudStreamPrimaryColor.DYNAMIC -> base.copy(primary = dynamicPrimary)
- primaryColor == CloudStreamPrimaryColor.DYNAMIC_TWO -> base.copy(primary = dynamicSecondary)
- else -> base.copy(primary = primaryColor.color)
- }
- }
- return color
-}
-
-private fun CloudStreamColorScheme.toMaterial3ColorScheme() = if (isLight) {
- lightColorScheme(
- primary = primary,
- background = background,
- surface = surface,
- surfaceVariant = surfaceVariant,
- surfaceContainer = surfaceContainer,
- onBackground = onBackground,
- onSurface = onBackground,
- onSurfaceVariant = onSurfaceVariant,
- onPrimary = Color.White,
- )
-} else {
- darkColorScheme(
- primary = primary,
- background = background,
- surface = surface,
- surfaceVariant = surfaceVariant,
- surfaceContainer = surfaceContainer,
- onBackground = onBackground,
- onSurface = onBackground,
- onSurfaceVariant = onSurfaceVariant,
- onPrimary = Color.White,
- )
-}
-
-internal val LocalSharedInfiniteTransition = staticCompositionLocalOf { throw NotImplementedError() }
-
-/**
- * Global synchronized animation, so many items in e.g. a LazyList can animate at the same time,
- * even if they appeared at different times
- * */
-@Composable
-@ReadOnlyComposable
-fun MaterialTheme.infiniteSharedTransition() = LocalSharedInfiniteTransition.current
-
-@Composable
-fun CloudStreamPreviewTheme(content: @Composable () -> Unit) {
- CloudStreamTheme(content = content)
-}
-
-@Composable
-fun CloudStreamTheme(
- mode: CloudStreamThemeMode = CloudStreamThemeMode.FollowSystem,
- primaryColor: CloudStreamPrimaryColor = CloudStreamPrimaryColor.NORMAL,
- content: @Composable () -> Unit,
-) {
- val csColors = modeToTheme(mode, primaryColor)
- val globalTransition = rememberInfiniteTransition(label = "GlobalSharedTransition")
- // We do not provide csColors as a global, because people should use MaterialTheme directly instead
- CompositionLocalProvider(LocalSharedInfiniteTransition provides globalTransition) {
- MaterialTheme(
- colorScheme = csColors.toMaterial3ColorScheme(),
- content = content,
- typography = AppFont.typography
- )
- }
-}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/mihon/LICENSE b/shared/src/commonMain/kotlin/com/mihon/LICENSE
deleted file mode 100644
index d9a10c0d8..000000000
--- a/shared/src/commonMain/kotlin/com/mihon/LICENSE
+++ /dev/null
@@ -1,176 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
diff --git a/shared/src/commonMain/kotlin/com/mihon/common/preference/InMemoryPreferenceStore.kt b/shared/src/commonMain/kotlin/com/mihon/common/preference/InMemoryPreferenceStore.kt
deleted file mode 100644
index 5c604e540..000000000
--- a/shared/src/commonMain/kotlin/com/mihon/common/preference/InMemoryPreferenceStore.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-package com.mihon.common.preference
-
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * Local-copy implementation of PreferenceStore mostly for test and preview purposes
- */
-class InMemoryPreferenceStore(
- initialPreferences: Sequence> = sequenceOf(),
-) : PreferenceStore {
-
- private val preferences: Map> =
- initialPreferences.toList().associateBy { it.key() }
-
- override fun getString(key: String, defaultValue: String): PreferenceData {
- val default = InMemoryPreference(key, null, defaultValue)
- val data: String? = preferences[key]?.get() as? String
- return if (data == null) default else InMemoryPreference(key, data, defaultValue)
- }
-
- override fun getLong(key: String, defaultValue: Long): PreferenceData {
- val default = InMemoryPreference(key, null, defaultValue)
- val data: Long? = preferences[key]?.get() as? Long
- return if (data == null) default else InMemoryPreference(key, data, defaultValue)
- }
-
- override fun getInt(key: String, defaultValue: Int): PreferenceData {
- val default = InMemoryPreference(key, null, defaultValue)
- val data: Int? = preferences[key]?.get() as? Int
- return if (data == null) default else InMemoryPreference(key, data, defaultValue)
- }
-
- override fun getFloat(key: String, defaultValue: Float): PreferenceData {
- val default = InMemoryPreference(key, null, defaultValue)
- val data: Float? = preferences[key]?.get() as? Float
- return if (data == null) default else InMemoryPreference(key, data, defaultValue)
- }
-
- override fun getBoolean(key: String, defaultValue: Boolean): PreferenceData {
- val default = InMemoryPreference(key, null, defaultValue)
- val data: Boolean? = preferences[key]?.get() as? Boolean
- return if (data == null) default else InMemoryPreference(key, data, defaultValue)
- }
-
- override fun getStringSet(key: String, defaultValue: Set): PreferenceData> {
- val default = InMemoryPreference(key, null, defaultValue)
-
- @Suppress("UNCHECKED_CAST")
- val data: Set? = preferences[key]?.get() as? Set
- return if (data == null) default else InMemoryPreference(key, data, defaultValue)
- }
-
- @Suppress("UNCHECKED_CAST")
- override fun getObjectFromString(
- key: String,
- defaultValue: T,
- serializer: (T) -> String,
- deserializer: (String) -> T,
- ): PreferenceData {
- val default = InMemoryPreference(key, null, defaultValue)
- val data: T? = preferences[key]?.get() as? T
- return if (data == null) default else InMemoryPreference(key, data, defaultValue)
- }
-
- @Suppress("UNCHECKED_CAST")
- override fun getObjectFromInt(
- key: String,
- defaultValue: T,
- serializer: (T) -> Int,
- deserializer: (Int) -> T,
- ): PreferenceData {
- val default = InMemoryPreference(key, null, defaultValue)
- val data: T? = preferences[key]?.get() as? T
- return if (data == null) default else InMemoryPreference(key, data, defaultValue)
- }
-
- @Suppress("UNCHECKED_CAST")
- override fun getObjectSetFromStringSet(
- key: String,
- defaultValue: Set,
- serializer: (T) -> String,
- deserializer: (String) -> T?,
- ): PreferenceData> {
- val default = InMemoryPreference(key, null, defaultValue)
- val data: Set? = preferences[key]?.get() as? Set
- return if (data == null) default else InMemoryPreference(key, data, defaultValue)
- }
-
- override fun getAll(): Map {
- return preferences
- }
-
- class InMemoryPreference(
- private val key: String,
- private var data: T?,
- private val defaultValue: T,
- ) : PreferenceData {
- override fun key(): String = key
-
- override fun get(): T = data ?: defaultValue()
-
- override fun isSet(): Boolean = data != null
-
- override fun delete() {
- data = null
- }
-
- override fun defaultValue(): T = defaultValue
-
- override fun changes(): Flow = flow { data }
-
- override fun stateIn(scope: CoroutineScope): StateFlow {
- return changes().stateIn(scope, SharingStarted.Eagerly, get())
- }
-
- override fun set(value: T) {
- data = value
- }
- }
-}
diff --git a/shared/src/commonMain/kotlin/com/mihon/common/preference/PreferenceData.kt b/shared/src/commonMain/kotlin/com/mihon/common/preference/PreferenceData.kt
deleted file mode 100644
index 16aeb41bc..000000000
--- a/shared/src/commonMain/kotlin/com/mihon/common/preference/PreferenceData.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.mihon.common.preference
-
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
-
-interface PreferenceData {
-
- fun key(): String
-
- fun get(): T
-
- fun set(value: T)
-
- fun isSet(): Boolean
-
- fun delete()
-
- fun defaultValue(): T
-
- fun changes(): Flow
-
- fun stateIn(scope: CoroutineScope): StateFlow
-
- companion object {
- /**
- * A preference that should not be exposed in places like backups without user consent.
- */
- fun isPrivate(key: String): Boolean {
- return key.startsWith(PRIVATE_PREFIX)
- }
- fun privateKey(key: String): String {
- return "$PRIVATE_PREFIX$key"
- }
-
- /**
- * A preference used for internal app state that isn't really a user preference
- * and therefore should not be in places like backups.
- */
- fun isAppState(key: String): Boolean {
- return key.startsWith(APP_STATE_PREFIX)
- }
- fun appStateKey(key: String): String {
- return "$APP_STATE_PREFIX$key"
- }
-
- private const val APP_STATE_PREFIX = "__APP_STATE_"
- private const val PRIVATE_PREFIX = "__PRIVATE_"
- }
-}
-
-inline fun PreferenceData.getAndSet(crossinline block: (T) -> R) = set(
- block(get()),
-)
-
-operator fun PreferenceData>.plusAssign(item: T) {
- set(get() + item)
-}
-
-operator fun PreferenceData>.plusAssign(items: Iterable) {
- set(get() + items)
-}
-
-operator fun PreferenceData>.minusAssign(item: T) {
- set(get() - item)
-}
-
-fun PreferenceData.toggle(): Boolean {
- set(!get())
- return get()
-}
diff --git a/shared/src/commonMain/kotlin/com/mihon/common/preference/PreferenceStore.kt b/shared/src/commonMain/kotlin/com/mihon/common/preference/PreferenceStore.kt
deleted file mode 100644
index 6075fb45c..000000000
--- a/shared/src/commonMain/kotlin/com/mihon/common/preference/PreferenceStore.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.mihon.common.preference
-
-interface PreferenceStore {
-
- fun getString(key: String, defaultValue: String = ""): PreferenceData
-
- fun getLong(key: String, defaultValue: Long = 0): PreferenceData
-
- fun getInt(key: String, defaultValue: Int = 0): PreferenceData
-
- fun getFloat(key: String, defaultValue: Float = 0f): PreferenceData
-
- fun getBoolean(key: String, defaultValue: Boolean = false): PreferenceData
-
- fun getStringSet(key: String, defaultValue: Set = emptySet()): PreferenceData>
-
- fun getObjectFromString(
- key: String,
- defaultValue: T,
- serializer: (T) -> String,
- deserializer: (String) -> T,
- ): PreferenceData
-
- fun getObjectFromInt(
- key: String,
- defaultValue: T,
- serializer: (T) -> Int,
- deserializer: (Int) -> T,
- ): PreferenceData
-
- fun getObjectSetFromStringSet(
- key: String,
- defaultValue: Set,
- serializer: (T) -> String,
- deserializer: (String) -> T?,
- ): PreferenceData>
-
- fun getAll(): Map
-}
-
-fun PreferenceStore.getLongArray(
- key: String,
- defaultValue: List