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/java/com/lagradost/cloudstream3/CloudStreamApp.kt b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt index 6ff3ab54d..466ef5a00 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt @@ -170,11 +170,11 @@ class CloudStreamApp : Application(), SingletonImageLoader.Factory { } /** Will fall back to WebView if in TV or emulator layout. */ - fun openBrowser(url: String, activity: Activity?) { + fun openBrowser(url: String, activity: FragmentActivity?) { openBrowser( url, isLayout(TV or EMULATOR), - (activity as? FragmentActivity)?.supportFragmentManager?.fragments?.lastOrNull() + activity?.supportFragmentManager?.fragments?.lastOrNull() ) } } 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/ui/account/AccountSelectActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt index 3990bcc78..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) 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 c9c9adfc2..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 @@ -30,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 @@ -68,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 @@ -112,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, @@ -513,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) @@ -714,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)) { @@ -785,17 +740,6 @@ 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) @@ -818,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) } @@ -829,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 @@ -850,7 +787,6 @@ class HomeFragment : BaseFragment( homeLoadingError.isVisible = false homeMasterRecycler.isVisible = true homeLoadingShimmer.stopShimmer() - //home_loaded?.isVisible = true if (toggleRandomButton) { val distinct = d.values @@ -871,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) + } } } 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 d90b6043f..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 @@ -307,7 +307,6 @@ open class FullScreenPlayer : AbstractPlayerFragment( playerVideoTitleRez, playerVideoInfo, playerGoBackHolder, - playerVideoClock, ).forEach { it.animateY(titleMove) } @@ -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 4495a5602..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 @@ -134,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 @@ -513,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) @@ -1115,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)}" }) @@ -1153,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 @@ -1161,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) - } } } @@ -1393,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) @@ -1562,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" @@ -1588,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() } @@ -1614,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 } @@ -1624,7 +1574,7 @@ class GeneratorPlayer : FullScreenPlayer() { if (!isPlayerActive.compareAndSet(false, true)) { return } - loadLink(firstAvailableLink, false) + loadLink(links.first(), false) showPlayerMetadata() } @@ -1691,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() { @@ -2217,7 +2166,6 @@ class GeneratorPlayer : FullScreenPlayer() { isPlayerActive.set(false) binding?.overlayLoadingSkipButton?.isVisible = false binding?.playerLoadingOverlay?.isVisible = true - viewModel.modifyState { setError(emptyList()) } uiReset() } @@ -2271,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) @@ -2357,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/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 066db8be8..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragmentScreen.kt +++ /dev/null @@ -1,430 +0,0 @@ -package com.lagradost.cloudstream3.ui.settings - -import android.annotation.SuppressLint -import android.os.Bundle -import androidx.activity.compose.BackHandler -import androidx.compose.animation.Crossfade -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -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.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.shape.CircleShape -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.SearchBarDefaults -import androidx.compose.material3.SearchBarState -import androidx.compose.material3.SearchBarValue -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.material3.rememberSearchBarState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.geometry.Offset -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.layout.onGloballyPositioned -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.clearAndSetSemantics -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.circle -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.Preference -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.persistentListOf -import kotlinx.collections.immutable.toPersistentList -import kotlinx.coroutines.launch -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, - ), - SettingsNavigation( - title = R.string.category_player, - navigation = R.id.action_navigation_global_to_navigation_settings_player, - screen = SettingsPlayerScreen, - icon = R.drawable.play_arrow_24px, - ), - /*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, - ), - 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, - ), - SettingsNavigation( - title = R.string.category_account, - navigation = R.id.action_navigation_global_to_navigation_settings_account, - screen = SettingsAccountScreen, - icon = R.drawable.encrypted_24px, - ), - 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 = R.string.add_repository - ), - ) - - data class SettingsNavigation( - val title: Int, - val navigation: Int, - val screen: SearchableSettings?, - val icon: Int, - val subtitle: Int? = null, - ) - - - @OptIn(ExperimentalMaterial3Api::class) - @Composable - @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") - override fun Content() { - val textFieldState = rememberTextFieldState() - val searchBarState = rememberSearchBarState() - - val screen = screens.mapNotNull { item -> - val contents = item.screen?.getPreferences() ?: return@mapNotNull null - SettingsData( - title = stringResource(item.title), - navigation = item.navigation, - contents = contents - ) - }.toPersistentList() - - 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() - - Box( - modifier = Modifier - .size(50.dp) - .border( - 2.dp, - MaterialTheme.colorScheme.onBackground.copy(alpha = 0.2f), - CircleShape - ) - .circle(), - ) { - AsyncImage( - contentScale = ContentScale.Crop, - model = image, - modifier = Modifier.fillMaxSize(), - 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(searchBarState = searchBarState, textFieldState = textFieldState) - Spacer(modifier = Modifier.height(MaterialTheme.padding.small)) - - SettingSearchResults( - nestedScrollConnection = parentFirstScrollConnection, - searchKey = textFieldState.text.toString(), - items = screen, - onItemClick = { item -> - SearchableSettings.highlightKey = item.highlightKey - activity?.navigate(item.navigation) - }, empty = { - Column( - modifier = Modifier - .nestedScroll(parentFirstScrollConnection) - ) { - screens.forEach { settingsTab -> - SettingsTab(settingsTab) - } - BuildStamp() - } - }) - - } - } - } - - @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) - @Composable - fun SettingsSearch(searchBarState: SearchBarState, textFieldState: TextFieldState) { - val scope = rememberCoroutineScope() - val inputField = - @Composable { - SearchBarDefaults.InputField( - textFieldState = textFieldState, - searchBarState = searchBarState, - modifier = Modifier.onFocusChanged { newFocus -> - if (newFocus.hasFocus) { - scope.launch { - searchBarState.animateToExpanded() - } - } - }, - onSearch = { scope.launch { searchBarState.animateToCollapsed() } }, - placeholder = { - Text(modifier = Modifier.clearAndSetSemantics {}, text = stringResource(R.string.search_hint)) - }, - leadingIcon = { - Crossfade( - targetState = searchBarState.targetValue, - label = "leftsearch", - ) { value -> - when (value) { - SearchBarValue.Expanded -> { - IconButton(onClick = { - textFieldState.edit { replace(0, length, "") } - scope.launch { - searchBarState.animateToCollapsed() - } - }) { - Icon( - painter = painterResource(R.drawable.keyboard_arrow_left_24px), - tint = MaterialTheme.colorScheme.onBackground, - contentDescription = null - ) - } - } - - SearchBarValue.Collapsed -> { - IconButton(onClick = { - scope.launch { - searchBarState.animateToExpanded() - } - }) { - Icon( - painter = painterResource(R.drawable.search_icon), - tint = MaterialTheme.colorScheme.onBackground, - contentDescription = null - ) - } - } - } - } - - /*SampleLeadingIcon(searchBarState, scope)*/ - }, - trailingIcon = { - Crossfade( - targetState = searchBarState.targetValue, - label = "rightsearch", - ) { value -> - when (value) { - SearchBarValue.Expanded -> { - IconButton(onClick = { - textFieldState.edit { replace(0, length, "") } - }) { - Icon( - painter = painterResource(R.drawable.close_24px), - tint = MaterialTheme.colorScheme.onBackground, - contentDescription = null - ) - } - } - - SearchBarValue.Collapsed -> { - } - } - } - }, - ) - } - - Surface( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp - 12.dp * searchBarState.progress) - .focusOutline(enabled = isLayout(TV), CircleShape) - .onGloballyPositioned { searchBarState.collapsedCoords = it }, - shape = SearchBarDefaults.inputFieldShape, - color = MaterialTheme.colorScheme.surfaceVariant, - tonalElevation = SearchBarDefaults.TonalElevation, - shadowElevation = SearchBarDefaults.ShadowElevation, - content = inputField, - ) - - BackHandler(enabled = searchBarState.targetValue == SearchBarValue.Expanded) { - textFieldState.edit { replace(0, length, "") } - scope.launch { - searchBarState.animateToCollapsed() - } - } - } - - @Composable - fun SettingsTab(settingsTab: SettingsNavigation) { - val pref = settingsTab.screen?.getPreferences() ?: emptyList() - val groups = pref.filterIsInstance() - .filter { it.enabled } - TextPreferenceWidget( - title = stringResource(settingsTab.title), - icon = painterResource(settingsTab.icon), - subtitle = settingsTab.subtitle?.let { stringResource(it) } - ?: groups.joinToString { it.title }) { - // Clear it if we have already set it but navigated back instantly - 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 91d729b91..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"), @@ -177,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 -> @@ -278,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) @@ -314,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 { _ -> @@ -341,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(), @@ -349,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 @@ -367,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 b2f20b017..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt +++ /dev/null @@ -1,299 +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 - -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 parallelDownloads by settings.general.parallelDownloads.collectAsState() - val concurrentConnections by settings.general.concurrentConnections.collectAsState() - val locale by settings.general.locale.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.BasicListPreference( - value = 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() - }, - 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()) - }), - )), - - 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), - value = parallelDownloads, - valueRange = 1..10, - title = stringResource(R.string.parallel_downloads), - subtitle = stringResource(R.string.download_parallel_settings_des), - onValueChanged = settings.general.parallelDownloads::set, - ), - Preference.PreferenceItem.SliderPreference( - icon = painterResource(R.drawable.arrow_and_edge_24px), - value = concurrentConnections, - valueRange = 1..10, - title = stringResource(R.string.concurrent_connections), - subtitle = stringResource(R.string.concurrent_connections_settings_des), - onValueChanged = settings.general.concurrentConnections::set, - ), - 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 309b65cb8..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayerScreen.kt +++ /dev/null @@ -1,353 +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 com.mihon.presentation.settings.collectAsState -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) - } - } - - val playerSeekTime by settings.player.doubleTapTime.collectAsState() - val tvSeekOnTime by settings.player.tvSeekOnTime.collectAsState() - val tvSeekOffTime by settings.player.tvSeekOffTime.collectAsState() - - 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.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( - value = playerSeekTime, - title = stringResource(R.string.double_tap_to_seek_amount_settings), - icon = painterResource(R.drawable.go_forward_30), - valueRange = 5..60, - steps = 10, - onValueChanged = settings.player.doubleTapTime::set - ), - ) - ), - Preference.PreferenceGroup( - title = stringResource(R.string.pref_category_android_tv), - enabled = isLayout(TV or EMULATOR), - preferenceItems = persistentListOf( - Preference.PreferenceItem.SliderPreference( - value = 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, - onValueChanged = settings.player.tvSeekOnTime::set - ), - Preference.PreferenceItem.SliderPreference( - value = 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, - onValueChanged = settings.player.tvSeekOffTime::set - ), - ) - ), - 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 5c56cb515..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUIScreen.kt +++ /dev/null @@ -1,306 +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.runtime.getValue -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 com.mihon.presentation.settings.collectAsState -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() - - val overscanDp by settings.ui.overscanDp.collectAsState() - val posterSize by settings.ui.posterSize.collectAsState() - - 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( - value = 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 - ) - }), - - 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( - value = 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) - }), - ), - ), - 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 3c90e8e36..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdatesScreen.kt +++ /dev/null @@ -1,229 +0,0 @@ -package com.lagradost.cloudstream3.ui.settings - -import android.content.Intent -import android.net.Uri -import android.widget.Toast -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.CommonActivity.showToast -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.InAppUpdater.runAutoUpdate -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 - } - } - - 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 = { - 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/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 91a75d0ab..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/BaseComposeFragment.kt +++ /dev/null @@ -1,43 +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.Fragment -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() - ) - } -} \ 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/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/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/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/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/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/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_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/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/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/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/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/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/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/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/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/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/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 da3f819b6..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 ইনস্টলার @@ -354,51 +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 diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml index c713b17fd..983a03db7 100644 --- a/app/src/main/res/values-b+cs/strings.xml +++ b/app/src/main/res/values-b+cs/strings.xml @@ -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,15 +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č 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 76f7240cf..bf401b20a 100644 --- a/app/src/main/res/values-b+fr/strings.xml +++ b/app/src/main/res/values-b+fr/strings.xml @@ -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,15 +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 - + Direct diff --git a/app/src/main/res/values-b+in/strings.xml b/app/src/main/res/values-b+in/strings.xml index 4597ec5ac..7d6475f31 100644 --- a/app/src/main/res/values-b+in/strings.xml +++ b/app/src/main/res/values-b+in/strings.xml @@ -747,12 +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 - diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-b+it/strings.xml index 2672f39b1..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,7 +346,7 @@ Protagonista Supporto Secondario - Fonte + Sorgente Casuale Prossimamente… Cam @@ -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,20 +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 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+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 418a4a2ca..35367a1b2 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -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,15 +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 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 680307ae9..455478b5a 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -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,14 +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 - 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 0b35d39a7..52d4e1d05 100644 --- a/app/src/main/res/values-b+sv/strings.xml +++ b/app/src/main/res/values-b+sv/strings.xml @@ -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,13 +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 - 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 e4a2dbc37..d711505a8 100644 --- a/app/src/main/res/values-b+tr/strings.xml +++ b/app/src/main/res/values-b+tr/strings.xml @@ -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,13 +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ı - diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index 56e5a916e..ba46736ac 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -646,7 +646,7 @@ Еп. %s Дата %s Оновити розширення - Успішно оновлено %d розширення(-ь) + Успішно оновлено %d розширення(-ь)! Оновити розширення вручну Починається оновлення розширень! Не оновлено жодного розширення. @@ -739,15 +739,4 @@ Відео Передперегляд Наживо - Показувати поточний час - Показувати поточний час у верхній частині екрана (на головній та у програвачі) - Налаштування профілю - Приховати джерела з від’ємним пріоритетом - Приховати джерела з помилками - - %d приховане посилання - %d приховані посилання - %d прихованих посилань - %d прихованих посилань - diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index a68d45183..3b2b3876f 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -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 @@ -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,12 +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 - diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index dc7eab451..56ec8f43e 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -655,7 +655,7 @@ 得分 %s 日期 %s 更新插件 - 成功更新了 %d 个插件 + 成功更新了 %d 个插件! 没有插件被更新。 手动更新插件 集数(升序) @@ -747,12 +747,4 @@ 视频 预览 播放中 - 显示实时时钟 - 在屏幕顶部显示实时时钟。应用于主页和播放器 - 配置文件设置 - 隐藏负优先级的来源 - 隐藏有错误的来源 - - %d 条隐藏链接 - 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-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/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 87e447ccb..31cf951cf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -139,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 @@ -375,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 @@ -459,8 +456,8 @@ Random Coming soon… Cam - CamRip - CamHD + Cam + Cam HQ HD TS @@ -625,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 @@ -734,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 @@ -793,4 +783,5 @@ %d downloads queued Live + 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/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 4652cf938..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,8 +66,7 @@ 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" } @@ -143,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" } @@ -163,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"] @@ -173,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/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/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 245339f00..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 @@ -110,7 +109,6 @@ 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.Hgcloudto import com.lagradost.cloudstream3.extractors.HglinkTo @@ -119,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 @@ -161,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 @@ -170,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 @@ -184,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 @@ -276,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 @@ -304,7 +295,6 @@ 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.VkExtractor @@ -1149,7 +1139,6 @@ val extractorApis: AtomicMutableList = atomicListOf( Vicloud(), Uservideo(), Userscloud(), - HubuCloud(), Movhide(), StreamhideCom(), @@ -1192,14 +1181,6 @@ val extractorApis: AtomicMutableList = atomicListOf( Vidaratem(), VidaaraxCom(), VidaaraxNet(), - Odysseusa(), - Handfacesnap(), - Namefacesnap(), - Thebesthostertv(), - Vidmatrixa(), - VidChampions(), - Antarcticadocs(), - Nameitweb(), GamoVideo(), Gdriveplayerapi(), @@ -1339,7 +1320,6 @@ val extractorApis: AtomicMutableList = atomicListOf( Flyfile(), Firestream(), Vids(), - Playmate() ) 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 2bc9150d0..000000000 --- a/shared/build.gradle.kts +++ /dev/null @@ -1,56 +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.bundles.compose) - implementation(libs.kotlinx.collections.immutable) - implementation(project(":library")) - } - - androidMain.dependencies { - implementation(libs.activity.compose) - implementation(libs.preference.ktx) - } - } -} - -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/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 fbd98f649..000000000 --- a/shared/src/commonMain/composeResources/values/strings.xml +++ /dev/null @@ -1,10 +0,0 @@ - - CloudStream - None - No results found - Ok - Cancel - 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 e7d8900e1..000000000 --- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.kt +++ /dev/null @@ -1,202 +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) -} - -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 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/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 ee910dd36..000000000 --- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Modifiers.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.lagradost.cloudstream4.compose - -import androidx.compose.foundation.border -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 - -/* -@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) - -/** - * 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 8255b4260..000000000 --- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/Viewmodel.kt +++ /dev/null @@ -1,145 +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 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/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, -): PreferenceData> { - return getObjectFromString( - key = key, - defaultValue = defaultValue, - serializer = { it.joinToString(",") }, - deserializer = { it.split(",").mapNotNull { l -> l.toLongOrNull() } }, - ) -} - -inline fun > PreferenceStore.getEnum( - key: String, - defaultValue: T, -): PreferenceData { - return getObjectFromString( - key = key, - defaultValue = defaultValue, - serializer = { it.name }, - deserializer = { - try { - enumValueOf(it) - } catch (e: IllegalArgumentException) { - defaultValue - } - }, - ) -} - -inline fun > PreferenceStore.getEnumSet( - key: String, - defaultValue: Set, -): PreferenceData> { - return getObjectSetFromStringSet( - key = key, - defaultValue = defaultValue, - serializer = { it.name }, - deserializer = { - try { - enumValueOf(it) - } catch (_: IllegalArgumentException) { - null - } - }, - ) -} diff --git a/shared/src/commonMain/kotlin/com/mihon/material/AppBar.kt b/shared/src/commonMain/kotlin/com/mihon/material/AppBar.kt deleted file mode 100644 index e54441595..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/material/AppBar.kt +++ /dev/null @@ -1,406 +0,0 @@ -package com.mihon.material - -import androidx.compose.foundation.basicMarquee -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.material3.surfaceColorAtElevation -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream4.generated.resources.Res -import com.lagradost.cloudstream4.generated.resources.action_bar_up_description -import com.lagradost.cloudstream4.generated.resources.arrow_back -import com.lagradost.cloudstream4.generated.resources.cancel -import com.lagradost.cloudstream4.generated.resources.close -import org.jetbrains.compose.resources.painterResource -import org.jetbrains.compose.resources.stringResource - -@Composable -fun AppBar( - title: String?, - - modifier: Modifier = Modifier, - backgroundColor: Color? = null, - // Text - subtitle: String? = null, - // Up button - navigateUp: (() -> Unit)? = null, - navigationIcon: Painter? = null, - // Menu - actions: @Composable RowScope.() -> Unit = {}, - // Action mode - actionModeCounter: Int = 0, - onCancelActionMode: () -> Unit = {}, - actionModeActions: @Composable RowScope.() -> Unit = {}, - - scrollBehavior: TopAppBarScrollBehavior? = null, -) { - val isActionMode by remember(actionModeCounter) { - derivedStateOf { actionModeCounter > 0 } - } - - AppBar( - modifier = modifier, - backgroundColor = backgroundColor, - titleContent = { - if (isActionMode) { - AppBarTitle(actionModeCounter.toString()) - } else { - AppBarTitle(title, subtitle = subtitle) - } - }, - navigateUp = navigateUp, - navigationIcon = navigationIcon, - actions = { - if (isActionMode) { - actionModeActions() - } else { - actions() - } - }, - isActionMode = isActionMode, - onCancelActionMode = onCancelActionMode, - scrollBehavior = scrollBehavior, - ) -} - -@Composable -fun AppBar( - // Title - titleContent: @Composable () -> Unit, - - modifier: Modifier = Modifier, - backgroundColor: Color? = null, - // Up button - navigateUp: (() -> Unit)? = null, - navigationIcon: Painter? = null, - // Menu - actions: @Composable RowScope.() -> Unit = {}, - // Action mode - isActionMode: Boolean = false, - onCancelActionMode: () -> Unit = {}, - - scrollBehavior: TopAppBarScrollBehavior? = null, -) { - Column( - modifier = modifier, - ) { - TopAppBar( - // TODO @WindowInsets remove this when we have converted everything to compose - windowInsets = WindowInsets(0, 0, 0, 0), - navigationIcon = { - if (isActionMode) { - IconButton(onClick = onCancelActionMode) { - Icon( - painter = painterResource(Res.drawable.close), - contentDescription = stringResource(Res.string.cancel), - ) - } - } else { - navigateUp?.let { - IconButton(onClick = it) { - UpIcon(navigationIcon = navigationIcon) - } - } - } - }, - title = titleContent, - actions = actions, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = backgroundColor ?: MaterialTheme.colorScheme.surfaceColorAtElevation( - elevation = if (isActionMode) 3.dp else 0.dp, - ), - ), - scrollBehavior = scrollBehavior, - ) - } -} - -@Composable -fun AppBarTitle( - title: String?, - modifier: Modifier = Modifier, - subtitle: String? = null, -) { - Column(modifier = modifier) { - title?.let { - Text( - text = it, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - } - subtitle?.let { - Text( - text = it, - style = MaterialTheme.typography.bodyMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.basicMarquee( - repeatDelayMillis = 2_000, - ), - ) - } - } -} - -/* -@Composable -fun AppBarActions( - actions: List, -) { - var showMenu by remember { mutableStateOf(false) } - - actions.filterIsInstance().map { - TooltipBox( - positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above), - tooltip = { - PlainTooltip { - Text(it.title) - } - }, - state = rememberTooltipState(), - focusable = false, - ) { - IconButton( - onClick = it.onClick, - enabled = it.enabled, - ) { - Icon( - imageVector = it.icon, - tint = it.iconTint ?: LocalContentColor.current, - contentDescription = it.title, - ) - } - } - } - - val overflowActions = actions.filterIsInstance() - if (overflowActions.isNotEmpty()) { - TooltipBox( - positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above), - tooltip = { - PlainTooltip { - Text(stringResource(MR.strings.action_menu_overflow_description)) - } - }, - state = rememberTooltipState(), - focusable = false, - ) { - IconButton( - onClick = { showMenu = !showMenu }, - ) { - Icon( - MaterialSymbols.Rounded.MoreVert, - contentDescription = stringResource(MR.strings.action_menu_overflow_description), - ) - } - } - - DropdownMenu( - expanded = showMenu, - onDismissRequest = { showMenu = false }, - ) { - overflowActions.map { - DropdownMenuItem( - onClick = { - it.onClick() - showMenu = false - }, - text = { Text(it.title, fontWeight = FontWeight.Normal) }, - ) - } - } - } -}*/ - -/* -/** - * @param searchEnabled Set to false if you don't want to show search action. - * @param searchQuery If null, use normal toolbar. - * @param placeholderText If null, [MR.strings.action_search_hint] is used. - */ -@Composable -fun SearchToolbar( - searchQuery: String?, - onChangeSearchQuery: (String?) -> Unit, - modifier: Modifier = Modifier, - titleContent: @Composable () -> Unit = {}, - navigateUp: (() -> Unit)? = null, - searchEnabled: Boolean = true, - placeholderText: String? = null, - onSearch: (String) -> Unit = {}, - onClickCloseSearch: () -> Unit = { onChangeSearchQuery(null) }, - actions: @Composable RowScope.() -> Unit = {}, - scrollBehavior: TopAppBarScrollBehavior? = null, - visualTransformation: VisualTransformation = VisualTransformation.None, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, -) { - val focusRequester = remember { FocusRequester() } - - AppBar( - modifier = modifier, - titleContent = { - if (searchQuery == null) return@AppBar titleContent() - - val keyboardController = LocalSoftwareKeyboardController.current - val focusManager = LocalFocusManager.current - - val searchAndClearFocus: () -> Unit = f@{ - if (searchQuery.isBlank()) return@f - onSearch(searchQuery) - focusManager.clearFocus() - keyboardController?.hide() - focusManager.moveFocus(FocusDirection.Next) - } - - BasicTextField( - value = searchQuery, - onValueChange = onChangeSearchQuery, - modifier = Modifier - .fillMaxWidth() - .focusRequester(focusRequester) - .runOnEnterKeyPressed(action = searchAndClearFocus) - .showSoftKeyboard(remember { searchQuery.isEmpty() }) - .clearFocusOnSoftKeyboardHide(), - textStyle = MaterialTheme.typography.titleMedium.copy( - color = MaterialTheme.colorScheme.onBackground, - fontWeight = FontWeight.Normal, - fontSize = 18.sp, - ), - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), - keyboardActions = KeyboardActions(onSearch = { searchAndClearFocus() }), - singleLine = true, - cursorBrush = SolidColor(MaterialTheme.colorScheme.onBackground), - visualTransformation = visualTransformation, - interactionSource = interactionSource, - decorationBox = { innerTextField -> - TextFieldDefaults.DecorationBox( - value = searchQuery, - innerTextField = innerTextField, - enabled = true, - singleLine = true, - visualTransformation = visualTransformation, - interactionSource = interactionSource, - placeholder = { - Text( - modifier = Modifier.secondaryItemAlpha(), - text = (placeholderText ?: stringResource(MR.strings.action_search_hint)), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleMedium.copy( - fontSize = 18.sp, - fontWeight = FontWeight.Normal, - ), - ) - }, - container = {}, - ) - }, - ) - }, - navigateUp = if (searchQuery == null) navigateUp else onClickCloseSearch, - actions = { - key("search") { - val onClick = { onChangeSearchQuery("") } - - if (!searchEnabled) { - // Don't show search action - } else if (searchQuery == null) { - TooltipBox( - positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above), - tooltip = { - PlainTooltip { - Text(stringResource(MR.strings.action_search)) - } - }, - state = rememberTooltipState(), - focusable = false, - ) { - IconButton( - onClick = onClick, - ) { - Icon( - MaterialSymbols.Rounded.Search, - contentDescription = stringResource(MR.strings.action_search), - ) - } - } - } else if (searchQuery.isNotEmpty()) { - TooltipBox( - positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above), - tooltip = { - PlainTooltip { - Text(stringResource(MR.strings.action_reset)) - } - }, - state = rememberTooltipState(), - focusable = false, - ) { - IconButton( - onClick = { - onClick() - focusRequester.requestFocus() - }, - ) { - Icon( - MaterialSymbols.Rounded.Close, - contentDescription = stringResource(MR.strings.action_reset), - ) - } - } - } - } - - key("actions") { actions() } - }, - isActionMode = false, - scrollBehavior = scrollBehavior, - ) -}*/ - -@Composable -fun UpIcon( - modifier: Modifier = Modifier, - navigationIcon: Painter? = null, -) { - val icon = navigationIcon - ?: painterResource(Res.drawable.arrow_back) - Icon( - painter = icon, - contentDescription = stringResource(Res.string.action_bar_up_description), - modifier = modifier, - ) -} -/* -sealed interface AppBar { - sealed interface AppBarAction - - data class Action( - val title: String, - val icon: ImageVector, - val iconTint: Color? = null, - val onClick: () -> Unit, - val enabled: Boolean = true, - ) : AppBarAction - - data class OverflowAction( - val title: String, - val onClick: () -> Unit, - ) : AppBarAction -} -*/ \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mihon/material/Constants.kt b/shared/src/commonMain/kotlin/com/mihon/material/Constants.kt deleted file mode 100644 index 2b38b4485..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/material/Constants.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.mihon.material - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.material3.MaterialTheme -import androidx.compose.ui.unit.dp - -val topSmallPaddingValues = PaddingValues(top = MaterialTheme.padding.small) - -const val DISABLED_ALPHA = .38f -const val SECONDARY_ALPHA = .78f - -class Padding { - - val extraLarge = 32.dp - - val large = 24.dp - - val medium = 16.dp - - val small = 8.dp - - val extraSmall = 4.dp -} - -val MaterialTheme.padding: Padding - get() = Padding() diff --git a/shared/src/commonMain/kotlin/com/mihon/material/LabeledCheckbox.kt b/shared/src/commonMain/kotlin/com/mihon/material/LabeledCheckbox.kt deleted file mode 100644 index 123bb34b7..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/material/LabeledCheckbox.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.mihon.material - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.material3.Checkbox -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.unit.dp - -@Composable -fun LabeledCheckbox( - label: String, - checked: Boolean, - onCheckedChange: (Boolean) -> Unit, - modifier: Modifier = Modifier, - enabled: Boolean = true, -) { - Row( - modifier = modifier - .clip(MaterialTheme.shapes.small) - .fillMaxWidth() - .heightIn(min = 48.dp) - .clickable( - role = Role.Checkbox, - onClick = { - if (enabled) { - onCheckedChange(!checked) - } - }, - ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), - ) { - Checkbox( - checked = checked, - onCheckedChange = null, - enabled = enabled, - ) - - Text(text = label) - } -} diff --git a/shared/src/commonMain/kotlin/com/mihon/material/Pill.kt b/shared/src/commonMain/kotlin/com/mihon/material/Pill.kt deleted file mode 100644 index 44d6c2931..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/material/Pill.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.mihon.material - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.unit.TextUnit -import androidx.compose.ui.unit.dp - -@Composable -fun Pill( - text: String, - modifier: Modifier = Modifier, - color: Color = MaterialTheme.colorScheme.surfaceContainerHigh, - contentColor: Color = MaterialTheme.colorScheme.onSurface, - style: TextStyle = LocalTextStyle.current, -) { - Surface( - modifier = modifier - .padding(start = 4.dp), - shape = MaterialTheme.shapes.extraLarge, - color = color, - contentColor = contentColor, - ) { - Box( - modifier = Modifier - .padding(6.dp, 1.dp), - contentAlignment = Alignment.Center, - ) { - Text( - text = text, - maxLines = 1, - style = style, - ) - } - } -} - -@Composable -fun Pill( - text: String, - modifier: Modifier = Modifier, - color: Color = MaterialTheme.colorScheme.surfaceContainerHigh, - contentColor: Color = MaterialTheme.colorScheme.onSurface, - fontSize: TextUnit = LocalTextStyle.current.fontSize, -) { - Pill( - text = text, - modifier = modifier, - color = color, - contentColor = contentColor, - style = LocalTextStyle.current.merge(fontSize = fontSize), - ) -} diff --git a/shared/src/commonMain/kotlin/com/mihon/material/Scaffold.kt b/shared/src/commonMain/kotlin/com/mihon/material/Scaffold.kt deleted file mode 100644 index 65c11f193..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/material/Scaffold.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.mihon.material - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.material3.FabPosition -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.ScaffoldDefaults -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.TopAppBarScrollBehavior -import androidx.compose.material3.contentColorFor -import androidx.compose.material3.rememberTopAppBarState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp - -@Composable -fun Scaffold( - modifier: Modifier = Modifier, - topBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior( - rememberTopAppBarState(), - ), - topBar: @Composable (TopAppBarScrollBehavior) -> Unit = {}, - bottomBar: @Composable () -> Unit = {}, - snackbarHost: @Composable () -> Unit = {}, - floatingActionButton: @Composable () -> Unit = {}, - floatingActionButtonPosition: FabPosition = FabPosition.End, - containerColor: Color = MaterialTheme.colorScheme.background, - contentColor: Color = contentColorFor(containerColor), - contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets, - content: @Composable (PaddingValues) -> Unit, -) { - var fabHeight by remember { mutableIntStateOf(0) } - val density = LocalDensity.current - - Scaffold( - modifier = modifier.nestedScroll(topBarScrollBehavior.nestedScrollConnection), - topBar = { topBar(topBarScrollBehavior) }, - bottomBar = bottomBar, - snackbarHost = snackbarHost, - floatingActionButton = { - Box(Modifier.onSizeChanged { fabHeight = it.height }) { - floatingActionButton() - } - }, - floatingActionButtonPosition = floatingActionButtonPosition, - containerColor = containerColor, - contentColor = contentColor, - contentWindowInsets = contentWindowInsets, - ) { contentPadding -> - val paddingValues = remember(contentPadding) { - FabAwarePaddingValues(contentPadding) { - if (fabHeight == 0) 0.dp else with(density) { fabHeight.toDp() } + FabSpacing - } - } - content(paddingValues) - } -} - -private class FabAwarePaddingValues( - private val base: PaddingValues, - private val extraBottom: () -> Dp, -) : PaddingValues { - override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp = - base.calculateLeftPadding(layoutDirection) - - override fun calculateTopPadding(): Dp = base.calculateTopPadding() - - override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp = - base.calculateRightPadding(layoutDirection) - - override fun calculateBottomPadding(): Dp = base.calculateBottomPadding() + extraBottom() -} -private val FabSpacing = 16.dp diff --git a/shared/src/commonMain/kotlin/com/mihon/material/SettingsItems.kt b/shared/src/commonMain/kotlin/com/mihon/material/SettingsItems.kt deleted file mode 100644 index ab640e685..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/material/SettingsItems.kt +++ /dev/null @@ -1,474 +0,0 @@ -package com.mihon.material - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.FlowRow -import androidx.compose.foundation.layout.FlowRowScope -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -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.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyGridScope -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.material3.Checkbox -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExposedDropdownMenuAnchorType -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.RadioButton -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -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.onFocusChanged -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.hapticfeedback.HapticFeedbackType -import androidx.compose.ui.platform.LocalHapticFeedback -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.tooling.preview.PreviewLightDark -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream4.compose.whiteOutline -import com.lagradost.cloudstream4.generated.resources.Res -import com.lagradost.cloudstream4.generated.resources.arrow_downward -import com.lagradost.cloudstream4.generated.resources.arrow_upward -import com.lagradost.cloudstream4.generated.resources.preview -import com.lagradost.cloudstream4.theme.CloudStreamPreviewTheme -import com.mihon.common.preference.PreferenceData -import com.mihon.common.preference.toggle -import com.mihon.presentation.secondaryItemAlpha -import com.mihon.presentation.settings.collectAsState -import com.mihon.presentation.settings.widget.PrefsHorizontalPadding -import org.jetbrains.compose.resources.StringResource -import org.jetbrains.compose.resources.painterResource -import org.jetbrains.compose.resources.stringResource - -object SettingsItemsPaddings { - val Horizontal = 24.dp - val Vertical = 10.dp -} - -@Composable -fun HeadingItem(labelRes: StringResource) { - HeadingItem(stringResource(labelRes)) -} - -@Composable -fun HeadingItem(text: String) { - Text( - text = text, - style = MaterialTheme.typography.header, - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = SettingsItemsPaddings.Horizontal, - vertical = SettingsItemsPaddings.Vertical, - ), - ) -} - -@Composable -fun IconItem(label: String, icon: ImageVector, onClick: () -> Unit) { - BaseSettingsItem( - label = label, - widget = { - Icon( - imageVector = icon, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - ) - }, - onClick = onClick, - ) -} - -@Composable -fun SortItem(label: String, sortDescending: Boolean?, onClick: () -> Unit) { - val arrowIcon = when (sortDescending) { - true -> painterResource(Res.drawable.arrow_downward) - false -> painterResource(Res.drawable.arrow_upward) - null -> null - } - - BaseSortItem( - label = label, - icon = arrowIcon, - onClick = onClick, - ) -} - -@Composable -fun BaseSortItem(label: String, icon: Painter?, onClick: () -> Unit) { - BaseSettingsItem( - label = label, - widget = { - if (icon != null) { - Icon( - painter = icon, - contentDescription = null, - tint = MaterialTheme.colorScheme.primary, - ) - } else { - Spacer(modifier = Modifier.size(24.dp)) - } - }, - onClick = onClick, - ) -} - -@Composable -fun CheckboxItem(label: String, pref: PreferenceData) { - val checked by pref.collectAsState() - CheckboxItem( - label = label, - checked = checked, - onClick = { pref.toggle() }, - ) -} - -@Composable -fun CheckboxItem(label: String, checked: Boolean, onClick: () -> Unit) { - BaseSettingsItem( - label = label, - widget = { - Checkbox( - checked = checked, - onCheckedChange = null, - ) - }, - onClick = onClick, - ) -} - -@Composable -fun RadioItem(label: String, selected: Boolean, onClick: () -> Unit) { - BaseSettingsItem( - label = label, - widget = { - RadioButton( - selected = selected, - onClick = null, - ) - }, - onClick = onClick, - ) -} - -@Composable -fun SliderItem( - value: Int, - valueRange: IntProgression, - label: String, - onChange: (Int) -> Unit, - steps: Int = with(valueRange) { (last - first) - 1 }, - valueString: String = value.toString(), - labelStyle: TextStyle = MaterialTheme.typography.bodyMedium, - pillColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh, -) { - BaseSliderItem( - value = value, - valueRange = valueRange, - steps = steps, - title = label, - valueString = valueString, - onChange = onChange, - titleStyle = labelStyle, - pillColor = pillColor, - modifier = Modifier.padding( - horizontal = SettingsItemsPaddings.Horizontal, - vertical = SettingsItemsPaddings.Vertical, - ), - ) -} - -@Composable -fun BaseSliderItem( - value: Int, - valueRange: IntProgression, - title: String, - onChange: (Int) -> Unit, - modifier: Modifier = Modifier, - subtitle: String? = null, - steps: Int = with(valueRange) { (last - first) - 1 }, - valueString: String = value.toString(), - titleStyle: TextStyle = MaterialTheme.typography.titleLarge, - subtitleStyle: TextStyle = MaterialTheme.typography.bodySmall, - pillColor: Color = MaterialTheme.colorScheme.surfaceContainerHigh, - icon: Painter? = null, -) { - var hasFocus by remember { mutableStateOf(false) } - val haptic = LocalHapticFeedback.current - Column( - modifier = Modifier.whiteOutline(hasFocus) - .fillMaxWidth() - .then(modifier), - verticalArrangement = Arrangement.spacedBy(2.dp), - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), - ) { - if (icon != null) { - Box( - modifier = Modifier.padding(end = PrefsHorizontalPadding) - ) { - Icon( - painter = icon, - contentDescription = null, - tint = MaterialTheme.colorScheme.onBackground, - ) - } - } - Column(modifier = Modifier.weight(1f)) { - Text( - text = title, - style = titleStyle, - ) - if (subtitle != null) { - Text( - text = subtitle, - style = subtitleStyle, - modifier = Modifier.secondaryItemAlpha(), - ) - } - } - Pill( - text = valueString, - style = MaterialTheme.typography.bodyMedium, - color = pillColor, - ) - } - Slider( - modifier = Modifier.onFocusChanged { newFocus -> - hasFocus = newFocus.hasFocus - }, - value = value, - onValueChange = f@{ - if (it == value) return@f - onChange(it) - haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove) - }, - valueRange = valueRange, - steps = steps, - ) - } -} - -@Composable -@PreviewLightDark -fun SliderItemPreview() { - CloudStreamPreviewTheme { - var value by remember { mutableIntStateOf(0) } - Surface { - BaseSliderItem( - value = value, - valueRange = 0..10, - title = "Item per row", - valueString = if (value == 0) "Auto" else value.toString(), - onChange = { value = it }, - modifier = Modifier.padding( - horizontal = SettingsItemsPaddings.Horizontal, - vertical = SettingsItemsPaddings.Vertical, - ), - icon = painterResource(Res.drawable.preview) - ) - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun SelectItem( - label: String, - options: Array, - selectedIndex: Int, - onSelect: (Int) -> Unit, -) { - var expanded by remember { mutableStateOf(false) } - - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { expanded = !expanded }, - ) { - OutlinedTextField( - modifier = Modifier - .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable) - .fillMaxWidth() - .padding( - horizontal = SettingsItemsPaddings.Horizontal, - vertical = SettingsItemsPaddings.Vertical, - ), - label = { Text(text = label) }, - value = options[selectedIndex].toString(), - onValueChange = {}, - readOnly = true, - singleLine = true, - trailingIcon = { - ExposedDropdownMenuDefaults.TrailingIcon( - expanded = expanded, - ) - }, - colors = ExposedDropdownMenuDefaults.textFieldColors(), - ) - - ExposedDropdownMenu( - modifier = Modifier.exposedDropdownSize(), - expanded = expanded, - onDismissRequest = { expanded = false }, - ) { - options.forEachIndexed { index, text -> - DropdownMenuItem( - text = { Text(text.toString()) }, - onClick = { - onSelect(index) - expanded = false - }, - contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, - ) - } - } - } -} - -/* -@Composable -fun TriStateItem( - label: String, - state: TriState, - enabled: Boolean = true, - onClick: ((TriState) -> Unit)?, -) { - Row( - modifier = Modifier - .clickable( - enabled = enabled && onClick != null, - onClick = { - when (state) { - TriState.DISABLED -> onClick?.invoke(TriState.ENABLED_IS) - TriState.ENABLED_IS -> onClick?.invoke(TriState.ENABLED_NOT) - TriState.ENABLED_NOT -> onClick?.invoke(TriState.DISABLED) - } - }, - ) - .fillMaxWidth() - .padding( - horizontal = SettingsItemsPaddings.Horizontal, - vertical = SettingsItemsPaddings.Vertical, - ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.large), - ) { - val stateAlpha = if (enabled && onClick != null) 1f else DISABLED_ALPHA - - Icon( - imageVector = when (state) { - TriState.DISABLED -> MaterialSymbols.Rounded.CheckBoxOutlineBlank - TriState.ENABLED_IS -> MaterialSymbols.RoundedFilled.CheckBox - TriState.ENABLED_NOT -> MaterialSymbols.RoundedFilled.DisabledByDefault - }, - contentDescription = null, - tint = if (!enabled || state == TriState.DISABLED) { - MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = stateAlpha) - } else { - when (onClick) { - null -> MaterialTheme.colorScheme.onSurface.copy(alpha = DISABLED_ALPHA) - else -> MaterialTheme.colorScheme.primary - } - }, - ) - Text( - text = label, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = stateAlpha), - style = MaterialTheme.typography.bodyMedium, - ) - } -}*/ - -@Composable -fun TextItem(label: String, value: String, onChange: (String) -> Unit) { - OutlinedTextField( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = SettingsItemsPaddings.Horizontal, vertical = 4.dp), - label = { Text(text = label) }, - value = value, - onValueChange = onChange, - singleLine = true, - ) -} - -@Composable -fun SettingsChipRow(labelRes: StringResource, content: @Composable FlowRowScope.() -> Unit) { - Column { - HeadingItem(labelRes) - FlowRow( - modifier = Modifier.padding( - start = SettingsItemsPaddings.Horizontal, - top = 0.dp, - end = SettingsItemsPaddings.Horizontal, - bottom = SettingsItemsPaddings.Vertical, - ), - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), - content = content, - ) - } -} - -@Composable -fun SettingsIconGrid(labelRes: StringResource, content: LazyGridScope.() -> Unit) { - Column { - HeadingItem(labelRes) - LazyVerticalGrid( - columns = GridCells.Adaptive(128.dp), - modifier = Modifier.padding( - start = SettingsItemsPaddings.Horizontal, - end = SettingsItemsPaddings.Horizontal, - bottom = SettingsItemsPaddings.Vertical, - ), - verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall), - horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), - content = content, - ) - } -} - -@Composable -private fun BaseSettingsItem( - label: String, - widget: @Composable RowScope.() -> Unit, - onClick: () -> Unit, -) { - Row( - modifier = Modifier - .clickable(onClick = onClick) - .fillMaxWidth() - .padding( - horizontal = SettingsItemsPaddings.Horizontal, - vertical = SettingsItemsPaddings.Vertical, - ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(24.dp), - ) { - widget(this) - Text( - text = label, - style = MaterialTheme.typography.bodyMedium, - ) - } -} diff --git a/shared/src/commonMain/kotlin/com/mihon/material/Slider.kt b/shared/src/commonMain/kotlin/com/mihon/material/Slider.kt deleted file mode 100644 index 105c38ab8..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/material/Slider.kt +++ /dev/null @@ -1,58 +0,0 @@ -package com.mihon.material - -import androidx.annotation.IntRange -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.height -import androidx.compose.material3.Slider -import androidx.compose.material3.SliderColors -import androidx.compose.material3.SliderDefaults -import androidx.compose.material3.SliderState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.dp -import kotlin.math.roundToInt - -@Composable -fun Slider( - value: Int, - onValueChange: (Int) -> Unit, - modifier: Modifier = Modifier, - enabled: Boolean = true, - valueRange: IntProgression = 0..1, - @IntRange(from = 0) steps: Int = with(valueRange) { (last - first) - 1 }, - onValueChangeFinished: (() -> Unit)? = null, - colors: SliderColors = SliderDefaults.colors(), - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - thumb: @Composable (SliderState) -> Unit = { - SliderDefaults.Thumb( - interactionSource = interactionSource, - colors = colors, - enabled = enabled, - thumbSize = DpSize(4.0.dp, 34.0.dp) - ) - }, - track: @Composable (SliderState) -> Unit = { sliderState -> - SliderDefaults.Track( - modifier = Modifier.height(10.dp), - colors = colors, - enabled = enabled, - sliderState = sliderState - ) - }, -) { - Slider( - value = value.toFloat(), - onValueChange = { onValueChange(it.roundToInt()) }, - modifier = modifier, - enabled = enabled, - valueRange = with(valueRange) { first.toFloat()..last.toFloat() }, - steps = steps, - onValueChangeFinished = onValueChangeFinished, - colors = colors, - interactionSource = interactionSource, - thumb = thumb, - track = track, - ) -} diff --git a/shared/src/commonMain/kotlin/com/mihon/material/Typography.kt b/shared/src/commonMain/kotlin/com/mihon/material/Typography.kt deleted file mode 100644 index 789f81539..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/material/Typography.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.mihon.material - -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Typography -import androidx.compose.runtime.Composable -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight - -val Typography.header: TextStyle - @Composable - get() = bodyMedium.copy( - color = MaterialTheme.colorScheme.onSurfaceVariant, - fontWeight = FontWeight.SemiBold, - ) diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/Modifier.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/Modifier.kt deleted file mode 100644 index 7a60342e9..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/Modifier.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.mihon.presentation - -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.MaterialTheme -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.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.input.key.Key -import androidx.compose.ui.input.key.KeyEventType -import androidx.compose.ui.input.key.key -import androidx.compose.ui.input.key.onPreviewKeyEvent -import androidx.compose.ui.input.key.type -import com.mihon.material.SECONDARY_ALPHA - -@Composable -fun Modifier.selectedBackground(isSelected: Boolean): Modifier { - if (!isSelected) return this - val alpha = if (isSystemInDarkTheme()) 0.16f else 0.22f - val color = MaterialTheme.colorScheme.secondary.copy(alpha = alpha) - return this.drawBehind { drawRect(color) } -} - -fun Modifier.secondaryItemAlpha(): Modifier = this.alpha(SECONDARY_ALPHA) - -fun Modifier.clickableNoIndication( - onLongClick: (() -> Unit)? = null, - onClick: () -> Unit, -) = this.combinedClickable( - interactionSource = null, - indication = null, - onLongClick = onLongClick, - onClick = onClick, -) - -/** - * For TextField, the provided [action] will be invoked when - * physical enter key is pressed. - * - * Naturally, the TextField should be set to single line only. - */ -fun Modifier.runOnEnterKeyPressed(action: () -> Unit): Modifier = this.onPreviewKeyEvent { - when (it.key) { - Key.Enter, Key.NumPadEnter -> { - // Physical keyboards generate two event types: - // - KeyDown when the key is pressed - // - KeyUp when the key is released - if (it.type == KeyEventType.KeyDown) { - action() - true - } else { - false - } - } - - else -> false - } -} - -/** - * For TextField on AppBar, this modifier will request focus - * to the element the first time it's composed. - */ -@Composable -fun Modifier.showSoftKeyboard(show: Boolean): Modifier { - if (!show) return this - val focusRequester = remember { FocusRequester() } - var openKeyboard by rememberSaveable { mutableStateOf(show) } - LaunchedEffect(focusRequester) { - if (openKeyboard) { - focusRequester.requestFocus() - openKeyboard = false - } - } - return this.focusRequester(focusRequester) -} - -/* -/** - * For TextField, this modifier will clear focus when soft - * keyboard is hidden. - */ -@Composable -fun Modifier.clearFocusOnSoftKeyboardHide( - onFocusCleared: (() -> Unit)? = null, -): Modifier { - var isFocused by remember { mutableStateOf(false) } - var keyboardShowedSinceFocused by remember { mutableStateOf(false) } - if (isFocused) { - val imeVisible = WindowInsets.isImeVisible - val focusManager = LocalFocusManager.current - LaunchedEffect(imeVisible) { - if (imeVisible) { - keyboardShowedSinceFocused = true - } else if (keyboardShowedSinceFocused) { - focusManager.clearFocus() - onFocusCleared?.invoke() - } - } - } - - return this.onFocusChanged { - if (isFocused != it.isFocused) { - if (isFocused) { - keyboardShowedSinceFocused = false - } - isFocused = it.isFocused - } - } -} -*/ \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/Navigation.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/Navigation.kt deleted file mode 100644 index 5cdb8aada..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/Navigation.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.mihon.presentation - -import androidx.compose.runtime.ProvidableCompositionLocal -import androidx.compose.runtime.staticCompositionLocalOf - -/** - * For invoking back press to the parent activity - */ -val LocalBackPress: ProvidableCompositionLocal<(() -> Unit)?> = staticCompositionLocalOf { null } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/Preference.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/Preference.kt deleted file mode 100644 index 11ad8b984..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/Preference.kt +++ /dev/null @@ -1,198 +0,0 @@ -package com.mihon.presentation.settings - -import androidx.annotation.IntRange -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.remember -import androidx.compose.ui.graphics.painter.Painter -import com.lagradost.cloudstream4.generated.resources.Res -import com.lagradost.cloudstream4.generated.resources.none -import com.mihon.common.preference.PreferenceData -import org.jetbrains.compose.resources.stringResource - -sealed class Preference { - abstract val title: String - abstract val enabled: Boolean - - sealed class PreferenceItem : Preference() { - abstract val subtitle: String? - abstract val icon: Painter? - abstract val onValueChanged: suspend (value: T) -> R - - /** - * A basic [PreferenceItem] that only displays texts. - */ - data class TextPreference( - override val title: String, - override val subtitle: String? = null, - override val enabled: Boolean = true, - val widget: @Composable (() -> Unit)? = null, - val onClick: (() -> Unit)? = null, - override val icon: Painter? = null - ) : PreferenceItem() { - override val onValueChanged: suspend (value: String) -> Unit = {} - } - - /** - * A [PreferenceItem] that provides a two-state toggleable option. - */ - data class SwitchPreference( - val preference: PreferenceData, - override val title: String, - override val subtitle: String? = null, - override val enabled: Boolean = true, - override val onValueChanged: suspend (value: Boolean) -> Boolean = { true }, - override val icon: Painter? = null - ) : PreferenceItem() - - /** - * A [PreferenceItem] that provides a slider to select an integer number. - */ - data class SliderPreference( - val value: Int, - override val title: String, - override val subtitle: String? = null, - val valueString: String? = null, - val valueRange: IntProgression = 0..1, - @IntRange(from = 0) val steps: Int = with(valueRange) { (last - first) - 1 }, - override val enabled: Boolean = true, - override val onValueChanged: suspend (value: Int) -> Unit = {}, - override val icon: Painter? = null - ) : PreferenceItem() - - /** - * A [PreferenceItem] that displays a list of entries as a dialog. - */ - @Suppress("UNCHECKED_CAST") - data class ListPreference( - val preference: PreferenceData, - val entries: Map, - override val title: String, - override val subtitle: String? = "%s", - val subtitleProvider: @Composable (value: T, entries: Map) -> String? = - { v, e -> subtitle?.format(e[v]) }, - override val icon: Painter? = null, - override val enabled: Boolean = true, - override val onValueChanged: suspend (value: T) -> Boolean = { true }, - val iconProvider: (@Composable (key: T, value: String) -> Unit)? = null, - ) : PreferenceItem() { - internal fun internalSet(value: Any) = preference.set(value as T) - internal suspend fun internalOnValueChanged(value: Any) = onValueChanged(value as T) - - @Composable - internal fun internalSubtitleProvider(value: Any?, entries: Map) = - subtitleProvider(value as T, entries as Map) - @Composable - internal fun InternalIconProvider(key: Any?, value: String) = - iconProvider?.invoke(key as T, value) - } - - /** - * [ListPreference] but with no connection to a [PreferenceData] - */ - data class BasicListPreference( - val value: String, - val entries: Map, - override val title: String, - override val subtitle: String? = "%s", - val subtitleProvider: @Composable (value: String, entries: Map) -> String? = - { v, e -> subtitle?.format(e[v]) }, - override val icon: Painter? = null, - override val enabled: Boolean = true, - override val onValueChanged: suspend (value: String) -> Unit = {}, - ) : PreferenceItem() - - /** - * A [PreferenceItem] that displays a list of entries as a dialog. - * Multiple entries can be selected at the same time. - */ - @Suppress("UNCHECKED_CAST") - data class MultiSelectListPreference( - val preference: PreferenceData>, - val entries: Map, - override val title: String, - override val subtitle: String? = "%s", - val subtitleProvider: @Composable (value: Set, entries: Map) -> String? = - { v, e -> - val combined = remember(v, e) { - v.mapNotNull { e[it] } - .joinToString() - .takeUnless { it.isBlank() } - } - ?: stringResource(Res.string.none) - subtitle?.format(combined) - }, - override val icon: Painter? = null, - override val enabled: Boolean = true, - override val onValueChanged: suspend (value: Set) -> Boolean = { true }, - ) : PreferenceItem, Boolean>() { - internal fun internalSet(value: Set) = preference.set(value as Set) - internal suspend fun internalOnValueChanged(value: Set) = onValueChanged(value as Set) - - @Composable - internal fun internalSubtitleProvider(value: Set, entries: Map) = - subtitleProvider(value as Set, entries as Map) - } - - /** - * A [PreferenceItem] that shows a EditText in the dialog. - */ - data class EditTextPreference( - val preference: PreferenceData, - override val title: String, - override val subtitle: String? = "%s", - override val enabled: Boolean = true, - override val onValueChanged: suspend (value: String) -> Boolean = { true }, - ) : PreferenceItem() { - override val icon: Painter? = null - } - - /** - * A [PreferenceItem] for individual tracker. - */ - /*data class TrackerPreference( - val tracker: Tracker, - val login: () -> Unit, - val logout: () -> Unit, - ) : PreferenceItem() { - override val title: String = "" - override val enabled: Boolean = true - override val subtitle: String? = null - override val icon: ImageVector? = null - override val onValueChanged: suspend (value: String) -> Unit = {} - }*/ - - data class InfoPreference( - override val title: String, - ) : PreferenceItem() { - override val enabled: Boolean = true - override val subtitle: String? = null - override val icon: Painter? = null - override val onValueChanged: suspend (value: String) -> Unit = {} - } - - data class CustomPreference( - override val title: String, - val content: @Composable () -> Unit, - ) : PreferenceItem() { - override val enabled: Boolean = true - override val subtitle: String? = null - override val icon: Painter? = null - override val onValueChanged: suspend (value: Unit) -> Unit = {} - } - } - - data class PreferenceGroup( - override val title: String, - override val enabled: Boolean = true, - - val preferenceItems: List>, - ) : Preference() -} - -@Composable -fun PreferenceData.collectAsState(): State { - val flow = remember(this) { changes() } - return flow.collectAsState(initial = get()) -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceItem.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceItem.kt deleted file mode 100644 index 151f3cb69..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceItem.kt +++ /dev/null @@ -1,188 +0,0 @@ -package com.mihon.presentation.settings - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.expandVertically -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkVertically -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.compositionLocalOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.structuralEqualityPolicy -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.mihon.presentation.settings.widget.EditTextPreferenceWidget -import com.mihon.presentation.settings.widget.InfoWidget -import com.mihon.presentation.settings.widget.ListPreferenceWidget -import com.mihon.presentation.settings.widget.MultiSelectListPreferenceWidget -import com.mihon.presentation.settings.widget.PrefsHorizontalPadding -import com.mihon.presentation.settings.widget.PrefsVerticalPadding -import com.mihon.presentation.settings.widget.SwitchPreferenceWidget -import com.mihon.presentation.settings.widget.TextPreferenceWidget -import com.mihon.presentation.settings.widget.TitleFontSize -import kotlinx.coroutines.launch -import com.mihon.material.BaseSliderItem - -val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false } -val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) { 56.dp } - -@Composable -fun StatusWrapper( - item: Preference.PreferenceItem<*, *>, - highlightKey: String?, - content: @Composable () -> Unit, -) { - val enabled = item.enabled - val highlighted = item.title == highlightKey - AnimatedVisibility( - visible = enabled, - enter = expandVertically() + fadeIn(), - exit = shrinkVertically() + fadeOut(), - content = { - CompositionLocalProvider( - LocalPreferenceHighlighted provides highlighted, - content = content, - ) - }, - ) -} - -@Composable -internal fun PreferenceItem( - item: Preference.PreferenceItem<*, *>, - highlightKey: String?, -) { - val scope = rememberCoroutineScope() - StatusWrapper( - item = item, - highlightKey = highlightKey, - ) { - when (item) { - is Preference.PreferenceItem.SwitchPreference -> { - val value by item.preference.collectAsState() - SwitchPreferenceWidget( - title = item.title, - subtitle = item.subtitle, - icon = item.icon, - checked = value, - onCheckedChanged = { newValue -> - scope.launch { - if (item.onValueChanged(newValue)) { - item.preference.set(newValue) - } - } - }, - ) - } - is Preference.PreferenceItem.SliderPreference -> { - BaseSliderItem( - value = item.value, - valueRange = item.valueRange, - steps = item.steps, - title = item.title, - subtitle = item.subtitle, - valueString = item.valueString.takeUnless { it.isNullOrEmpty() } ?: item.value.toString(), - onChange = { - scope.launch { - item.onValueChanged(it) - } - }, - titleStyle = MaterialTheme.typography.titleLarge.copy(fontSize = TitleFontSize), - modifier = Modifier.padding( - horizontal = PrefsHorizontalPadding, - vertical = PrefsVerticalPadding, - ), - icon = item.icon - ) - } - is Preference.PreferenceItem.ListPreference<*> -> { - val value by item.preference.collectAsState() - ListPreferenceWidget( - value = value, - title = item.title, - subtitle = item.internalSubtitleProvider(value, item.entries), - icon = item.icon, - entries = item.entries, - iconProvider = item::InternalIconProvider, - onValueChange = { newValue -> - scope.launch { - if (item.internalOnValueChanged(newValue!!)) { - item.internalSet(newValue) - } - } - }, - ) - } - is Preference.PreferenceItem.BasicListPreference -> { - ListPreferenceWidget( - value = item.value, - title = item.title, - subtitle = item.subtitleProvider(item.value, item.entries), - icon = item.icon, - entries = item.entries, - onValueChange = { scope.launch { item.onValueChanged(it) } }, - ) - } - is Preference.PreferenceItem.MultiSelectListPreference<*> -> { - val values by item.preference.collectAsState() - MultiSelectListPreferenceWidget( - values = values, - title = item.title, - subtitle = item.internalSubtitleProvider(values, item.entries), - icon = item.icon, - entries = item.entries, - onValuesChange = { newValues -> - scope.launch { - if (item.internalOnValueChanged(newValues)) { - item.internalSet(newValues) - } - } - }, - ) - } - is Preference.PreferenceItem.TextPreference -> { - TextPreferenceWidget( - title = item.title, - subtitle = item.subtitle, - icon = item.icon, - widget = item.widget, - onPreferenceClick = item.onClick, - ) - } - is Preference.PreferenceItem.EditTextPreference -> { - val values by item.preference.collectAsState() - EditTextPreferenceWidget( - title = item.title, - subtitle = item.subtitle, - icon = item.icon, - value = values, - onConfirm = { - val accepted = item.onValueChanged(it) - if (accepted) item.preference.set(it) - accepted - }, - ) - } - /*is Preference.PreferenceItem.TrackerPreference -> { - val isLoggedIn by item.tracker.let { tracker -> - tracker.isLoggedInFlow.collectAsState(tracker.isLoggedIn) - } - TrackingPreferenceWidget( - tracker = item.tracker, - isLoggedIn = isLoggedIn, - onClick = { if (isLoggedIn) item.logout() else item.login() }, - ) - }*/ - is Preference.PreferenceItem.InfoPreference -> { - InfoWidget(text = item.title) - } - is Preference.PreferenceItem.CustomPreference -> { - item.content() - } - } - } -} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScaffold.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScaffold.kt deleted file mode 100644 index 4f97d9f2b..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScaffold.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.mihon.presentation.settings - -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -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 com.mihon.material.AppBar -import com.mihon.material.Scaffold - -@Composable -fun PreferenceScaffold( - title: String, - actions: @Composable RowScope.() -> Unit = {}, - onBackPressed: (() -> Unit)? = null, - itemsProvider: @Composable () -> List, -) { - val (top, bottom) = remember { FocusRequester.createRefs() } - - Scaffold( - // TODO @WindowInsets remove this when we have converted everything to compose - contentWindowInsets = WindowInsets(0, 0, 0, 0), - topBar = { - AppBar( - modifier = Modifier.focusRequester(top).focusProperties { - down = bottom - }, - title = title, - navigateUp = onBackPressed, - actions = actions, - scrollBehavior = it, - ) - }, - content = { contentPadding -> - PreferenceScreen( - modifier = Modifier.focusRequester(bottom).focusProperties { - up = top - }, - items = itemsProvider(), - contentPadding = contentPadding, - ) - }, - ) -} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScreen.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScreen.kt deleted file mode 100644 index 0e99aa9a3..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/PreferenceScreen.kt +++ /dev/null @@ -1,96 +0,0 @@ -package com.mihon.presentation.settings - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastForEachIndexed -import com.mihon.presentation.settings.widget.PreferenceGroupHeader -import kotlinx.coroutines.delay -import kotlin.time.Duration.Companion.seconds - -/** - * Preference Screen composable which contains a list of [Preference] items - * @param items [Preference] items which should be displayed on the preference screen. An item can be a single [PreferenceItem] or a group ([Preference.PreferenceGroup]) - * @param modifier [Modifier] to be applied to the preferenceScreen layout - */ -@Composable -fun PreferenceScreen( - items: List, - modifier: Modifier = Modifier, - contentPadding: PaddingValues = PaddingValues(0.dp), -) { - val state = rememberLazyListState() - val highlightKey = SearchableSettings.highlightKey - if (highlightKey != null) { - LaunchedEffect(Unit) { - val i = items.findHighlightedIndex(highlightKey) - if (i >= 0) { - delay(0.5.seconds) - state.animateScrollToItem(i) - } - SearchableSettings.highlightKey = null - } - } - - LazyColumn( - modifier = modifier, - state = state, - contentPadding = contentPadding, - ) { - items.fastForEachIndexed { i, preference -> - when (preference) { - // Create Preference Group - is Preference.PreferenceGroup -> { - if (!preference.enabled) return@fastForEachIndexed - - item { - Column { - PreferenceGroupHeader(title = preference.title) - } - } - items(preference.preferenceItems) { item -> - PreferenceItem( - item = item, - highlightKey = highlightKey, - ) - } - item { - if (i < items.lastIndex) { - Spacer(modifier = Modifier.height(12.dp)) - } - } - } - - // Create Preference Item - is Preference.PreferenceItem<*, *> -> item { - PreferenceItem( - item = preference, - highlightKey = highlightKey, - ) - } - } - } - } -} - -private fun List.findHighlightedIndex(highlightKey: String): Int { - return flatMap { - if (it is Preference.PreferenceGroup) { - buildList { - add(null) // Header - addAll(it.preferenceItems.map { groupItem -> groupItem.title }) - add(null) // Spacer - } - } else { - listOf(it.title) - } - }.indexOfFirst { it == highlightKey } -} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/Search.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/Search.kt deleted file mode 100644 index d221fc801..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/Search.kt +++ /dev/null @@ -1,158 +0,0 @@ -package com.mihon.presentation.settings - -import androidx.compose.animation.Crossfade -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.retain.retain -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream4.generated.resources.Res -import com.lagradost.cloudstream4.generated.resources.no_results_found -import com.mihon.presentation.secondaryItemAlpha -import com.mihon.presentation.settings.widget.TextPreferenceWidget -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.PersistentList -import kotlinx.collections.immutable.toPersistentList -import org.jetbrains.compose.resources.stringResource - -private fun getLocalizedBreadcrumb(path: String, node: String?, isLtr: Boolean): String { - return if (node == null) { - path - } else { - if (isLtr) { - // This locale reads left to right. - "$path > $node" - } else { - // This locale reads right to left. - "$node < $path" - } - } -} - -data class SettingsData( - val title: String, - val contents: List, - val navigation: T, -) - -data class SearchResultItem( - val title: String, - val breadcrumbs: String, - val highlightKey: String, - val navigation: T, -) - -@Composable -fun SettingSearchResults( - searchKey: String, - onItemClick: (SearchResultItem) -> Unit, - items: ImmutableList>, - nestedScrollConnection: NestedScrollConnection, - empty: @Composable () -> Unit -) { - val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr - - // We want to retain the results as backpress should not re-calc it - var result by retain>?>> { - mutableStateOf(null) - } - - LaunchedEffect(searchKey) { - if (searchKey.isEmpty()) { - result = null - return@LaunchedEffect - } - result = items.asSequence() - .flatMap { settingsData -> - settingsData.contents.asSequence() - // Only search from enabled prefs and one with valid title - .filter { it.enabled && it.title.isNotBlank() } - // Flatten items contained inside *enabled* PreferenceGroup - .flatMap { p -> - when (p) { - is Preference.PreferenceGroup -> { - if (p.enabled) { - p.preferenceItems.asSequence() - .filter { it.enabled && it.title.isNotBlank() } - .map { p.title to it } - } else { - emptySequence() - } - } - - is Preference.PreferenceItem<*, *> -> sequenceOf(null to p) - } - } - // Don't show info preference - .filterNot { it.second is Preference.PreferenceItem.InfoPreference } - // Filter by search query - .filter { (_, p) -> - val inTitle = p.title.contains(searchKey, true) - val inSummary = p.subtitle?.contains(searchKey, true) ?: false - inTitle || inSummary // TODO filter out %s ? This is a bug-ish in mihon - } - // Map result data - .map { (categoryTitle, p) -> - SearchResultItem( - navigation = settingsData.navigation, - title = p.title, - breadcrumbs = getLocalizedBreadcrumb( - path = settingsData.title, - node = categoryTitle, - isLtr = isLtr, - ), - highlightKey = p.title, - ) - } - } - .take(10) // Just take top 10 result for quicker result - .toPersistentList() - } - - Crossfade( - targetState = result, - label = "results", - ) { - when { - it == null -> empty() - - it.isEmpty() -> { - Text( - text = stringResource(Res.string.no_results_found), - modifier = Modifier.secondaryItemAlpha().fillMaxWidth().padding(20.dp), - textAlign = TextAlign.Center - ) - } - - else -> { - Column( - modifier = Modifier - .nestedScroll(nestedScrollConnection) - ) { - it.forEach { item -> - TextPreferenceWidget( - title = item.title, - subtitle = item.breadcrumbs, - onPreferenceClick = { - onItemClick(item) - } - ) - } - } - } - } - } -} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/SearchableSettings.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/SearchableSettings.kt deleted file mode 100644 index c114f0dd0..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/SearchableSettings.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.mihon.presentation.settings - -import androidx.compose.foundation.layout.RowScope -import androidx.compose.runtime.Composable -import androidx.compose.runtime.ReadOnlyComposable -import com.lagradost.cloudstream4.compose.Screen -import com.mihon.presentation.LocalBackPress - -interface SearchableSettings : Screen { - @Composable - @ReadOnlyComposable - fun getTitleRes(): String - - @Composable - fun getPreferences(): List - - @Composable - fun RowScope.AppBarAction() { - } - - @Composable - override fun Content() { - val handleBack = LocalBackPress.current - PreferenceScaffold( - title = getTitleRes(), - onBackPressed = if (handleBack != null) handleBack::invoke else null, - actions = { AppBarAction() }, - itemsProvider = { getPreferences() }, - ) - } - - companion object { - // HACK: for the background blipping thingy. - // The title of the target PreferenceItem - // Set before showing the destination screen and reset after - // See BasePreferenceWidget.highlightBackground - var highlightKey: String? = null - } -} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/BasePreferenceWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/BasePreferenceWidget.kt deleted file mode 100644 index e103a1440..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/BasePreferenceWidget.kt +++ /dev/null @@ -1,127 +0,0 @@ -package com.mihon.presentation.settings.widget - -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.StartOffset -import androidx.compose.animation.core.StartOffsetType -import androidx.compose.animation.core.repeatable -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.sizeIn -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.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.lagradost.cloudstream4.compose.focusOutline -import com.mihon.presentation.settings.LocalPreferenceHighlighted -import com.mihon.presentation.settings.LocalPreferenceMinHeight -import kotlinx.coroutines.delay -import kotlin.time.Duration.Companion.seconds - -@Composable -internal fun BasePreferenceWidget( - modifier: Modifier = Modifier, - title: String? = null, - subcomponent: @Composable (ColumnScope.() -> Unit)? = null, - icon: @Composable (() -> Unit)? = null, - onClick: (() -> Unit)? = null, - widget: @Composable (() -> Unit)? = null, -) { - val highlighted = LocalPreferenceHighlighted.current - val minHeight = LocalPreferenceMinHeight.current - Row( - modifier = modifier - .highlightBackground(highlighted) - .focusOutline() - .sizeIn(minHeight = minHeight) - .clickable(enabled = onClick != null, onClick = { onClick?.invoke() }) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - ) { - if (icon != null) { - Box( - modifier = Modifier.padding(start = PrefsHorizontalPadding, end = 8.dp), - content = { icon() }, - ) - } - Column( - modifier = Modifier - .weight(1f) - .padding(vertical = PrefsVerticalPadding), - ) { - if (!title.isNullOrBlank()) { - Text( - modifier = Modifier.padding(horizontal = PrefsHorizontalPadding), - text = title, - overflow = TextOverflow.Ellipsis, - maxLines = 2, - style = MaterialTheme.typography.titleLarge, - fontSize = TitleFontSize, - ) - } - subcomponent?.invoke(this) - } - if (widget != null) { - Box( - modifier = Modifier.padding(end = PrefsHorizontalPadding), - content = { widget() }, - ) - } - } -} - -@Composable -internal fun Modifier.highlightBackground(highlighted: Boolean): Modifier { - var highlightFlag by remember { mutableStateOf(false) } - LaunchedEffect(Unit) { - if (highlighted) { - highlightFlag = true - delay(3.seconds) - highlightFlag = false - } - } - val highlight by animateColorAsState( - targetValue = if (highlightFlag) { - MaterialTheme.colorScheme.onBackground.copy(alpha = .12f) - } else { - Color.Transparent - }, - animationSpec = if (highlightFlag) { - repeatable( - iterations = 5, - animation = tween(durationMillis = 200), - repeatMode = RepeatMode.Reverse, - initialStartOffset = StartOffset( - offsetMillis = 600, - offsetType = StartOffsetType.Delay, - ), - ) - } else { - tween(200) - }, - label = "highlight", - ) - return this.background(color = highlight) -} - -internal val TrailingWidgetBuffer = 16.dp -internal val PrefsHorizontalPadding = 16.dp -internal val PrefsVerticalPadding = 16.dp -internal val TitleFontSize = 16.sp diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/EditTextPreferenceWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/EditTextPreferenceWidget.kt deleted file mode 100644 index 137318928..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/EditTextPreferenceWidget.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.mihon.presentation.settings.widget - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.window.DialogProperties -import com.lagradost.cloudstream4.generated.resources.Res -import com.lagradost.cloudstream4.generated.resources.cancel -import com.lagradost.cloudstream4.generated.resources.error -import com.lagradost.cloudstream4.generated.resources.ok -import kotlinx.coroutines.launch -import org.jetbrains.compose.resources.painterResource -import org.jetbrains.compose.resources.stringResource - -@Composable -fun EditTextPreferenceWidget( - title: String, - subtitle: String?, - icon: Painter?, - value: String, - onConfirm: suspend (String) -> Boolean, -) { - var isDialogShown by remember { mutableStateOf(false) } - - TextPreferenceWidget( - title = title, - subtitle = subtitle?.format(value), - icon = icon, - onPreferenceClick = { isDialogShown = true }, - ) - - if (isDialogShown) { - val scope = rememberCoroutineScope() - val onDismissRequest = { isDialogShown = false } - var textFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) { - mutableStateOf(TextFieldValue(value)) - } - AlertDialog( - onDismissRequest = onDismissRequest, - title = { Text(text = title) }, - text = { - OutlinedTextField( - value = textFieldValue, - onValueChange = { textFieldValue = it }, - trailingIcon = { - if (textFieldValue.text.isBlank()) { - Icon(painter = painterResource(Res.drawable.error), contentDescription = null) - } else { - IconButton(onClick = { textFieldValue = TextFieldValue("") }) { - Icon(painter = painterResource(Res.drawable.cancel), contentDescription = null) - } - } - }, - isError = textFieldValue.text.isBlank(), - singleLine = true, - modifier = Modifier.fillMaxWidth(), - ) - }, - properties = DialogProperties( - usePlatformDefaultWidth = true, - ), - confirmButton = { - TextButton( - enabled = textFieldValue.text != value && textFieldValue.text.isNotBlank(), - onClick = { - scope.launch { - if (onConfirm(textFieldValue.text)) { - onDismissRequest() - } - } - }, - ) { - Text(text = stringResource(Res.string.ok)) - } - }, - dismissButton = { - TextButton(onClick = onDismissRequest) { - Text(text = stringResource(Res.string.cancel)) - } - }, - ) - } -} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/InfoWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/InfoWidget.kt deleted file mode 100644 index 3382ebda5..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/InfoWidget.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.mihon.presentation.settings.widget - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.PreviewLightDark -import com.lagradost.cloudstream4.generated.resources.Res -import com.lagradost.cloudstream4.generated.resources.info -import com.lagradost.cloudstream4.generated.resources.lorem_content -import com.lagradost.cloudstream4.generated.resources.lorem_title -import com.lagradost.cloudstream4.theme.CloudStreamPreviewTheme -import org.jetbrains.compose.resources.painterResource -import org.jetbrains.compose.resources.stringResource -import com.mihon.presentation.secondaryItemAlpha -import com.mihon.material.padding - -@Composable -internal fun InfoWidget(text: String) { - Column( - modifier = Modifier - .padding( - horizontal = PrefsHorizontalPadding, - vertical = MaterialTheme.padding.medium, - ) - .secondaryItemAlpha(), - verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium), - ) { - Icon( - painter = painterResource(Res.drawable.info), - contentDescription = null, - ) - Text( - text = text, - style = MaterialTheme.typography.bodySmall, - ) - } -} - -@PreviewLightDark -@Composable -private fun InfoWidgetPreview() { - CloudStreamPreviewTheme { - Surface { - InfoWidget(text = stringResource(Res.string.lorem_content)) - } - } -} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/ListPreferenceWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/ListPreferenceWidget.kt deleted file mode 100644 index 6a573e7e8..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/ListPreferenceWidget.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.mihon.presentation.settings.widget - -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.graphics.painter.Painter -import com.lagradost.cloudstream4.compose.SingleSelectDialog -import com.lagradost.cloudstream4.generated.resources.Res -import com.lagradost.cloudstream4.generated.resources.cancel -import com.lagradost.cloudstream4.generated.resources.ok -import org.jetbrains.compose.resources.stringResource - -@Composable -fun ListPreferenceWidget( - value: T, - title: String, - subtitle: String?, - icon: Painter?, - entries: Map, - onValueChange: (T) -> Unit, - iconProvider: (@Composable (key: T, value: String) -> Unit)? = null -) { - var isDialogShown by remember { mutableStateOf(false) } - - TextPreferenceWidget( - title = title, - subtitle = subtitle, - icon = icon, - onPreferenceClick = { isDialogShown = true }, - ) - - if (isDialogShown) { - SingleSelectDialog( - dismiss = { isDialogShown = false }, - title = title, - entries = entries, - selectedKey = value, - confirm = { key -> - onValueChange(key) - isDialogShown = false - }, - confirmText = stringResource(Res.string.ok), - dismissText = stringResource(Res.string.cancel), - iconProvider = iconProvider - ) - /*AlertDialog( - onDismissRequest = { isDialogShown = false }, - title = { Text(text = title) }, - text = { - Box { - val state = rememberLazyListState() - LazyColumn(state = state) { - entries.forEach { current -> - val isSelected = value == current.key - item { - DialogRow( - label = current.value, - isSelected = isSelected, - onSelected = { - onValueChange(current.key!!) - isDialogShown = false - }, - ) - } - } - } - if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter)) - if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter)) - } - }, - confirmButton = { - TextButton(onClick = { isDialogShown = false }) { - Text(text = stringResource(Res.string.cancel)) - } - }, - )*/ - } -} -/* -@Composable -private fun DialogRow( - label: String, - isSelected: Boolean, - onSelected: () -> Unit, -) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .clip(MaterialTheme.shapes.small) - .selectable( - selected = isSelected, - onClick = { if (!isSelected) onSelected() }, - ) - .fillMaxWidth() - .minimumInteractiveComponentSize(), - ) { - RadioButton( - selected = isSelected, - onClick = null, - ) - Text( - text = label, - style = MaterialTheme.typography.bodyLarge.merge(), - modifier = Modifier.padding(start = 24.dp), - ) - } -} -*/ \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/MultiSelectListPreferenceWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/MultiSelectListPreferenceWidget.kt deleted file mode 100644 index c0a2d735f..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/MultiSelectListPreferenceWidget.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.mihon.presentation.settings.widget - -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.graphics.painter.Painter -import androidx.compose.ui.window.DialogProperties -import com.lagradost.cloudstream4.compose.MultiSelectDialog -import com.lagradost.cloudstream4.generated.resources.Res -import com.lagradost.cloudstream4.generated.resources.cancel -import com.lagradost.cloudstream4.generated.resources.ok -import org.jetbrains.compose.resources.stringResource - -@Composable -fun MultiSelectListPreferenceWidget( - values: Set, - title: String, - subtitle: String?, - icon: Painter?, - entries: Map, - onValuesChange: (Set) -> Unit, -) { - var isDialogShown by remember { mutableStateOf(false) } - - TextPreferenceWidget( - title = title, - subtitle = subtitle, - icon = icon, - onPreferenceClick = { isDialogShown = true }, - ) - - if (isDialogShown) { - MultiSelectDialog( - title = title, - entries = entries, - properties = DialogProperties( - usePlatformDefaultWidth = true, - ), - confirmText = stringResource(Res.string.ok), - confirm = { selection -> - onValuesChange(selection) - isDialogShown = false - }, - dismissText = stringResource(Res.string.cancel), - dismiss = { - isDialogShown = false - }, - selectedKeys = values, - ) - - /* - AlertDialog( - onDismissRequest = { isDialogShown = false }, - title = { Text(text = title) }, - text = { - LazyColumn { - entries.forEach { current -> - item { - val isSelected = selected.contains(current.key) - LabeledCheckbox( - label = current.value, - checked = isSelected, - onCheckedChange = { - if (it) { - selected.add(current.key) - } else { - selected.remove(current.key) - } - }, - ) - } - } - } - }, - properties = DialogProperties( - usePlatformDefaultWidth = true, - ), - confirmButton = { - TextButton( - onClick = { - onValuesChange(selected.toMutableSet()) - isDialogShown = false - }, - ) { - Text(text = stringResource(Res.string.ok)) - } - }, - dismissButton = { - TextButton(onClick = { isDialogShown = false }) { - Text(text = stringResource(Res.string.cancel)) - } - }, - )*/ - } -} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/PreferenceGroupHeader.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/PreferenceGroupHeader.kt deleted file mode 100644 index b7d1197a9..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/PreferenceGroupHeader.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.mihon.presentation.settings.widget - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp - -@Composable -fun PreferenceGroupHeader(title: String) { - Box( - contentAlignment = Alignment.CenterStart, - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 8.dp, top = 14.dp), - ) { - Text( - text = title, - color = MaterialTheme.colorScheme.onBackground, - modifier = Modifier.padding(horizontal = PrefsHorizontalPadding), - style = MaterialTheme.typography.bodyMedium, - ) - } -} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/SwitchPreferenceWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/SwitchPreferenceWidget.kt deleted file mode 100644 index 2744cdcdc..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/SwitchPreferenceWidget.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.mihon.presentation.settings.widget - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Surface -import androidx.compose.material3.Switch -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.tooling.preview.PreviewLightDark -import com.lagradost.cloudstream4.generated.resources.Res -import com.lagradost.cloudstream4.generated.resources.preview -import com.lagradost.cloudstream4.theme.CloudStreamPreviewTheme -import org.jetbrains.compose.resources.painterResource - -@Composable -fun SwitchPreferenceWidget( - modifier: Modifier = Modifier, - title: String, - subtitle: String? = null, - icon: Painter? = null, - checked: Boolean = false, - onCheckedChanged: (Boolean) -> Unit, -) { - TextPreferenceWidget( - modifier = modifier, - title = title, - subtitle = subtitle, - icon = icon, - widget = { - Switch( - checked = checked, - onCheckedChange = null, - modifier = Modifier.padding(start = TrailingWidgetBuffer), - ) - }, - onPreferenceClick = { onCheckedChanged(!checked) }, - ) -} - -@PreviewLightDark -@Composable -private fun SwitchPreferenceWidgetPreview() { - CloudStreamPreviewTheme { - Surface { - Column { - SwitchPreferenceWidget( - title = "Text preference with icon", - subtitle = "Text preference summary", - icon = painterResource(Res.drawable.preview) , - checked = true, - onCheckedChanged = {}, - ) - SwitchPreferenceWidget( - title = "Text preference", - subtitle = "Text preference summary", - checked = false, - onCheckedChanged = {}, - ) - SwitchPreferenceWidget( - title = "Text preference no summary", - checked = false, - onCheckedChanged = {}, - ) - SwitchPreferenceWidget( - title = "Another text preference no summary", - checked = false, - onCheckedChanged = {}, - ) - } - } - } -} diff --git a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/TextPreferenceWidget.kt b/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/TextPreferenceWidget.kt deleted file mode 100644 index 91c19cd6a..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/widget/TextPreferenceWidget.kt +++ /dev/null @@ -1,86 +0,0 @@ -package com.mihon.presentation.settings.widget - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.tooling.preview.PreviewLightDark -import androidx.compose.ui.unit.dp -import com.lagradost.cloudstream4.generated.resources.Res -import com.lagradost.cloudstream4.generated.resources.preview -import com.lagradost.cloudstream4.theme.CloudStreamPreviewTheme -import org.jetbrains.compose.resources.painterResource -import com.mihon.presentation.secondaryItemAlpha - -@Composable -fun TextPreferenceWidget( - modifier: Modifier = Modifier, - title: String? = null, - subtitle: String? = null, - icon: Painter? = null, - iconTint: Color = MaterialTheme.colorScheme.onBackground, - widget: @Composable (() -> Unit)? = null, - onPreferenceClick: (() -> Unit)? = null, -) { - BasePreferenceWidget( - modifier = modifier, - title = title, - subcomponent = if (!subtitle.isNullOrBlank()) { - { - Text( - text = subtitle, - modifier = Modifier - .padding(horizontal = PrefsHorizontalPadding) - .secondaryItemAlpha(), - style = MaterialTheme.typography.bodyMedium, - maxLines = 10, - ) - } - } else { - null - }, - icon = if (icon != null) { - { - Icon( - painter = icon, - modifier = Modifier.size(24.dp), - tint = iconTint, - contentDescription = null, - ) - } - } else { - null - }, - onClick = onPreferenceClick, - widget = widget, - ) -} - -@PreviewLightDark -@Composable -private fun TextPreferenceWidgetPreview() { - CloudStreamPreviewTheme { - Surface { - Column { - TextPreferenceWidget( - title = "Text preference with icon", - subtitle = "Text preference summary", - icon = painterResource(Res.drawable.preview), - onPreferenceClick = {}, - ) - TextPreferenceWidget( - title = "Text preference", - subtitle = "Text preference summary", - onPreferenceClick = {}, - ) - } - } - } -} diff --git a/shared/src/commonMain/kotlin/com/mihon/readme.md b/shared/src/commonMain/kotlin/com/mihon/readme.md deleted file mode 100644 index 2984058d0..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/readme.md +++ /dev/null @@ -1,4 +0,0 @@ -Mihon (tachiyomi) has a great codebase for working with compose. -Because they do not provide these features as a lib, we are forced to fork each file we want instead. - -https://github.com/mihonapp/mihon \ No newline at end of file diff --git a/shared/src/jvmMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.jvm.kt b/shared/src/jvmMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.jvm.kt deleted file mode 100644 index cca2d70d7..000000000 --- a/shared/src/jvmMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.jvm.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.lagradost.cloudstream4 - -import androidx.compose.runtime.Composable -import com.mihon.common.preference.InMemoryPreferenceStore - -/** As this is mostly for testing so far, we can assume we have a shared global for settings */ -internal val settings = AppSettings() - -@Composable -actual fun rememberAppSettings(): AppSettings { - return settings -} - -fun AppSettings() : AppSettings { - return AppSettings(preferences = InMemoryPreferenceStore()) -} diff --git a/shared/src/jvmMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.jvm.kt b/shared/src/jvmMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.jvm.kt deleted file mode 100644 index dc9f4f1ae..000000000 --- a/shared/src/jvmMain/kotlin/com/lagradost/cloudstream4/theme/DeviceTheme.jvm.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.lagradost.cloudstream4.theme - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.ui.graphics.Color - -internal actual object DeviceTheme { - @Composable - @ReadOnlyComposable - actual fun resolveDynamicTheme(): CloudStreamColorScheme = darkScheme() - - @Composable - @ReadOnlyComposable - actual fun resolveDynamicPrimaryColor(): Color = CloudStreamPrimaryColor.NORMAL.color - - @Composable - @ReadOnlyComposable - actual fun resolveDynamicSecondaryColor(): Color = CloudStreamPrimaryColor.NORMAL.color -} \ No newline at end of file diff --git a/shared/src/readme.md b/shared/src/readme.md deleted file mode 100644 index 8b37a84a0..000000000 --- a/shared/src/readme.md +++ /dev/null @@ -1,11 +0,0 @@ -https://kotlinlang.org/docs/multiplatform/compose-multiplatform-create-first-app.html#examine-the-project-structure - -`jvmMain` contains source files for the desktop target, which uses Kotlin/JVM. - -`androidMain` contains Android source files and targets Kotlin/JVM. - -`iosMain` contains Kotlin code for iOS and targets Kotlin/Native. - -`jsMain` contains JavaScript-specific Kotlin code and targets Kotlin/JS. - -`wasmJsMain` contains Wasm-specific Kotlin code and targets Kotlin/Wasm. \ No newline at end of file