diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c714368ee..0feb866be 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()) @@ -278,8 +276,6 @@ dependencies { implementation(libs.jackson.module.kotlin) // JSON Parser implementation(libs.zipline) - // Temp/deprecated; will be removed once extensions have time to migrate from using it - implementation("com.google.code.gson:gson:2.11.0") // Deprecated; will be removed once extensions have time to migrate from using it implementation("me.xdrop:fuzzywuzzy:1.4.0") @@ -290,12 +286,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..a9cd9c01e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt @@ -113,7 +113,7 @@ class CloudStreamApp : Application(), SingletonImageLoader.Factory { get() = _context?.get() private set(value) { _context = WeakReference(value) - setContext(value) + setContext(WeakReference(value)) } fun getKeyClass(path: String, valueType: Class): T? { @@ -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/download/DownloadViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt index 19c006b28..0d35d5670 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt @@ -10,7 +10,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.api.Log -import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.isEpisodeBased import com.lagradost.cloudstream3.mvvm.Resource @@ -37,7 +36,6 @@ import com.lagradost.cloudstream3.utils.ResourceLiveData import com.lagradost.cloudstream3.utils.downloader.DownloadObjects import com.lagradost.cloudstream3.utils.downloader.DownloadQueueManager import com.lagradost.cloudstream3.utils.downloader.VideoDownloadManager.deleteFilesAndUpdateSettings -import com.lagradost.cloudstream3.utils.downloader.VideoDownloadManager.downloadDeleteEvent import com.lagradost.cloudstream3.utils.downloader.VideoDownloadManager.getDownloadFileInfo import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -69,17 +67,6 @@ class DownloadViewModel : ViewModel() { private val _selectedItemIds = ConsistentLiveData?>(null) val selectedItemIds: LiveData?> = _selectedItemIds - init { - // Keep the Downloads list in sync when a download is deleted/cancelled from - // anywhere in the app (result page button, queue, notification, etc.). See - // onDownloadDeleted for the rationale (issue #1227). - downloadDeleteEvent += ::onDownloadDeleted - } - - override fun onCleared() { - downloadDeleteEvent -= ::onDownloadDeleted - super.onCleared() - } fun cancelSelection() { updateSelectedItems { null } @@ -402,18 +389,6 @@ class DownloadViewModel : ViewModel() { postChildren(_childCards.success?.filter { it.data.id !in idsToRemove }) } - /** - * Refreshes the Downloads screen in real time when a download is deleted/cancelled. - */ - private fun onDownloadDeleted(id: Int) { - // Keep multi-select state consistent: forget the removed id if it was selected. - updateSelectedItems { it?.minus(id) } - - val context = CloudStreamApp.context ?: return - updateHeaderList(context) - postChildren(_childCards.success?.filterNot { it.data.id == id }) - } - private fun updateStorageStats(visual: List) { try { val stat = StatFs(Environment.getExternalStorageDirectory().path) @@ -609,4 +584,4 @@ class DownloadViewModel : ViewModel() { val names: List, val parentName: String? ) -} +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt index 450dc9a61..382a770cd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt @@ -164,11 +164,9 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : } } - fun downloadDeleteEvent(data: Int) { - if (data == persistentId) { - resetView() - } - } + /*fun downloadDeleteEvent(data: Int) { + + }*/ /*fun downloadEvent(data: Pair) { val (id, action) = data @@ -187,7 +185,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : override fun onAttachedToWindow() { VideoDownloadManager.downloadStatusEvent += ::downloadStatusEvent - VideoDownloadManager.downloadDeleteEvent += ::downloadDeleteEvent + // VideoDownloadManager.downloadDeleteEvent += ::downloadDeleteEvent // VideoDownloadManager.downloadEvent += ::downloadEvent VideoDownloadManager.downloadProgressEvent += ::downloadProgressEvent @@ -202,7 +200,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : override fun onDetachedFromWindow() { VideoDownloadManager.downloadStatusEvent -= ::downloadStatusEvent - VideoDownloadManager.downloadDeleteEvent -= ::downloadDeleteEvent + // VideoDownloadManager.downloadDeleteEvent -= ::downloadDeleteEvent // VideoDownloadManager.downloadEvent -= ::downloadEvent VideoDownloadManager.downloadProgressEvent -= ::downloadProgressEvent 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..ab0715c6d 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 @@ -24,15 +24,11 @@ import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import androidx.preference.PreferenceManager import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.plugins.PluginManager -import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity 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 @@ -47,7 +43,6 @@ import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable -import com.lagradost.cloudstream3.plugins.Plugin import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi import com.lagradost.cloudstream3.ui.BaseFragment @@ -68,7 +63,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 +106,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, @@ -438,30 +424,11 @@ class HomeFragment : BaseFragment( .inflate(R.layout.sort_bottom_single_provider_choice, parent, false) val titleText = view.findViewById(R.id.text1) val pinIcon = view.findViewById(R.id.pinicon) - val settingsIcon = view.findViewById(R.id.action_settings) - val name = getItem(position) titleText?.text = name - val providerApi = currentValidApis[position] val isPinned = - pinnedphashset.contains(providerApi.name) + pinnedphashset.contains(currentValidApis[position].name) pinIcon.visibility = if (isPinned) View.VISIBLE else View.GONE - - val pluginInstance = providerApi.sourcePlugin?.let { PluginManager.plugins[it] } as? Plugin - val isDownloadedPluginWithSettings = pluginInstance?.openSettings != null && !isLayout(TV) - - settingsIcon.visibility = if (isDownloadedPluginWithSettings) View.VISIBLE else View.GONE - if (isDownloadedPluginWithSettings) { - settingsIcon.setOnClickListener { - try { - val activityContext = it.context.getActivity() ?: it.context - pluginInstance.openSettings?.invoke(activityContext) - } catch (e: Throwable) { - logError(e) - } - } - } - return view } } @@ -484,14 +451,7 @@ class HomeFragment : BaseFragment( arrayAdapter.clear() val sortedApis = validAPIs .filter { - val isPinned = pinnedphashset.contains(it.name) - - // Hide pinned NSFW when NSFW not selected. NSFW is distracting when not chosen. - if (isPinned && !preSelectedTypes.contains(TvType.NSFW)) { - if (it.supportedTypes.all { type -> type == TvType.NSFW }) return@filter false - } - - it.hasMainPage && (isPinned || it.supportedTypes.any( + it.hasMainPage && (pinnedphashset.contains(it.name) || it.supportedTypes.any( preSelectedTypes::contains )) } @@ -513,10 +473,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 +672,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 +711,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 +733,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 +743,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 +758,6 @@ class HomeFragment : BaseFragment( homeLoadingError.isVisible = false homeMasterRecycler.isVisible = true homeLoadingShimmer.stopShimmer() - //home_loaded?.isVisible = true if (toggleRandomButton) { val distinct = d.values @@ -871,16 +778,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/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt index 1020205ac..e42e774b5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt @@ -58,7 +58,7 @@ class HomeScrollAdapter( when (binding) { is HomeScrollViewBinding -> { - binding.homeScrollPreview.loadImage(posterUrl, item.posterHeaders) + binding.homeScrollPreview.loadImage(posterUrl) binding.homeScrollPreviewTags.apply { text = item.tags?.joinToString(" • ") ?: "" isGone = item.tags.isNullOrEmpty() @@ -79,8 +79,8 @@ class HomeScrollAdapter( binding.homeScrollPreview.setOnClickListener { view -> callback.invoke(view ?: return@setOnClickListener, position, item) } - binding.homeScrollPreview.loadImage(posterUrl, item.posterHeaders) + binding.homeScrollPreview.loadImage(posterUrl) } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index 9c5685c68..d316f28cd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -108,7 +108,6 @@ import com.lagradost.cloudstream3.utils.PLAYREADY_DRM_UUID import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTagToLanguageName import com.lagradost.cloudstream3.utils.WIDEVINE_DRM_UUID import com.lagradost.cloudstream3.utils.videoskip.VideoSkipStamp -import com.lagradost.cloudstream4.AppSettings import kotlinx.coroutines.delay import okhttp3.Interceptor import org.chromium.net.CronetEngine @@ -1390,17 +1389,10 @@ class CS3IPlayer : IPlayer { context.getString(if (context.isUsingMobileData()) R.string.quality_pref_mobile_data_key else R.string.quality_pref_key), Int.MAX_VALUE ) - val settings = AppSettings(context) try { hasUsedFirstRender = false - val playWhenReady = if (settings.player.startPaused.get()) { - false - } else { - isPlaying // this keep the current state of the player - } - // ye this has to be a val for whatever reason // this makes no sense exoPlayer = buildExoPlayer( @@ -1412,7 +1404,7 @@ class CS3IPlayer : IPlayer { playBackSpeed, cacheSize = cacheSize, videoBufferMs = videoBufferMs, - playWhenReady = playWhenReady, + playWhenReady = isPlaying, // this keep the current state of the player subtitleOffset = currentSubtitleOffset, maxVideoHeight = maxVideoHeight, audioSources = audioSources, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 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..cfbacc5d1 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,9 +874,10 @@ 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) + resultPoster.loadImage(d.posterImage) var isExpanded = false resultDescription.apply { @@ -909,7 +910,7 @@ class ResultFragmentTv : BaseFragment( R.drawable.profile_bg_teal ).random() - backgroundPoster.loadImage(d.posterBackgroundImage, headers = d.posterHeaders) { + backgroundPoster.loadImage(d.posterBackgroundImage) { error { getImageFromDrawable(context ?: return@error null, error) } } @@ -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 269452ff2..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragmentScreen.kt +++ /dev/null @@ -1,471 +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.Immutable -import androidx.compose.runtime.NonRestartableComposable -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.PersistentList -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, - subtitle = persistentListOf( - R.string.extension_language, - R.string.title_downloads, - R.string.pref_category_bypass, - R.string.pref_category_links - ) - ), - SettingsNavigation( - title = R.string.category_player, - navigation = R.id.action_navigation_global_to_navigation_settings_player, - screen = SettingsPlayerScreen, - icon = R.drawable.play_arrow_24px, - subtitle = persistentListOf( - R.string.pref_category_subtitles, - R.string.pref_category_player_features, - R.string.pref_category_gestures, - R.string.pref_category_player_layout, - R.string.pref_category_cache - ) - ), - /*SettingsNavigation( - title = R.string.category_providers, - navigation = R.id.action_navigation_global_to_navigation_settings_providers, - screen = SettingsProvidersScreen, - icon = R.drawable.build_24px, - ),*/ - SettingsNavigation( - title = R.string.category_ui, - navigation = R.id.action_navigation_global_to_navigation_settings_ui, - screen = SettingsUIScreen, - icon = R.drawable.format_paint_24px, - subtitle = persistentListOf( - R.string.pref_category_looks, - R.string.pref_category_ui_features, - R.string.search_poster_img_des, - R.string.poster_ui_settings - ) - ), - SettingsNavigation( - title = R.string.category_updates, - navigation = R.id.action_navigation_global_to_navigation_settings_updates, - screen = SettingsUpdatesScreen, - icon = R.drawable.mobile_arrow_down_24px, - subtitle = persistentListOf( - R.string.pref_category_app_updates, - R.string.pref_category_backup, - R.string.pref_category_extensions, - R.string.pref_category_actions - ) - ), - SettingsNavigation( - title = R.string.category_account, - navigation = R.id.action_navigation_global_to_navigation_settings_account, - screen = SettingsAccountScreen, - icon = R.drawable.encrypted_24px, - subtitle = persistentListOf( - R.string.pref_category_accounts, - R.string.pref_category_security - ) - ), - SettingsNavigation( - title = R.string.pref_category_extensions, - navigation = R.id.action_navigation_global_to_navigation_settings_extensions, - screen = null, - icon = R.drawable.extension_24px, - subtitle = persistentListOf(R.string.add_repository) - ), - ) - - @Immutable - data class SettingsNavigation( - val title: Int, - val navigation: Int, - val screen: SearchableSettings?, - val icon: Int, - val subtitle: PersistentList, - ) - - - @OptIn(ExperimentalMaterial3Api::class) - @Composable - @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") - override fun Content() { - val textFieldState = rememberTextFieldState() - val searchBarState = rememberSearchBarState() - - 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(), - deferredItems = ::generateSearchItems, - onItemClick = { item -> - SearchableSettings.highlightKey = item.highlightKey - activity?.navigate(item.navigation) - }, empty = { - Column( - modifier = Modifier - .nestedScroll(parentFirstScrollConnection) - ) { - screens.forEach { settingsTab -> - SettingsTab(settingsTab) - } - BuildStamp() - } - }) - - } - } - } - - @Composable - @NonRestartableComposable - fun generateSearchItems() = - screens.mapNotNull { item -> - val contents = item.screen?.getPreferences() ?: return@mapNotNull null - SettingsData( - title = stringResource(item.title), - navigation = item.navigation, - contents = contents - ) - }.toPersistentList() - - @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) - @Composable - fun SettingsSearch(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) { - TextPreferenceWidget( - title = stringResource(settingsTab.title), - icon = painterResource(settingsTab.icon), - // This can not be converted to joinToString due to stringResource being composable - subtitle = settingsTab.subtitle.map { stringResource(it) }.joinToString(), - // Clear it if we have already set it but navigated back instantly - onPreferenceClick = { - SearchableSettings.highlightKey = null - activity?.navigate(settingsTab.navigation) - }) - } - - @Composable - fun BuildStamp() { - val (commitHash, buildTimestamp) = remember { - val commitHash = activity?.currentCommitHash() ?: "" - val buildTimestamp = SimpleDateFormat.getDateTimeInstance( - DateFormat.LONG, DateFormat.MEDIUM, - Locale.getDefault() - ).apply { - timeZone = TimeZone.getTimeZone("UTC") - }.format(Date(BuildConfig.BUILD_DATE)).replace("UTC", "") - commitHash to buildTimestamp - } - - Row( - horizontalArrangement = Arrangement.SpaceEvenly, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .height(50.dp) - .focusOutline() - .clickable { - clipboardHelper( - txt(R.string.extension_version), - "${BuildConfig.VERSION_NAME} $commitHash $buildTimestamp" - ) - }, - ) { - ProvideTextStyle(MaterialTheme.typography.bodyMedium) { - Text(text = BuildConfig.VERSION_NAME) - if (commitHash != "") { - Text("•") - Text(text = commitHash) - } - Text("•") - Text(text = buildTimestamp) - } - } - } -} - - -@PreviewLightDark -@Composable -fun Preview() { - CloudStreamPreviewTheme { - SettingsFragmentScreen.Content() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index 32f60752e..354424978 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.ui.settings import android.content.Context import android.net.Uri import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog @@ -18,7 +17,6 @@ import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity -import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R @@ -79,7 +77,6 @@ val appLanguages = arrayListOf( Pair("Azərbaycan dili", "az"), Pair("Bahasa Indonesia", "in"), Pair("Bahasa Melayu", "ms"), - Pair("català", "ca"), Pair("Deutsch", "de"), Pair("English", "en"), Pair("Español", "es"), @@ -100,7 +97,6 @@ val appLanguages = arrayListOf( Pair("Português", "pt"), Pair("Português (Brasil)", "pt-BR"), Pair("Română", "ro"), - Pair("Shqip мова", "sq"), Pair("Slovenčina", "sk"), Pair("Soomaaliga", "so"), Pair("Svenska", "sv"), @@ -110,7 +106,6 @@ val appLanguages = arrayListOf( Pair("Wikang Filipino", "fil"), Pair("Čeština", "cs"), Pair("Ελληνικά", "el"), - Pair("беларуская мова", "be"), Pair("български", "bg"), Pair("македонски", "mk"), Pair("русский", "ru"), @@ -130,7 +125,6 @@ val appLanguages = arrayListOf( Pair("தமிழ்", "ta"), Pair("ಕನ್ನಡ", "kn"), Pair("മലയാളം", "ml"), - Pair("ພາສາລາວ", "lo"), Pair("ဗမာစာ", "my"), Pair("ትግርኛ", "ti"), Pair("አማርኛ", "am"), @@ -178,96 +172,6 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { putString(context.getString(R.string.download_path_key_visual), visual) } } - - fun getCurrent(): MutableList { - return getKey>(USER_PROVIDER_API)?.toMutableList() - ?: mutableListOf() - } - fun showAdd() { - val providers = allProviders.distinctBy { it::class }.sortedBy { it.name } - val context = activity - context?.showDialog( - providers.map { "${it.name} (${it.mainUrl})" }, - -1, - context.getString(R.string.add_site_pref), - true, - {}) { selection -> - val provider = providers.getOrNull(selection) ?: return@showDialog - - val binding : AddSiteInputBinding = AddSiteInputBinding.inflate(LayoutInflater.from( - context - ),null,false) - - val builder = - AlertDialog.Builder(context, R.style.AlertDialogCustom) - .setView(binding.root) - - val dialog = builder.create() - dialog.show() - - binding.text2.text = provider.name - binding.applyBtt.setOnClickListener { - val name = binding.siteNameInput.text?.toString() - val url = binding.siteUrlInput.text?.toString() - val lang = binding.siteLangInput.text?.toString() - val realLang = if (lang.isNullOrBlank()) provider.lang else lang - val simpleName = provider::class.simpleName - if (url.isNullOrBlank() || name.isNullOrBlank() || simpleName == null) { - showToast(R.string.error_invalid_data, Toast.LENGTH_SHORT) - return@setOnClickListener - } - - val current = getCurrent() - val newSite = CustomSite(simpleName, name, url, realLang) - current.add(newSite) - setKey(USER_PROVIDER_API, current.toTypedArray()) - // reload apis - MainActivity.afterPluginsLoadedEvent.invoke(false) - - dialog.dismissSafe(activity) - } - binding.cancelBtt.setOnClickListener { - dialog.dismissSafe(activity) - } - } - } - - fun showDelete() { - val current = getCurrent() - val context = activity - context?.showMultiDialog( - current.map { it.name }, - listOf(), - context.getString(R.string.remove_site_pref), - {}) { indexes -> - current.removeAll(indexes.map { current[it] }) - setKey(USER_PROVIDER_API, current.toTypedArray()) - } - } - - fun showAddOrDelete() { - val context = activity - - val binding : AddRemoveSitesBinding = AddRemoveSitesBinding.inflate( - LayoutInflater.from( - context - ),null,false) - val builder = - AlertDialog.Builder(context ?: return, R.style.AlertDialogCustom) - .setView(binding.root) - - val dialog = builder.create() - dialog.show() - - binding.addSite.setOnClickListener { - showAdd() - dialog.dismissSafe(activity) - } - binding.removeSite.setOnClickListener { - showDelete() - dialog.dismissSafe(activity) - } - } } private val pathPicker = getChooseFolderLauncher { uri, path -> @@ -279,6 +183,10 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { setPreferencesFromResource(R.xml.settings_general, rootKey) val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext()) + fun getCurrent(): MutableList { + return getKey>(USER_PROVIDER_API)?.toMutableList() + ?: mutableListOf() + } getPref(R.string.locale_key)?.setOnPreferenceClickListener { pref -> val current = getCurrentLocale(pref.context) @@ -315,7 +223,83 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { true } + fun showAdd() { + val providers = allProviders.distinctBy { it::class }.sortedBy { it.name } + activity?.showDialog( + providers.map { "${it.name} (${it.mainUrl})" }, + -1, + context?.getString(R.string.add_site_pref) ?: return, + true, + {}) { selection -> + val provider = providers.getOrNull(selection) ?: return@showDialog + val binding : AddSiteInputBinding = AddSiteInputBinding.inflate(layoutInflater,null,false) + + val builder = + AlertDialog.Builder(context ?: return@showDialog, R.style.AlertDialogCustom) + .setView(binding.root) + + val dialog = builder.create() + dialog.show() + + binding.text2.text = provider.name + binding.applyBtt.setOnClickListener { + val name = binding.siteNameInput.text?.toString() + val url = binding.siteUrlInput.text?.toString() + val lang = binding.siteLangInput.text?.toString() + val realLang = if (lang.isNullOrBlank()) provider.lang else lang + val simpleName = provider::class.simpleName + if (url.isNullOrBlank() || name.isNullOrBlank() || simpleName == null) { + showToast(R.string.error_invalid_data, Toast.LENGTH_SHORT) + return@setOnClickListener + } + + val current = getCurrent() + val newSite = CustomSite(simpleName, name, url, realLang) + current.add(newSite) + setKey(USER_PROVIDER_API, current.toTypedArray()) + // reload apis + MainActivity.afterPluginsLoadedEvent.invoke(false) + + dialog.dismissSafe(activity) + } + binding.cancelBtt.setOnClickListener { + dialog.dismissSafe(activity) + } + } + } + + fun showDelete() { + val current = getCurrent() + + activity?.showMultiDialog( + current.map { it.name }, + listOf(), + context?.getString(R.string.remove_site_pref) ?: return, + {}) { indexes -> + current.removeAll(indexes.map { current[it] }) + setKey(USER_PROVIDER_API, current.toTypedArray()) + } + } + + fun showAddOrDelete() { + val binding : AddRemoveSitesBinding = AddRemoveSitesBinding.inflate(layoutInflater,null,false) + val builder = + AlertDialog.Builder(context ?: return, R.style.AlertDialogCustom) + .setView(binding.root) + + val dialog = builder.create() + dialog.show() + + binding.addSite.setOnClickListener { + showAdd() + dialog.dismissSafe(activity) + } + binding.removeSite.setOnClickListener { + showDelete() + dialog.dismissSafe(activity) + } + } getPref(R.string.override_site_key)?.setOnPreferenceClickListener { _ -> @@ -342,7 +326,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { val prefValues = resources.getIntArray(R.array.dns_pref_values) val currentDns = - settingsManager.getInt(getString(R.string.dns_key), 0) + settingsManager.getInt(getString(R.string.dns_pref), 0) activity?.showBottomDialog( prefNames.toList(), @@ -350,7 +334,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { getString(R.string.dns_pref), true, {}) { - settingsManager.edit { putInt(getString(R.string.dns_key), prefValues[it]) } + settingsManager.edit { putInt(getString(R.string.dns_pref), prefValues[it]) } (context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) } } return@setOnPreferenceClickListener true @@ -368,7 +352,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { (first + ctx.getExternalFilesDirs("").mapNotNull { it.path } + currentDir) - } catch (_: Exception) { + } catch (e: Exception) { first }).filterNotNull().distinct() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral2.kt deleted file mode 100644 index 37b1812b9..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral2.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.lagradost.cloudstream3.ui.settings - -import com.lagradost.cloudstream3.utils.BaseComposeFragment -import com.mihon.presentation.settings.SearchableSettings - -/** Empty glue code to connect the old navigation graph to Compose */ -class SettingsGeneral2 : BaseComposeFragment(), SearchableSettings by SettingsGeneralScreen \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneralScreen.kt deleted file mode 100644 index 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 e79a69fc9..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayerScreen.kt +++ /dev/null @@ -1,359 +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.startPaused, - title = stringResource(R.string.start_paused_settings), - subtitle = stringResource(R.string.start_paused_settings_des), - icon = painterResource(R.drawable.pause_24px), - ), - Preference.PreferenceItem.SwitchPreference( - preference = settings.player.skipOpEnabled, - title = stringResource(R.string.video_skip_op), - subtitle = stringResource(R.string.enable_skip_op_from_database_des), - icon = painterResource(R.drawable.keyboard_double_arrow_right_24px), - ), - Preference.PreferenceItem.SwitchPreference( - preference = settings.player.autoRotateEnabled, - title = stringResource(R.string.auto_rotate_video), - subtitle = stringResource(R.string.auto_rotate_video_desc), - icon = painterResource(R.drawable.screen_rotation_alt_24px), - ), - Preference.PreferenceItem.SwitchPreference( - preference = settings.player.rotateButtonEnabled, - title = stringResource(R.string.rotate_video), - subtitle = stringResource(R.string.rotate_video_desc), - icon = painterResource(R.drawable.screen_rotation), - ), - Preference.PreferenceItem.SwitchPreference( - preference = settings.player.previewBarEnabled, - enabled = isLayout(PHONE or EMULATOR), - title = stringResource(R.string.preview_seekbar), - subtitle = stringResource(R.string.preview_seekbar_desc), - icon = painterResource(R.drawable.picture_in_picture_center_24px), - ), - Preference.PreferenceItem.ListPreference( - preference = settings.player.softwareDecoding, - title = stringResource(R.string.software_decoding), - subtitle = stringResource(R.string.software_decoding_desc), - icon = painterResource(R.drawable.memory_24px), - entries = integerArrayResource(R.array.software_decoding_switch_values).zip( - stringArrayResource(R.array.software_decoding_switch) - ).toMap() - ), - Preference.PreferenceItem.SwitchPreference( - preference = settings.player.extraBrightnessEnabled, - title = stringResource(R.string.extra_brightness_settings), - subtitle = stringResource(R.string.extra_brightness_settings_des), - icon = painterResource(R.drawable.light_mode_24px), - ), - Preference.PreferenceItem.SwitchPreference( - preference = settings.player.episodeSync, - title = stringResource(R.string.episode_sync_settings), - subtitle = stringResource(R.string.episode_sync_settings_des), - icon = painterResource(R.drawable.autorenew_24px) - ), - ), - ), - Preference.PreferenceGroup( - title = stringResource(R.string.pref_category_gestures), - preferenceItems = persistentListOf( - Preference.PreferenceItem.SwitchPreference( - preference = settings.player.swipeHorizontalEnabled, - title = stringResource(R.string.swipe_to_seek_settings), - subtitle = stringResource(R.string.swipe_to_seek_settings_des), - icon = painterResource(R.drawable.swipe_24px), - ), - Preference.PreferenceItem.SwitchPreference( - preference = settings.player.swipeVerticalEnabled, - title = stringResource(R.string.swipe_to_change_settings), - subtitle = stringResource(R.string.swipe_to_change_settings_des), - icon = painterResource(R.drawable.swipe_vertical_24px), - ), - Preference.PreferenceItem.SwitchPreference( - preference = settings.player.doubleTapToSeekEnabled, - title = stringResource(R.string.double_tap_to_seek_settings), - subtitle = stringResource(R.string.double_tap_to_seek_settings_des), - icon = painterResource(R.drawable.touch_double_24px), - ), - Preference.PreferenceItem.SwitchPreference( - preference = settings.player.doubleTapToPauseEnabled, - title = stringResource(R.string.double_tap_to_pause_settings), - subtitle = stringResource(R.string.double_tap_to_pause_settings_des), - icon = painterResource(R.drawable.touch_double_24px), - ), - - Preference.PreferenceItem.SliderPreference( - 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/ui/subtitles/SubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt index bf77ef247..cf892424f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt @@ -115,9 +115,6 @@ class SubtitlesFragment : BaseDialogFragment( ) } - view.clipToPadding = false - view.clipChildren = false - // we default to 25sp, this is needed as RoundedBackgroundColorSpan breaks on override sizes val size = data.fixedTextSize ?: 25.0f view.setFixedTextSize(TypedValue.COMPLEX_UNIT_SP, size) 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/pause_24px.xml b/app/src/main/res/drawable/pause_24px.xml deleted file mode 100644 index e8de0339c..000000000 --- a/app/src/main/res/drawable/pause_24px.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/picture_in_picture_center_24px.xml b/app/src/main/res/drawable/picture_in_picture_center_24px.xml deleted file mode 100644 index 60d7e133b..000000000 --- a/app/src/main/res/drawable/picture_in_picture_center_24px.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/pip_24px.xml b/app/src/main/res/drawable/pip_24px.xml deleted file mode 100644 index 5fba5b85e..000000000 --- a/app/src/main/res/drawable/pip_24px.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/play_arrow_24px.xml b/app/src/main/res/drawable/play_arrow_24px.xml deleted file mode 100644 index b7b67fccc..000000000 --- a/app/src/main/res/drawable/play_arrow_24px.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/plugin_lang.xml b/app/src/main/res/drawable/plugin_lang.xml deleted file mode 100644 index ac33489ac..000000000 --- a/app/src/main/res/drawable/plugin_lang.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/quick_novel_icon.xml b/app/src/main/res/drawable/quick_novel_icon.xml index 8f0b3cf3e..b19aaafea 100644 --- a/app/src/main/res/drawable/quick_novel_icon.xml +++ b/app/src/main/res/drawable/quick_novel_icon.xml @@ -1,23 +1,31 @@ - - - - - + android:width="24dp" + android:height="24dp" + android:viewportWidth="108" + android:viewportHeight="108" + android:name="vector" + android:tint="?attr/white" +> + + + + + + - + \ No newline at end of file diff --git a/app/src/main/res/drawable/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 @@ - - - - - - - + + - - @@ -1085,8 +1076,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..077929d87 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/player_select_source_priority.xml b/app/src/main/res/layout/player_select_source_priority.xml index 0d825de77..182cd1861 100644 --- a/app/src/main/res/layout/player_select_source_priority.xml +++ b/app/src/main/res/layout/player_select_source_priority.xml @@ -160,13 +160,6 @@ - - - - - - - - + android:src="@drawable/pin_ic" + android:visibility="gone" + tools:visibility="visible" + tools:ignore="RtlHardcoded" /> \ No newline at end of file diff --git a/app/src/main/res/layout/source_profile_settings_dialog.xml b/app/src/main/res/layout/source_profile_settings_dialog.xml deleted file mode 100644 index 3f1f39fed..000000000 --- a/app/src/main/res/layout/source_profile_settings_dialog.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/trailer_custom_layout.xml b/app/src/main/res/layout/trailer_custom_layout.xml index b1dc96479..88a318874 100644 --- a/app/src/main/res/layout/trailer_custom_layout.xml +++ b/app/src/main/res/layout/trailer_custom_layout.xml @@ -102,8 +102,6 @@ @@ -171,12 +169,6 @@ android:layout_marginEnd="32dp" android:orientation="vertical"> - - مقطع استعراض البث قائم - أظهر ساعة الوقت الحقيقي - أظهر شاشة الوقت الحقيقي في قمة الشاشة. ينطبق على الصفحة الرئيسية و المشغل - اعدادات الملف الشخصي - أخفي المصادر صاحبة الأولوية السلبية - أخفي المصادر التي تحتوي أخطاء diff --git a/app/src/main/res/values-b+bn/strings.xml b/app/src/main/res/values-b+bn/strings.xml index 39ca9396e..e90608793 100644 --- a/app/src/main/res/values-b+bn/strings.xml +++ b/app/src/main/res/values-b+bn/strings.xml @@ -3,7 +3,7 @@ পোস্টার ক্লাউডস্ট্রিম দিয়ে চালান হোম - অভিনয়ে: %s + অভিনয়েঃ %s %1$dদিন %2$dঘন্টা %3$dমিনিট %1$dঘন্টা %2$dমিনিট %d মিনিট @@ -22,7 +22,7 @@ খুঁজুন ডাউনলোডসমূহ সেটিংস - %1$s পর্ব %2$d + %1$s এপি %2$d পর্ব %d মুক্তির তারিখ শেয়ার ব্রাউজারে খুলুন @@ -131,8 +131,8 @@ স্টোরেজ এর অনুমতি অনুপস্থিত। দয়া করে আবার চেষ্টা করুন। তথ্য ট্রেইলার প্রদর্শন করুন - Kitsu থেকে পোস্টারসমূহ প্রদর্শন করুন - স্বয়ংক্রিয় প্লাগইন আপডেট + কিটসু হতে পোস্টারসমূহ প্রদর্শন করুন + স্বয়ংক্রিয়ভাবে প্লাগিন এর হালনাগাদ স্বয়ংক্রিয়ভাবে প্লাগিনসমুহের ডাউনলোড হালনাগাদ ও ব্যাকআপ অ্যাপ এর হালনাগাদ দেখান @@ -148,9 +148,9 @@ সামনে বা পিছনের দিকে যেতে ডান বা বাম দিকে দুবার আলতো চাপুন ফাইল ডিলিট মান ডিফল্ট এ রিসেট করুন - স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন যোগ। + স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন সিনেমা - অ্যানিমে ফিলার পর্ব প্রদর্শন + এনিমে এর ফিলার পর্ব দেখায় টিভি সিরিজ লিংক পাওয়া যায়নি চা খাওয়ানো হয়েছে @@ -174,7 +174,7 @@ ডিলিট শুরু কার্টুন - Some devices do not support the new package installer. Try the legacy option if updates do not install। + কিছু ফোন নতুন প্যাকেজ ইনস্টলার সাপোর্ট করে না। যদি আপডেটগুলি ইনস্টল না হয় তবে পুরোনো পদ্ধতি ব্যবহার করে দেখুন। সাবটাইটেল নেই এই প্রোভাইডার ক্রোমকাস্ট সাপোর্ট করে না উন্নত অনুসন্ধান @@ -204,7 +204,7 @@ আমাদের তৈরি Anime দেখার অ্যাপ্লিকেশন 18+ এনিমে - অনুসন্ধানের ফলে নির্বাচিত ভিডিওর মান লুকান + অনুসন্ধান ফলাফলে নির্বাচিত ভিডিও কুয়ালিটি লুকান অ্যাপ লাইভস্ট্রিম APK ইনস্টলার @@ -220,7 +220,7 @@ আপনার বর্তমান পর্বের অগ্রগতি স্বয়ংক্রিয়ভাবে সিঙ্ক করুন প্লাগইন ডাউনলোড ফিল্টার করতে মোড নির্বাচন করুন লিঙ্ক পুনরায় লোড হয়েছে - অ্যাকাউন্ট পরিবর্তন + সুইচ অ্যাকাউন্ট দাবিত্যাগ এশিয়ান ড্রামা সোর্স @@ -256,7 +256,7 @@ টিভি লেআউট ব্যবহারকারীর নাম %1$d-%2$d - নতুন সাইটের নাম + NewSiteName ডিফল্ট OVA ওভিয়ে @@ -302,7 +302,7 @@ নতুন এপিসোডের নোটিফিকেশন অন্য এক্সটেনশনের মধ্যে খুঁজুন কোন আপডেট পাওয়া যায়নি - অ আ ক খ ১২৩ + password123 আসছে %s সময়ের মধ্যে বাতিল করুন %s \nঅবশিষ্ট @@ -322,7 +322,7 @@ আপডেট ওয়াইফাই তে যে কোয়ালিটিতে দেখতে চান মোবাইল ডাটায় যে কোয়ালিটিতে দেখতে চান - প্লেয়ারের তথ্য দেখুন + ভিডিও প্লেয়ারে রেজুলেশন ভিডিও বাফার সাইজ ভিডিও বাফার লেনথ যখন আইএসপি ব্লক করবে তখন কার্যকরী @@ -354,72 +354,4 @@ স্পিচ রিকগনিশন উপলব্ধ নেই কথা বলা শুরু করুন… ডাউনলোড তালিকা - সম্পূর্ণ সিরিজ প্লে করুন - এই ভিডিওটি একটি টরেন্ট, অর্থাৎ আপনার ভিডিও অ্যাক্টিভিটি ট্র্যাক করা সম্ভব।\nএগোনোর আগে টরেন্টিং সম্পর্কে ভালোভাবে বুঝে নিন। - ডিলিট করার জন্য আইটেম সিলেক্ট করুন - বর্তমানে কোনো ডাউনলোড নেই। - বর্তমানে কোনো ডাউনলোড অপেক্ষায় নেই। - অফলাইনে দেখার জন্য উপলব্ধ - সব সিলেক্ট করুন - সব ডিসিলেক্ট করুন - লোকাল ভিডিও খুলুন - অতিরিক্ত ব্রাইটনেস - ডিসপ্লে ব্রাইটনেস ১০০% ছাড়িয়ে গেলে ব্রাইটনেস ফিল্টার চালু করুন - সার্চ সাজেশন - টাইপ করার সময় সার্চ সাজেশন দেখান - সাজেশন মুছে ফেলুন - প্লেয়ার মেটাডেটা ওভারলে প্রদর্শন - কাস্ট প্যানেল প্রদর্শন - প্রাক-মুক্তি সংস্করণ ইনস্টলেশন - প্রাক-মুক্তি সংস্করণ ইতোমধ্যে ইনস্টল রয়েছে। - প্রাক-মুক্তি সংস্করণ ইনস্টল করতে সমস্যা হয়েছে। - (%1$d | %2$s) মুছুন - সাবধান - আপনি কি নিম্নলিখিত আইটেমগুলো স্থায়ীভাবে মুছতে চান?\n\n%s - ফাইল মুছুন - আপনি কি নিশ্চিত যে আপনি %1$s-এ নিচের এপিসোডগুলো স্থায়ীভাবে মুছে ফেলতে চান?\n\n%2$s - আপনি নিচের সিরিজগুলোর সব এপিসোডও স্থায়ীভাবে মুছে ফেলবেন:\n\n%s - আপনি কি নিশ্চিত যে আপনি নিচের সিরিজটির সব এপিসোড স্থায়ীভাবে মুছে ফেলতে চান?\n\n%s - সঙ্গীত - অডিও বই - মিডিয়া - অডিও - পডকাস্ট - ভিডিও - এনকোডিং ত্রুটি - অসমর্থিত ত্রুটি - কাস্ট মিরর - প্লে মিরর" - রেটিং লেবেল - পর্বের বিবরণ - প্লেয়ারে প্রদর্শিত - স্কিপের পরিমাণ - প্লেয়ার দৃশ্যমান থাকলে যে সময় পরিমাণ স্কিপ করা হয় - GitHub-এর সাথে সংযোগ করা যায়নি। jsDelivr proxy চালু করা হচ্ছে। - jsDelivr ব্যবহার করে GitHub-এর সরাসরি লিংক ব্লক করা এড়িয়ে যান। এতে আপডেট কয়েকদিন বিলম্বিত হতে পারে। - নিরাপত্তা - অ্যাকাউন্টসমূহ - লাইভ ঘড়ি দেখান - পর্দার উপরের অংশে লাইভ ঘড়ি দেখায়। হোমপেজ ও প্লেয়ারে প্রযোজ্য - %1$s %2$s - অতিরিক্ত_উজ্জ্বলতা_সক্রিয় - স্থানীয়ভাবে ব্যবহারকারী যাচাই - ট্র্যাকিং যোগ করা - সমন্বয় - মূল্যায়ন - %s ব্যবহারকারী প্রমাণিত - %s যোগ করা হয়েছে - /%d - %s-এ লগইন করা যায়নি - কোনোটি নয় - স্বাভাবিক - সব - সর্বোচ্চ - সর্বনিম্ন - সীমারেখা - ছায়া - উত্থিত - সাবটাইটেল সিঙ্ক করুন - ১০০০ মিলিসেকেন্ড - সাবটাইটেল বিলম্ব - সাবটাইটেল যদি %d মিলিসেকেন্ড আগে দেখায় তবে এটি ব্যবহার করুন diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml index 036ee9265..983a03db7 100644 --- a/app/src/main/res/values-b+cs/strings.xml +++ b/app/src/main/res/values-b+cs/strings.xml @@ -322,8 +322,8 @@ Náhodný Již brzy… Kam - CamRip - CamHD + Kam + Kam HQ HD TS @@ -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,16 +754,4 @@ Video Náhled Živě - Nastavení profilu - Skrýt zdroje s negativní prioritou - Skrýt zdroje s chybami - - %d skrytý odkaz - %d skryté odkazy - %d skrytých odkazů - %d skrytých odkazů - - Zobrazit hodiny s reálným časem - Zobrazit hodiny s reálným časem v horní části obrazovky. Platí pro domovskou obrazovku a přehrávač - Logcat 🐈 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 dd33369fe..c47a84842 100644 --- a/app/src/main/res/values-b+it/strings.xml +++ b/app/src/main/res/values-b+it/strings.xml @@ -225,7 +225,7 @@ Livestream NSFW Altro - Errore fonte + Errore sorgente Errore remoto Errore del renderer Errore imprevisto del lettore @@ -247,7 +247,7 @@ Controlla aggiornamenti Blocca Ridimensiona - Fonte + Sorgente Salta OP Non mostrare di nuovo Salta questo aggiornamento @@ -346,12 +346,12 @@ Protagonista Supporto Secondario - Fonte + Sorgente Casuale Prossimamente… Cam - CamRip - CamHD + Cam + Cam HQ HD TS @@ -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,21 +753,10 @@ %d download in coda %d download in coda - Priorità fonti - Decidi come le fonti video devono essere ordinate nel lettore + Priorità sorgente + Decidi come le sorgenti video devono essere ordinate nel lettore Mostra sovrapposizione metadati lettore Video Anteprima Live - Impostazioni profilo - Nascondi fonti con priorità negativa - Nascondi fonti con errori - - %d collegamento nascosto - %d collegamenti nascosti - %d collegamenti nascosti - - Mostra orologio in tempo reale - Mostra un orologio in tempo reale nella parte superiore dello schermo. Si applica alla homepage e al lettore - Logcat 🐈 diff --git a/app/src/main/res/values-b+ja/strings.xml b/app/src/main/res/values-b+ja/strings.xml index c7bdb4ef0..c5e6272b2 100644 --- a/app/src/main/res/values-b+ja/strings.xml +++ b/app/src/main/res/values-b+ja/strings.xml @@ -383,7 +383,7 @@ お気に入り 生体認証は、このデバイスではサポートされていません 指紋、顔ID、PIN、パターン、パスワードでアプリをロック解除します. - スマホやパソコンで %s にアクセスし、上記のコードを入力してください + スマホやPCで%sにアクセスし、上記のコードを入力してください PINコードが期限切れになりました! ライブラリが空です :(\nライブラリアカウントにログインするか、ローカルライブラリに表示を追加してください。 このリストは空です。 別のものに切り替えてみてください。 @@ -645,7 +645,7 @@ アプリを再起動し、ポップアップに同意して続行します。 ソフトウェアデコーディング ソフトウェアデコーディングにより、機器でサポートされていないビデオファイルを再生できますが、高解像度で遅延や不安定な再生を引き起こす可能性があります。 - %d 個のプラグインを更新しました + %d 個のプラグインを正常に更新しました! 更新されたプラグインはありません。 プレーヤー通知 バックグラウンドから再生を制御するためのプレーヤー通知 @@ -733,12 +733,4 @@ 動画 プレビュー ライブ - 現地時刻を表示 - 画面上部に現地時刻を表示します。ホーム画面とプレイヤーに適用されます - プロフィール設定 - 優先度がマイナスのソースを非表示 - エラーが発生したソースを非表示 - - %d件の非表示リンク - diff --git a/app/src/main/res/values-b+ko/strings.xml b/app/src/main/res/values-b+ko/strings.xml index 5ccc6d7bc..4feeabe33 100644 --- a/app/src/main/res/values-b+ko/strings.xml +++ b/app/src/main/res/values-b+ko/strings.xml @@ -262,7 +262,7 @@ 포스터 이미지 제목 Cam - CamRip + Cam Blu-ray WP DVD @@ -405,7 +405,7 @@ 인터넷에서 불러오기 로드됨 %s 공개 예정… - CamHD + Cam HD TS 플레이어 @@ -664,7 +664,7 @@ 플러그인 업데이트하기 수동으로 플러그인 업데이트 하기 플러그인 업데이트 절차를 시작합니다! - 플러그인 %d개를 성공적으로 업데이트했습니다 + 성공적으로 %d 플러그인을 업데이트 하였습니다! 업데이트 된 플러그인이 없습니다. 플레이어 알림 백그라운드에서 재생을 제어하기 위한 플레이어 알림 @@ -733,13 +733,4 @@ 비디오 프리뷰 라이브 - 프로필 설정 - 오류가 있는 소스 숨기기 - 우선순위가 음수인 소스 숨기기 - - 숨겨진 링크 %d개 - - 실시간 시계 표시 - 화면 상단에 실시간 시계를 표시합니다. 홈페이지와 플레이어에 적용됩니다 - Logcat 🐈 diff --git a/app/src/main/res/values-b+lv/strings.xml b/app/src/main/res/values-b+lv/strings.xml index a8ecaa68a..531b5420b 100644 --- a/app/src/main/res/values-b+lv/strings.xml +++ b/app/src/main/res/values-b+lv/strings.xml @@ -8,7 +8,7 @@ Galvenais plakāts Nākamais nejaušais Iet atpakaļ - Nomainīt pakalpojumu sniedzēju + Nomainīt dvēju Apskatīt background Ātrums (%.2fx) Lidzīgi: %.1f @@ -24,7 +24,7 @@ Nākamā epizode Pārlūks Izlaist ladešanos - Ielādē… + Lādējas… Skaties Aizturēts Pabeigts @@ -42,7 +42,7 @@ Palaist epizodi Ieladēt Lejupielāde iepauzēta - Lejupielāde sākta + Lādēšana sakās Lejupielāde neizdevās Lejupielāde atcelta Lejupielāde pabeigta @@ -50,7 +50,7 @@ Tīkla plūsma Kļūda, ielādējot saites Iekšējā atmiņa - Dublēts + Dub Dzēst datni Atskaņot datni Atsākt ielādi @@ -74,8 +74,8 @@ Stūra tips Fonts Fonta lielums - Meklēt pēc pakalpojumu sniedzējiem - Meklēt pēc tipiem + Meklēt izmantojot devējus + Meklēt izmantojot tipus %d Banāni iedoti veidotājiem %d. epizode būs pieejama Filtrs @@ -105,9 +105,9 @@ Noņemt Vairāk informācijas @string/home_play - Lai šis pakalpojumu sniedzējs darbotos pareizi, var būt nepieciešams VPN - Šis pakalpojumu sniedzējs piedāvā torrentus, tāpēc ļoti ieteicams izmantot VPN - Vietne nenodrošina metadatus. Ja tie nebūs pieejami vietnē, video lejupielāde neizdosies. + VPNs varētu būt vajadzīgs lai šis devējs strādātu pareizi + Šis devējs ir Torrents vpn ir rekomendēts + Dati nav doti no saites, video lādēšanas neizdosies ja neiksestē saitē. Apraksts Nav apraksts atrasts Apraksts nav atrasts @@ -150,7 +150,7 @@ Atjauninājumi un dublēšana Informācija Advancēta meklēšana - Sniedz meklēšanas rezultātus, kas sagrupēti pēc pakalpojumu sniedzēja + Dod tev meklēšanas rezultātus citus no devēja Rādīt anime papilda epizodi Rādīt reklāmkadrus Rādīt Kitsu vietnes plakātus @@ -168,7 +168,7 @@ Iedot banānu izstrādātājiem Iedotie banāni Lietotnes valoda - Šis pakalpojumu sniedzējs neatbalsta Chromecast + Šim devējam nav Chromecast pieņemšana Nav linku strastu Links kopēts cliobordā Restartēt uz parasto value @@ -193,7 +193,7 @@ -30 +30 Šis pilnibā dzesīs %s \nEsat parliecināts? - %dmin.\natlikusi(-šas) + %dm \natlikušas Pabeigts Statuss Gads @@ -295,8 +295,8 @@ Vēlamie multimediji Iespējot nepiedienīgu, izaicinošu saturu (NSFW) atbalstītajos papildinājumos Subtitru kodējums - Pakalpojumu sniedzēji - Pakalpojumu sniedzēja tests + Devēji + Devēju tests Izskats Automātiski Televizora izskats @@ -378,7 +378,7 @@ https://piemērs.com/piemērs.mp4 Referents Nākamais - Skatīties video šajās valodās + Skatieties videoklipus šajās valodās Iepriekšējais Izlaist uzstādīšanu Mainiet lietotnes izskatu, lai tā atbilstu savai ierīcei @@ -570,7 +570,7 @@ Atjaunināt papildinājumus Atjaunināt papildinājumus manuāli Sāk papildinājuma atjaunināšanas procesu! - Veiksmīgi atjaunināts(-i) %d papildinājums(-i) + Veiksmīgi atjaunināts(-i) %d papildinājums(-i)! Neviens papildinājums netika atjaunināts. Meklēt citos papildinājumos Pārbaudīt visus papildinājumus @@ -614,8 +614,4 @@ Papildu spilgtums Papildu spilgtums iespējots Meklēšanas ieteikumi - Pārlādēt pakalpojumu sniedzēju - Tiešraide - Video - Saites atkārtoti ielādētas diff --git a/app/src/main/res/values-b+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 0e70de68a..35367a1b2 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -324,8 +324,8 @@ Losowy Już wkrótce… Cam - CamRip - CamHD + Cam + Cam HQ HD TS @@ -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,16 +742,4 @@ Wideo Zapowiedź Na żywo - Ustawienia profilu - Ukryj źródła z negatywnym priorytetem - Ukryj źródła z błędami - - %d ukryty odnośnik - %d ukryte odnośniki - %d ukrytych odnośników - %d ukrytych odnośników - - Pokaż zegar czasu rzeczywistego - Wyświetl zegar czasu rzeczywistego u góry ekranu. Dotyczy strony głównej i odtwarzacza - Logcat 🐈 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 8ceb1789c..d711505a8 100644 --- a/app/src/main/res/values-b+tr/strings.xml +++ b/app/src/main/res/values-b+tr/strings.xml @@ -351,8 +351,8 @@ Rastgele Çok yakında… Cam - CamRip - CamHD + Cam + Cam HQ HD TS @@ -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,14 +749,4 @@ Canlı Ön Gösterim Video - Gerçek zamanlı saati göster - Ekranın üst kısmında gerçek zamanlı bir saat göster. Ana sayfa ve oynatıcı için uygulanır - Profil ayarları - Eksi önceliğe sahip kaynakları gizle - Hatalı kaynakları gizle - - %d gizli bağlantı - %d gizli bağlantı - - Logcat 🐈 diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index f434a2e4c..ba46736ac 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -381,8 +381,8 @@ Додатково https://example.com/example.mp4 Cam - CamRip - CamHD + Cam + Cam HQ HD TS @@ -646,7 +646,7 @@ Еп. %s Дата %s Оновити розширення - Успішно оновлено %d розширення(-ь) + Успішно оновлено %d розширення(-ь)! Оновити розширення вручну Починається оновлення розширень! Не оновлено жодного розширення. @@ -739,16 +739,4 @@ Відео Передперегляд Наживо - Показувати поточний час - Показувати поточний час у верхній частині екрана (на головній та у програвачі) - Налаштування профілю - Приховати джерела з від’ємним пріоритетом - Приховати джерела з помилками - - %d приховане посилання - %d приховані посилання - %d прихованих посилань - %d прихованих посилань - - Logcat 🐈 diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index 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 fc18bf6ce..56ec8f43e 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -352,8 +352,8 @@ 随机 即将到来… Cam - CamRip - CamHD + Cam + Cam HQ HD TS @@ -655,7 +655,7 @@ 得分 %s 日期 %s 更新插件 - 成功更新了 %d 个插件 + 成功更新了 %d 个插件! 没有插件被更新。 手动更新插件 集数(升序) @@ -747,13 +747,4 @@ 视频 预览 播放中 - 显示实时时钟 - 在屏幕顶部显示实时时钟。应用于主页和播放器 - 配置文件设置 - 隐藏负优先级的来源 - 隐藏有错误的来源 - - %d 条隐藏链接 - - Logcat 🐈 diff --git a/app/src/main/res/values-b+be/strings.xml b/app/src/main/res/values-be/strings.xml similarity index 99% rename from app/src/main/res/values-b+be/strings.xml rename to app/src/main/res/values-be/strings.xml index 9fbf8a8d5..3d9200faf 100644 --- a/app/src/main/res/values-b+be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -125,7 +125,7 @@ Працягнуць прагляд Выдаліць Больш інфармацыі - @string/home_play + \@string/home_play Для карэктнай працы гэтага пастаўшчыка можа спатрэбіцца VPN Гэты пастаўшчык — Torrent, рэкамендуецца VPN Вэб-сайт не пастаўляе метададзеных, загрузіць відэа не ўдасца, калі на сайце яго няма. diff --git a/app/src/main/res/values-b+ca/strings.xml b/app/src/main/res/values-ca/strings.xml similarity index 99% rename from app/src/main/res/values-b+ca/strings.xml rename to app/src/main/res/values-ca/strings.xml index 621243c78..495eb5e3c 100644 --- a/app/src/main/res/values-b+ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -123,7 +123,7 @@ Continua mirant Eliminar Més info - @string/home_play + \@string/home_play Potser necessiteu una VPN per que aquest proveïdor funcioni correctament Aquest proveïdor és un Torrent, es recomana fer servir una VPN El lloc no proveeix metadades, la càrrega del video fallarà si no existeix al lloc. diff --git a/app/src/main/res/values-lo/strings.xml b/app/src/main/res/values-lo/strings.xml deleted file mode 100644 index c1a3daa02..000000000 --- a/app/src/main/res/values-lo/strings.xml +++ /dev/null @@ -1,744 +0,0 @@ - - - %1$s ຕອນ %2$d - ນັກສະແດງ: %s - ຕອນທີ %d ຈະອອກໃນອີກ - ຊີຊັນ %1$d ຕອນ %2$d ຈະອອກໃນອີກ - %1$dd %2$dh %3$dm - %1$dh %2$dm - %dນ - %1$dh %2$dm %3$ds - %1$dm %2$ds - %1$ds - ໂປສເຕີ - ໂປສເຕີ - ໂປສເຕີຕອນ - ໂປສເຕີຫຼັກ - ສຸ່ມຖັດໄປ - ກັບຄືນ - ຫຼິ້ນແຕ່ເລີ່ມຕົ້ນ - ປ່ຽນຜູ້ໃຫ້ບໍລິການ - ຕົວຢ່າງພື້ນຫຼັງ - ຄວາມໄວ (%.2fx) - ຄະແນນ: %.1f - ພົບການອັບເດດໃໝ່!\n%1$s -> %2$s - ເນື້ອຫາເສີມ (Filler) - %d ນາທີ - CloudStream - ຫຼິ້ນດ້ວຍ CloudStream - ໜ້າຫຼັກ - ຄົ້ນຫາ - ດາວໂຫຼດ - ຄິວດາວໂຫຼດ - ການຕັ້ງຄ່າ - ຄົ້ນຫາ… - ຄົ້ນຫາ %s… - ບໍ່ສາມາດໃຊ້ການຮັບຮູ້ສຽງໄດ້ - ເລີ່ມເວົ້າ… - ບໍ່ມີຂໍ້ມູນ - ຕົວເລືອກເພີ່ມເຕີມ - ຕອນຕໍ່ໄປ - ປະເພດ - ແບ່ງປັນ - ເປີດໃນບຣາວເຊີ - ບຣາວເຊີ - ຂ້າມການໂຫຼດ - ກຳລັງໂຫຼດ… - ກຳລັງເບິ່ງ - ພັກໄວ້ກ່ອນ - ເບິ່ງຈົບແລ້ວ - ເລີກເບິ່ງ - ແຜນທີ່ຈະເບິ່ງ - ກຳລັງເບິ່ງຄືນ - ຫຼິ້ນຮູບເງົາ - ຫຼິ້ນຕົວຢ່າງ - ຫຼິ້ນໄລຟ໌ສົດ - ສະຕຣີມ Torrent - ຫຼິ້ນຊີຣີທັງໝົດ - ວິດີໂອນີ້ແມ່ນ Torrent, ຊຶ່ງໝາຍຄວາມວ່າການເຄື່ອນໄຫວຂອງທ່ານສາມາດຖືກຕິດຕາມໄດ້.\nກະລຸນາໝັ້ນໃຈວ່າທ່ານເຂົ້າໃຈກ່ຽວກັບ Torrent ກ່ອນທີ່ຈະດຳເນີນການຕໍ່. - ແຫຼ່ງວິດີໂອ - ຄຳບັນຍາຍ (Subtitles) - ລອງເຊື່ອມຕໍ່ໃໝ່… - ກັບຄືນ - ຫຼິ້ນຕອນນີ້ - ດາວໂຫຼດ - ດາວໂຫຼດແລ້ວ - ກຳລັງດາວໂຫຼດ - ຢຸດການດາວໂຫຼດຊົ່ວຄາວ - ເລີ່ມການດາວໂຫຼດ - ດາວໂຫຼດບໍ່ສຳເລັດ - ຍົກເລີກການດາວໂຫຼດ - ດາວໂຫຼດສຳເລັດ - ເລືອກລາຍການທີ່ຈະລຶບ - ຍັງບໍ່ມີລາຍການດາວໂຫຼດໃນຂະນະນີ້. - ຍັງບໍ່ມີຄິວດາວໂຫຼດໃນຂະນະນີ້. - ສາມາດເບິ່ງແບບອອຟໄລໄດ້ - ເລືອກທັງໝົດ - ຍົກເລີກການເລືອກທັງໝົດ - ເລີ່ມການອັບເດດ - ສະຕຣີມຜ່ານເຄືອຂ່າຍ - ເປີດວິດີໂອໃນເຄື່ອງ - ເກີດຂໍ້ຜິດພາດໃນການໂຫຼດລິ້ງ - ໂຫຼດລິ້ງໃໝ່ແລ້ວ - ໜ່ວຍຄວາມຈຳພາຍໃນ - ພາກ - ຊັບ - ລຶບໄຟລ໌ - ຫຼິ້ນໄຟລ໌ - ດາວໂຫຼດຕໍ່ - ຢຸດການດາວໂຫຼດ - ຂໍ້ມູນເພີ່ມເຕີມ - ເຊື່ອງ - ຫຼິ້ນ - ຂໍ້ມູນ - ກັ່ນຕອງບຸກມາກ - ບຸກມາກ - ລຶບອອກ - ຕັ້ງສະຖານະການເບິ່ງ - ນຳໃຊ້ - ຄັດລອກ - ປິດ - ລ້າງຂໍ້ມູນ - ບັນທຶກ - ຊື່ ແລະ URL ຂອງ Repository - ຄັດລອກແລ້ວ! - ແຈ້ງເຕືອນຕອນໃໝ່ - ຄົ້ນຫາໃນສ່ວນເສີມອື່ນໆ - ສະແດງລາຍການແນະນຳ - ຄວາມໄວເຄື່ອງຫຼິ້ນ - ຕັ້ງຄ່າຄຳບັນຍາຍ - ສີຂໍ້ຄວາມ - ສີຂອບຂໍ້ຄວາມ - ສີພື້ນຫຼັງ - ສີໜ້າຕ່າງ - ປະເພດຂອບ - ລະດັບຄວາມສູງຂອງຄຳບັນຍາຍ - ຟອນທ໌ - ຂະໜາດຟອນທ໌ - ຄົ້ນຫາໂດຍໃຊ້ຜູ້ໃຫ້ບໍລິການ - ຄົ້ນຫາໂດຍໃຊ້ປະເພດ - ມອບ Benene ໃຫ້ຜູ້ພັດທະນາແລ້ວ %d ອັນ - ຍັງບໍ່ໄດ້ມອບ Benene - ເລືອກພາສາອັດຕະໂນມັດ - ພາສາທີ່ຈະດາວໂຫຼດ - ພາສາຂອງຄຳບັນຍາຍ - ກົດຄ້າງເພື່ອຄືນຄ່າເລີ່ມຕົ້ນ - ນຳເຂົ້າຟອນທ໌ໂດຍການວາງໄວ້ໃນ %s - ເບິ່ງຕໍ່ - ລຶບອອກ - ຂໍ້ມູນເພີ່ມເຕີມ - \@string/home_play - ອາດຈຳເປັນຕ້ອງໃຊ້ VPN ເພື່ອໃຫ້ຜູ້ໃຫ້ບໍລິການນີ້ເຮັດວຽກໄດ້ຢ່າງຖືກຕ້ອງ - ຜູ້ໃຫ້ບໍລິການນີ້ແມ່ນ Torrent, ແນະນຳໃຫ້ໃຊ້ VPN - ເວັບໄຊທ໌ບໍ່ໄດ້ໃຫ້ຂໍ້ມູນ Metadata, ວິດີໂອອາດຈະໂຫຼດບໍ່ໄດ້ຖ້າມັນບໍ່ມີຢູ່ໃນເວັບໄຊທ໌. - ຄຳອະທິບາຍ - ບໍ່ພົບເນື້ອເລື່ອງ - ບໍ່ພົບຄຳອະທິບາຍ - ສະແດງ Logcat 🐈 - ບັນທຶກ (Log) - ຮູບຊ້ອນຮູບ (PiP) - ຫຼິ້ນຕໍ່ເນື່ອງໃນເຄື່ອງຫຼິ້ນຂະໜາດນ້ອຍເທິງແອັບຯອື່ນໆ - ປຸ່ມປັບຂະໜາດເຄື່ອງຫຼິ້ນ - ລຶບຂອບສີດຳອອກ - ຄຳບັນຍາຍ - ຕັ້ງຄ່າຄຳບັນຍາຍໃນເຄື່ອງຫຼິ້ນ - ຄຳບັນຍາຍ Chromecast - ຕັ້ງຄ່າຄຳບັນຍາຍ Chromecast - ຄວາມໄວໃນການຫຼິ້ນ - ເພີ່ມຕົວເລືອກຄວາມໄວໃນເຄື່ອງຫຼິ້ນ - ປັດເພື່ອເລື່ອນເບິ່ງ - ປັດໄປທາງຊ້າຍ ຫຼື ຂວາ ເພື່ອຄວບຄຸມຕຳແໜ່ງໃນວິດີໂອ - ປັດເພື່ອປ່ຽນການຕັ້ງຄ່າ - ປັດຂຶ້ນ ຫຼື ລົງ ທາງດ້ານຊ້າຍ ຫຼື ຂວາ ເພື່ອປ່ຽນຄວາມສະຫວ່າງ ຫຼື ສຽງ - ຫຼິ້ນຕອນຕໍ່ໄປອັດຕະໂນມັດ - ເລີ່ມຕອນຕໍ່ໄປເມື່ອຕອນປັດຈຸບັນຈົບລົງ - ແຕະສອງຄັ້ງເພື່ອເລື່ອນ - ແຕະສອງຄັ້ງເພື່ອຢຸດ - ໄລຍະເວລາການເລື່ອນ (ວິນາທີ) - ແຕະສອງຄັ້ງທາງດ້ານຂວາ ຫຼື ຊ້າຍ ເພື່ອເລື່ອນໄປໜ້າ ຫຼື ຖອຍຫຼັງ - ແຕະສອງຄັ້ງຢູ່ເຄິ່ງກາງເພື່ອຢຸດ - ໃຊ້ຄວາມສະຫວ່າງຂອງລະບົບ - ໃຊ້ຄວາມສະຫວ່າງຂອງລະບົບໃນເຄື່ອງຫຼິ້ນແທນການໃຊ້ແຜ່ນມົວສີດຳ - ຄວາມສະຫວ່າງພິເສດ - ເປີດໃຊ້ຟິວເຕີຄວາມສະຫວ່າງເມື່ອຄວາມສະຫວ່າງໜ້າຈໍເກີນ 100% - extra_brightness_enabled - ອັບເດດຄວາມຄືບໜ້າການເບິ່ງ - ຊິງຄ໌ຄວາມຄືບໜ້າຂອງຕອນປັດຈຸບັນໂດຍອັດຕະໂນມັດ - ກູ້ຄືນຂໍ້ມູນຈາກຂໍ້ມູນສຳຮອງ - ສຳຮອງຂໍ້ມູນ - ຄວາມຖີ່ໃນການສຳຮອງຂໍ້ມູນ - ໂຫຼດໄຟລ໌ສຳຮອງແລ້ວ - ບໍ່ສາມາດກູ້ຄືນຂໍ້ມູນຈາກໄຟລ໌ %s - ບັນທຶກຂໍ້ມູນແລ້ວ - ບໍ່ມີສິດໃນການເຂົ້າເຖິງບ່ອນເກັບຂໍ້ມູນ. ກະລຸນາລອງໃໝ່. - ເກີດຂໍ້ຜິດພາດໃນການສຳຮອງຂໍ້ມູນ %s - ຄົ້ນຫາ - ຄັງເກັບ - ບັນຊີ ແລະ ຄວາມປອດໄພ - ອັບເດດ ແລະ ສຳຮອງຂໍ້ມູນ - ຂໍ້ມູນ - ການຄົ້ນຫາຂັ້ນສູງ - ສະແດງຜົນການຄົ້ນຫາແຍກຕາມຜູ້ໃຫ້ບໍລິການ - ຄຳແນະນຳການຄົ້ນຫາ - ສະແດງຄຳແນະນຳໃນຂະນະທີ່ພິມຄົ້ນຫາ - ລ້າງຄຳແນະນຳ - ສະແດງຕອນເສີມ (filler) ສຳລັບອານິເມະ - ສະແດງຂໍ້ມູນ Metadata ເທິງເຄື່ອງຫຼິ້ນ - ສະແດງຕົວຢ່າງ (Trailer) - ສະແດງໂປສເຕີຈາກ Kitsu - ສະແດງລາຍຊື່ນັກສະແດງ - ເຊື່ອງຄຸນນະພາບວິດີໂອທີ່ເລືອກໃນຜົນການຄົ້ນຫາ - ອັບເດດປລັກອິນອັດຕະໂນມັດ - ດາວໂຫຼດປລັກອິນອັດຕະໂນມັດ - ເລືອກໂໝດການກັ່ນຕອງການດາວໂຫຼດປລັກອິນ - ຕິດຕັ້ງປລັກອິນທັງໝົດທີ່ຍັງບໍ່ໄດ້ຕິດຕັ້ງຈາກ repository ທີ່ເພີ່ມໄວ້ໂດຍອັດຕະໂນມັດ. - ສະແດງການອັບເດດແອັບຯ - ຄົ້ນຫາການອັບເດດໃໝ່ໂດຍອັດຕະໂນມັດຫຼັງຈາກເປີດແອັບຯ. - ຕັ້ງຄ່າໃໝ່ທັງໝົດ - ຕິດຕັ້ງເວີຊັນທົດລອງ (Pre-release) - ຕິດຕັ້ງເວີຊັນທົດລອງແລ້ວ. - ບໍ່ສາມາດຕິດຕັ້ງເວີຊັນທົດລອງໄດ້. - ຕົວຕິດຕັ້ງ APK - ບາງອຸປະກອນບໍ່ຮອງຮັບຕົວຕິດຕັ້ງແພັກເກດແບບໃໝ່. ລອງໃຊ້ຕົວເລືອກແບບເກົ່າ (Legacy) ຖ້າບໍ່ສາມາດອັບເດດໄດ້. - Github - ແອັບຯ Light novel ໂດຍຜູ້ພັດທະນາດຽວກັນ - ແອັບຯ Anime ໂດຍຜູ້ພັດທະນາດຽວກັນ - ເຂົ້າຮ່ວມ Discord - ມອບ Benene ໃຫ້ຜູ້ພັດທະນາ - ມອບ Benene ແລ້ວ - ພາສາຂອງແອັບຯ - ຜູ້ໃຫ້ບໍລິການນີ້ບໍ່ຮອງຮັບ Chromecast - ບໍ່ພົບລິ້ງ - ຄັດລອກລິ້ງລົງໃນຄລິບບອດແລ້ວ - ຫຼິ້ນຕອນນີ້ - ຄືນຄ່າເປັນຄ່າເລີ່ມຕົ້ນ - ຊີຊັນ - %1$s %2$d%3$s - ບໍ່ມີຊີຊັນ - ຕອນ - ຕອນ - %1$d-%2$d - %1$d %2$s - ຈະມາໃນອີກ %s - S - E - ບໍ່ພົບຕອນ - ລຶບ - ລຶບໄຟລ໌ - ລຶບໄຟລ໌ທັງໝົດ - ລຶບ (%1$d | %2$s) - ຍົກເລີກ - ຢຸດ - ເລີ່ມ - ບໍ່ສຳເລັດ - ສຳເລັດ - ຄຳເຕືອນ - ຫຼິ້ນຕໍ່ - -30 - +30 - ນີ້ຈະລຶບ %s ຢ່າງຖາວອນ\nທ່ານແນ່ໃຈບໍ່? - ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການລຶບລາຍການຕໍ່ໄປນີ້ຢ່າງຖາວອນ?\n\n%s - ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການລຶບຕອນຕໍ່ໄປນີ້ໃນ %1$s ຢ່າງຖາວອນ?\n\n%2$s - ທ່ານຈະລຶບທຸກຕອນໃນຊີຣີຕໍ່ໄປນີ້ຢ່າງຖາວອນ:\n\n%s - ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການລຶບທຸກຕອນໃນຊີຣີຕໍ່ໄປນີ້ຢ່າງຖາວອນ?\n\n%s - ເຫຼືອອີກ\n%d ນາທີ - ເຫຼືອອີກ\n%s - ກຳລັງສາຍ - ຈົບແລ້ວ - ສະຖານະ - ປີ - ຄະແນນ - ຄວາມຍາວ - ເວັບໄຊທ໌ - ເລື່ອງຫຍໍ້ - ຢູ່ໃນຄິວ - ບໍ່ມີຄຳບັນຍາຍ - ຄ່າເລີ່ມຕົ້ນ - ວ່າງ - ໃຊ້ໄປແລ້ວ - ແອັບຯ - ຮູບເງົາ - ຊີຣີ - ກາຕູນ - ອານິເມະ - Torrents - ສາລະຄະດີ - OVA - ຊີຣີເອເຊຍ - ໄລຟ໌ສົດ - 18+ (NSFW) - ອື່ນໆ - ຮູບເງົາ - ຊີຣີ - ກາຕູນ - ອານິເມະ - OVA - Torrent - ສາລະຄະດີ - ຊີຣີເອເຊຍ - ໄລຟ໌ສົດ - 18+ (NSFW) - ວິດີໂອ - ເພງ - ໜັງສືສຽງ - ສື່ - ສຽງ - ພອດແຄສຕ໌ - ວິດີໂອ - ຂໍ້ຜິດພາດຈາກແຫຼ່ງວິດີໂອ - ຂໍ້ຜິດພາດທາງໄກ - ຂໍ້ຜິດພາດໃນການປະມວນຜົນສັນຍານ - ຂໍ້ຜິດພາດໃນການເຂົ້າລະຫັດ - ຂໍ້ຜິດພາດ: ບໍ່ຮອງຮັບ - ເກີດຂໍ້ຜິດພາດທີ່ບໍ່ຄາດຄິດໃນເຄື່ອງຫຼິ້ນ - ການດາວໂຫຼດຜິດພາດ, ກະລຸນາກວດສອບສິດການເຂົ້າເຖິງບ່ອນເກັບຂໍ້ມູນ - Chromecast ຕອນນີ້ - Chromecast mirror - Cast mirror - ຫຼິ້ນໃນແອັບຯ - ຫຼິ້ນ mirror" - ຫຼິ້ນໃນ %s - ດາວໂຫຼດອັດຕະໂນມັດ - ດາວໂຫຼດ mirror - ໂຫຼດລິ້ງໃໝ່ - ດາວໂຫຼດຄຳບັນຍາຍ - ປ້າຍກຳກັບຄຸນນະພາບ - ປ້າຍກຳກັບພາກສຽງ - ປ້າຍກຳກັບຄຳບັນຍາຍ - ປ້າຍກຳກັບຄະແນນ - ຊື່ເລື່ອງ - ຂໍ້ຄວາມບອກຕອນ - ເປີດ/ປິດ ອົງປະກອບ UI ເທິງໂປສເຕີ - ບໍ່ພົບການອັບເດດ - ກວດສອບການອັບເດດ - ລັອກ - ປັບຂະໜາດ - ແຫຼ່ງວິດີໂອ - ຂ້າມ OP - ບໍ່ຕ້ອງສະແດງອີກ - ຂ້າມການອັບເດດນີ້ - ອັບເດດ - ຄຸນນະພາບການເບິ່ງທີ່ຕ້ອງການ (WiFi) - ຄຸນນະພາບການເບິ່ງທີ່ຕ້ອງການ (ເນັດມືຖື) - ຈຳນວນຕົວອັກສອນສູງສຸດຂອງຊື່ໃນເຄື່ອງຫຼິ້ນ - ສະແດງຂໍ້ມູນເຄື່ອງຫຼິ້ນ - ຂະໜາດບັບເຟີວິດີໂອ - ຄວາມຍາວບັບເຟີວິດີໂອ - ແຄດວິດີໂອໃນພື້ນທີ່ເກັບຂໍ້ມູນ - ລ້າງແຄດວິດີໂອ ແລະ ຮູບພາບ - ເມື່ອສະແດງເຄື່ອງຫຼິ້ນ - ໄລຍະການເລື່ອນ - ໄລຍະການເລື່ອນທີ່ໃຊ້ເມື່ອເຫັນເຄື່ອງຫຼິ້ນ - ເມື່ອເຊື່ອງເຄື່ອງຫຼິ້ນ - ໄລຍະການເລື່ອນ - ໄລຍະການເລື່ອນທີ່ໃຊ້ເມື່ອເຄື່ອງຫຼິ້ນຖືກເຊື່ອງໄວ້ - ອາດເຮັດໃຫ້ແອັບຯຄ້າງໄດ້ ຖ້າຕັ້ງຄ່າສູງເກີນໄປໃນອຸປະກອນທີ່ມີແຮມໜ້ອຍ ເຊັ່ນ Android TV. - ອາດເຮັດໃຫ້ເກີດບັນຫາ ຖ້າຕັ້ງຄ່າສູງເກີນໄປໃນອຸປະກອນທີ່ມີພື້ນທີ່ໜ້ອຍ ເຊັ່ນ Android TV. - DNS over HTTPS - ມີປະໂຫຍດໃນການຂ້າມການບລັອກຈາກ ISP - GitHub Proxy - ບໍ່ສາມາດເຊື່ອມຕໍ່ GitHub ໄດ້. ກຳລັງເປີດໃຊ້ jsDelivr proxy… - ຂ້າມການບລັອກ URL ຂອງ GitHub ໂດຍໃຊ້ jsDelivr. ອາດຈະເຮັດໃຫ້ການອັບເດດຊ້າລົງ 2-3 ມື້. - ຄັດລອກເວັບໄຊທ໌ - ລຶບເວັບໄຊທ໌ - ເພີ່ມເວັບໄຊທ໌ທີ່ຄືກັບເວັບໄຊທ໌ທີ່ມີຢູ່ແລ້ວ ແຕ່ໃຊ້ URL ອື່ນ - ບ່ອນເກັບໄຟລ໌ດາວໂຫຼດ - NGINX server URL - ສະແດງອານິເມະ ພາກສຽງ/ຄຳບັນຍາຍ - ພໍດີກັບໜ້າຈໍ - ຍືດໃຫ້ເຕັມ - ຊູມ - ຂໍ້ຄວາມປະຕິເສດຄວາມຮັບຜິດຊອບ - ການຂ້າມ ISP - ລິ້ງ - ອັບເດດແອັບຯ - ສຳຮອງຂໍ້ມູນ - ສ່ວນເສີມ - ການກະທຳ - ແຄດ - Android TV - ການຄວບຄຸມດ້ວຍທ່າທາງ - ຄວາມປອດໄພ - ບັນຊີ - ຄຸນສົມບັດເຄື່ອງຫຼິ້ນ - ຄຳບັນຍາຍ - ຮູບແບບການສະແດງຜົນ - ຄ່າເລີ່ມຕົ້ນ - ຮູບລັກສະນະ - ຄຸນສົມບັດ - ທົ່ວໄປ - ສະແດງໂມງເວລາຈິງ - ສະແດງໂມງເວລາຈິງຢູ່ເທິງສຸດຂອງໜ້າຈໍ. ນຳໃຊ້ກັບໜ້າຫຼັກ ແລະ ເຄື່ອງຫຼິ້ນ - ປຸ່ມສຸ່ມ - ສະແດງປຸ່ມສຸ່ມໃນໜ້າທຳອິດ ແລະ ຫ້ອງສະໝຸດ - ພາສາຂອງສ່ວນເສີມ - ຮູບແບບແອັບ - ສື່ທີ່ມັກ - ເປີດເນື້ອຫາ 18+ (NSFW) ໃນສ່ວນເສີມທີ່ຮອງຮັບ - ການເຂົ້າລະຫັດຄຳບັນຍາຍ - ຜູ້ໃຫ້ບໍລິການ - ທົດສອບຜູ້ໃຫ້ບໍລິການ - ທົດສອບສ່ວນເສີມທັງໝົດ - ການທົດສອບນີ້ແມ່ນສຳລັບຜູ້ພັດທະນາເທົ່ານັ້ນ ແລະ ບໍ່ໄດ້ຢືນຢັນ ຫຼື ປະຕິເສດການເຮັດວຽກຂອງສ່ວນເສີມໃດໆ. - ຮູບແບບ - ອັດຕະໂນມັດ - ຮູບແບບ TV - ຮູບແບບໂທລະສັບ - ຮູບແບບຕົວຈຳລອງ - ສີຫຼັກ - ທີມຂອງແອັບ - ຕຳແໜ່ງຊື່ຂອງໂປສເຕີ - ວາງຊື່ໄວ້ກ້ອງໂປສເຕີ - ລະຫັດຜ່ານ123 - ຊື່ຜູ້ໃຊ້ - hello@world.com - 127.0.0.1 - ຊື່ເວັບໄຊໃໝ່ - https://example.com - ລະຫັດພາສາ (lo) - %1$s %2$s - ບັນຊີ - ອອກຈາກລະບົບ - ເຂົ້າສູ່ລະບົບ - ຢືນຢັນຕົວຕົນພາຍໃນເຄື່ອງ - ສະຫຼັບບັນຊີ - ເພີ່ມບັນຊີ - ສ້າງບັນຊີ - ເພີ່ມການຕິດຕາມ - ເພີ່ມ %s ແລ້ວ - ຊິງຄ໌ຂໍ້ມູນ - ໃຫ້ຄະແນນແລ້ວ - %d / 10 - /?? - /%d - %s ຢືນຢັນຕົວຕົນແລ້ວ - ບໍ່ສາມາດເຂົ້າສູ່ລະບົບໄດ້ທີ່ %s - ປິດໃຊ້ງານ - ບໍ່ມີ - ປົກກະຕິ - ທັງໝົດ - ສູງສຸດ - ຕ່ຳສຸດ - ເສັ້ນຂອບ - ຍຸບລົງ - ເງົາ - ນູນຂຶ້ນ - ປັບເວລາຄຳບັນຍາຍ - 1000 ms - ຄວາມຊັກຊ້າຂອງຄຳບັນຍາຍ - ໃຊ້ສິ່ງນີ້ຖ້າຄຳບັນຍາຍສະແດງໄວເກີນໄປ %d ms - ໃຊ້ສິ່ງນີ້ຖ້າຄຳບັນຍາຍສະແດງຊ້າເກີນໄປ %d ms - ບໍ່ມີການຊັກຊ້າຂອງຄຳບັນຍາຍ - ເຝີດີມີຊີ້ນໝູອົບອັດແໜ້ນ - ແນະນຳ - ໂຫຼດ %s ແລ້ວ - ໂຫຼດຈາກໄຟລ໌ - ໂຫຼດຈາກອິນເຕີເນັດ - ໂຫຼດອັນທຳອິດທີ່ມີ - ໄຟລ໌ທີ່ດາວໂຫຼດແລ້ວ - ນັກສະແດງຫຼັກ - ນັກສະແດງສົມທົບ - ນັກສະແດງພື້ນຫຼັງ - ແຫຼ່ງຂໍ້ມູນ - ສຸ່ມ - ໄວໆນີ້… - ກ້ອງ - ກ້ອງ - ກ້ອງ - HQ - HD - TS - TC - Blu-ray - WP - DVD - 4K - SD - UHD - HDR - SDR - ເວັບ - ຮູບໂປສເຕີ - ຮູບ QR Code - ເຄື່ອງຫຼິ້ນ - ຄວາມລະອຽດ ແລະ ຊື່ - ຊື່ - ຄວາມລະອຽດ - ຂໍ້ມູນສື່ - ID ບໍ່ຖືກຕ້ອງ - ຂໍ້ມູນບໍ່ຖືກຕ້ອງ - URL ບໍ່ຖືກຕ້ອງ - ຂໍ້ຜິດພາດ - ເອົາຄຳອະທິບາຍສຽງອອກຈາກຄຳບັນຍາຍ - ເອົາສ່ວນເກີນອອກຈາກຄຳບັນຍາຍ - ກັ່ນຕອງຕາມພາສາສື່ທີ່ມັກ - ເພີ່ມເຕີມ - ຕົວຢ່າງໜັງ - https://example.com/example.mp4 - Referer (ທາງເລືອກ) - ຖັດໄປ - ເບິ່ງວິດີໂອໃນພາສາເຫຼົ່ານີ້ - ກ່ອນໜ້າ - ຂ້າມການຕັ້ງຄ່າ - ປ່ຽນຮູບລັກສະນະຂອງແອັບໃຫ້ເໝາະສົມກັບອຸປະກອນຂອງທ່ານ - ພາກສ່ວນໃດທີ່ເຈົ້າຢາກເບິ່ງ - ສຳເລັດ - ສ່ວນເສີມ - ເພີ່ມບ່ອນເກັບຂໍ້ມູນ (Repository) - ຊື່ບ່ອນເກັບຂໍ້ມູນ (ທາງເລືອກ) - URL ບ່ອນເກັບຂໍ້ມູນ ຫຼື ລະຫັດສັ້ນ - ໂຫຼດປລັກອິນແລ້ວ - ດາວໂຫຼດປລັກອິນແລ້ວ - ລົບປລັກອິນແລ້ວ - ບໍ່ສາມາດໂຫຼດ %s ໄດ້ - 18+ - ເລີ່ມດາວໂຫຼດ %1$d %2$s… - ດາວໂຫຼດ %1$d %2$s ສຳເລັດ - %s ທັງໝົດຖືກດາວໂຫຼດແລ້ວ - ບໍ່ພົບປລັກອິນໃນບ່ອນເກັບຂໍ້ມູນ - ບໍ່ພົບບ່ອນເກັບຂໍ້ມູນ, ກວດເບິ່ງ URL ຫຼື ລອງໃຊ້ VPN - ດາວໂຫຼດເປັນກຸ່ມ - ປລັກອິນ - ປລັກອິນ - ນີ້ຈະລົບປລັກອິນທັງໝົດໃນບ່ອນເກັບຂໍ້ມູນນີ້ຄືກັນ - ລົບບ່ອນເກັບຂໍ້ມູນ - ລົບປລັກອິນ - ດາວໂຫຼດລາຍຊື່ເວັບໄຊທີ່ເຈົ້າຕ້ອງການໃຊ້ - ດາວໂຫຼດແລ້ວ: %d - ປິດການໃຊ້ງານ: %d - ຍັງບໍ່ໄດ້ດາວໂຫຼດ: %d - ອັບເດດແລ້ວ %d ປລັກອິນ - CloudStream ບໍ່ມີເວັບໄຊຕິດຕັ້ງມາໃຫ້ແຕ່ທຳອິດ. ເຈົ້າຕ້ອງຕິດຕັ້ງເວັບໄຊຈາກບ່ອນເກັບຂໍ້ມູນ (Repositories). \n \nເຂົ້າຮ່ວມ Discord ຂອງພວກເຮົາ ຫຼື ຄົ້ນຫາທາງອອນລາຍ. - ເບິ່ງບ່ອນເກັບຂໍ້ມູນຂອງຊຸມຊົນ - ລາຍການສາທາລະນະ - ເຮັດໃຫ້ຄຳບັນຍາຍທັງໝົດເປັນຕົວພິມໃຫຍ່ - ຄຳເຕືອນ: CloudStream ບໍ່ມີສ່ວນຮັບຜິດຊອບໃດໆໃນການໃຊ້ສ່ວນເສີມຈາກບຸກຄົນທີສາມ ແລະ ບໍ່ມີການຊ່ວຍເຫຼືອສຳລັບພວກມັນ! - %s (ປິດໃຊ້ງານ) - ແທຣັກ - ແທຣັກສຽງ - ແທຣັກວິດີໂອ - ເລີ່ມແອັບໃໝ່ເພື່ອເບິ່ງການປ່ຽນແປງ. - ເລີ່ມໃໝ່ - ຢຸດ - ເປີດໂໝດປອດໄພ - ສ່ວນເສີມທັງໝົດຖືກປິດເນື່ອງຈາກແອັບຂັດຂ້ອງ ເພື່ອຊ່ວຍໃຫ້ເຈົ້າຫາຕົວທີ່ເຮັດໃຫ້ເກີດບັນຫາ. - ເບິ່ງຂໍ້ມູນການຂັດຂ້ອງ - ຄະແນນ: %s - ລາຍລະອຽດ - ເວີຊັນ - ສະຖານະ - ຂະໜາດ - ຜູ້ຂຽນ - ທີ່ຮອງຮັບ - ພາສາ - ກະລຸນາຕິດຕັ້ງສ່ວນເສີມກ່ອນ - HLS Playlist - ເຄື່ອງຫຼິ້ນວິດີໂອທີ່ມັກ - ເຄື່ອງຫຼິ້ນພາຍໃນແອັບ - ຖາມທຸກຄັ້ງ - ເລືອກອຸປະກອນ Cast - ບໍ່ພົບແອັບ - ທຸກພາສາ - ຂ້າມ %s - ເພງເປີດ - ເພງປິດ - ສະຫຼຸບຄວາມເດີມ - ເພງປິດແບບປະສົມ - ເພງເປີດແບບປະສົມ - ລາຍຊື່ຜູ້ສ້າງ - ຕົວຢ່າງຕອນຕໍ່ໄປ - ບົດນຳ - ລ້າງປະຫວັດ - ປະຫວັດ - ສະແດງປຸ່ມຂ້າມສຳລັບເພງເປີດ/ເພງປິດ - ຂໍ້ຄວາມຫຼາຍເກີນໄປ. ບໍ່ສາມາດບັນທຶກໃສ່ຄລິບບອດໄດ້. - ເກີດຂໍ້ຜິດພາດໃນການເຂົ້າເຖິງຄລິບບອດ, ກະລຸນາລອງໃໝ່. - ເກີດຂໍ້ຜິດພາດໃນການຄັດລອກ, ກະລຸນາຄັດລອກ logcat ແລະ ຕິດຕໍ່ທີມງານ. - ໝາຍວ່າເບິ່ງແລ້ວ - ເອົາອອກຈາກປະຫວັດການເບິ່ງ - ເຈົ້າແນ່ໃຈບໍ່ວ່າຕ້ອງການອອກ? - ແມ່ນ - ບໍ່ - ຕົກລົງ - ຍົກເລີກ - ເປີດບ່ອນເກັບຂໍ້ມູນ - ປິດການປະຢັດແບັດເຕີຣີ - ເພື່ອໃຫ້ການດາວໂຫຼດ ແລະ ການແຈ້ງເຕືອນລາຍການທີ່ຕິດຕາມບໍ່ຖືກຂັດຈັງຫວະ, CloudStream ຕ້ອງການການອະນຸຍາດໃຫ້ເຮັດວຽກຢູ່ພື້ນຫຼັງ. ເມື່ອກົດ ຕົກລົງ, ເຈົ້າຈະເຫັນໜ້າຕ່າງຂໍອະນຸຍາດ, ກະລຸນາກົດ \'ອະນຸຍາດ\'.\n\nໝາຍເຫດ: ການອະນຸຍາດນີ້ບໍ່ໄດ້ໝາຍຄວາມວ່າແອັບຈະກິນແບັດເຕີຣີ. ມັນຈະເຮັດວຽກຢູ່ພື້ນຫຼັງສະເພາະເວລາທີ່ຈຳເປັນ ເຊັ່ນ: ເວລາຮັບການແຈ້ງເຕືອນ ຫຼື ດາວໂຫຼດວິດີໂອ. - ການໃຊ້ແບັດເຕີຣີຂອງແອັບຖືກຕັ້ງເປັນແບບບໍ່ຈຳກັດແລ້ວ - ບໍ່ສາມາດເປີດຂໍ້ມູນແອັບ CloudStream ໄດ້. - ກຳລັງດາວໂຫຼດອັບເດດແອັບ… - ກຳລັງຕິດຕັ້ງອັບເດດແອັບ… - ບໍ່ສາມາດຕິດຕັ້ງເວີຊັນໃໝ່ຂອງແອັບໄດ້ - ແບບເກົ່າ - PackageInstaller - ແອັບຈະຖືກອັບເດດເມື່ອທ່ານອອກ - ຈັດຮຽງຕາມ - ຈັດຮຽງ - ຄະແນນ (ສູງ ຫາ ຕ່ຳ) - ຄະແນນ (ຕ່ຳ ຫາ ສູງ) - ອັບເດດ (ໃໝ່ ຫາ ເກົ່າ) - ອັບເດດ (ເກົ່າ ຫາ ໃໝ່) - ຕາມຕົວອັກສອນ (ກ ຫາ ຮ) - ຕາມຕົວອັກສອນ (ຮ ຫາ ກ) - ຕອນ (ນ້ອຍ ຫາ ຫຼາຍ) - ຕອນ (ຫຼາຍ ຫາ ນ້ອຍ) - ຄະແນນ (ສູງສຸດ) - ຄະແນນ (ຕ່ຳສຸດ) - ວັນທີສາຍ (ໃໝ່ສຸດ) - ວັນທີສາຍ (ເກົ່າສຸດ) - ຕອນ %s - ຄະແນນ %s - ວັນທີ %s - ເລືອກຫ້ອງສະໝຸດ - ເປີດດ້ວຍ - ຫ້ອງສະໝຸດຂອງເຈົ້າຍັງວ່າງ :( \nເຂົ້າສູ່ລະບົບບັນຊີຫ້ອງສະໝຸດ ຫຼື ເພີ່ມລາຍການໃສ່ຫ້ອງສະໝຸດໃນເຄື່ອງ. - ລາຍການນີ້ຍັງວ່າງ. ລອງສະຫຼັບໄປອັນອື່ນເບິ່ງ. - ພົບໄຟລ໌ໂໝດປອດໄພ!\nຈະບໍ່ໂຫຼດສ່ວນເສີມໃດໆຕອນເລີ່ມຕົ້ນ ຈົນກວ່າໄຟລ໌ຈະຖືກລົບອອກ. - ຄືນຄ່າ - ກຳລັງອັບເດດລາຍການທີ່ຕິດຕາມ - ຕິດຕາມແລ້ວ - ຕິດຕາມ %s ແລ້ວ - ຍົກເລີກການຕິດຕາມ %s ແລ້ວ - ຕອນທີ %d ອອກແລ້ວ! - ຕິດຕາມ - ຍົກເລີກການຕິດຕາມ - ໂປຣໄຟລ໌ %d - ຕັ້ງຄ່າໂປຣໄຟລ໌ - ເຊື່ອງແຫຼ່ງຂໍ້ມູນທີ່ມີລຳດັບຄວາມສຳຄັນຕິດລົບ - ເຊື່ອງແຫຼ່ງຂໍ້ມູນທີ່ມີຂໍ້ຜິດພາດ - - ເຊື່ອງ %d ລິ້ງ - - Wi-Fi - ຂໍ້ມູນມືຖື - ຕັ້ງເປັນຄ່າເລີ່ມຕົ້ນ - ໃຊ້ງານ - ແກ້ໄຂ - ລຳດັບຄວາມສຳຄັນຂອງແຫຼ່ງຂໍ້ມູນ - ຕັດສິນໃຈວ່າຈະໃຫ້ຈັດຮຽງແຫຼ່ງວິດີໂອແນວໃດໃນເຄື່ອງຫຼິ້ນ - ໂປຣໄຟລ໌ - ຊ່ວຍເຫຼືອ - ຢູ່ບ່ອນນີ້ ເຈົ້າສາມາດປ່ຽນວິທີການຈັດຮຽງແຫຼ່ງຂໍ້ມູນ. ຖ້າວິດີໂອມີລຳດັບຄວາມສຳຄັນສູງກວ່າ ມັນຈະປາກົດຢູ່ທາງເທິງໃນການເລືອກແຫຼ່ງຂໍ້ມູນ. ຜົນລວມຂອງລຳດັບຄວາມສຳຄັນຂອງແຫຼ່ງຂໍ້ມູນ ແລະ ຄຸນນະພາບ ແມ່ນລຳດັບຄວາມສຳຄັນຂອງວິດີໂອ. \n\nແຫຼ່ງຂໍ້ມູນ A: 3 \nຄຸນນະພາບ B: 7 \nຈະມີລຳດັບຄວາມສຳຄັນລວມແມ່ນ 10. \n\nໝາຍເຫດ: ຖ້າຜົນລວມແມ່ນ 10 ຫຼື ຫຼາຍກວ່າ, ເຄື່ອງຫຼິ້ນຈະຂ້າມການໂຫຼດໂດຍອັດຕະໂນມັດເມື່ອລິ້ງນັ້ນຖືກໂຫຼດ! - ຄຸນນະພາບ - ພື້ນຫຼັງໂປຣໄຟລ໌ - UI ບໍ່ສາມາດສ້າງໄດ້ຢ່າງຖືກຕ້ອງ, ນີ້ແມ່ນບັກໃຫຍ່ ແລະ ຄວນລາຍງານທັນທີ %s - ເຈົ້າໄດ້ລົງຄະແນນແລ້ວ - ລາຍການທີ່ມັກ - ເພີ່ມ %s ເຂົ້າໃນລາຍການທີ່ມັກແລ້ວ - ເອົາ %s ອອກຈາກລາຍການທີ່ມັກແລ້ວ - ເພີ່ມເຂົ້າໃນລາຍການທີ່ມັກ - ເອົາອອກຈາກລາຍການທີ່ມັກ - ພົບລາຍການທີ່ອາດຈະຊ້ຳກັນ - ເພີ່ມ - ແທນທີ່ - ແທນທີ່ທັງໝົດ - ເບິ່ງຄືວ່າມີລາຍການທີ່ອາດຈະຊ້ຳກັນຢູ່ໃນຫ້ອງສະໝຸດຂອງເຈົ້າ: \'%s.\' \n\nເຈົ້າຕ້ອງການເພີ່ມລາຍການນີ້ຕໍ່ໄປ, ແທນທີ່ອັນເກົ່າ ຫຼື ຍົກເລີກການກະທຳ? - ພົບລາຍການທີ່ອາດຈະຊ້ຳກັນໃນຫ້ອງສະໝຸດຂອງເຈົ້າ: \n\n%s \n\nເຈົ້າຕ້ອງການເພີ່ມລາຍການນີ້ຕໍ່ໄປ, ແທນທີ່ອັນເກົ່າ ຫຼື ຍົກເລີກການກະທຳ? - ໃສ່ PIN - ໃສ່ PIN ສຳລັບ %s - ໃສ່ PIN ປັດຈຸບັນ - ລັອກໂປຣໄຟລ໌ - PIN - PIN ບໍ່ຖືກຕ້ອງ. ກະລຸນາລອງໃໝ່. - PIN ຕ້ອງມີ 4 ຕົວ - ເລືອກບັນຊີ - ບໍ່ມີບັນຊີ - ຈັດການບັນຊີ - ແກ້ໄຂບັນຊີ - ເຂົ້າສູ່ລະບົບໂດຍ %s - ຂ້າມການເລືອກບັນຊີຕອນເລີ່ມຕົ້ນ - ໃຊ້ບັນຊີເລີ່ມຕົ້ນ - ໝຸນວິດີໂອ - ສະແດງປຸ່ມສະຫຼັບການວາງແນວໜ້າຈໍ - ເປີດການສະຫຼັບແນວໜ້າຈໍອັດຕະໂນມັດຕາມແນວວິດີໂອ - ໝຸນອັດຕະໂນມັດ - ມັກ - ເລີກມັກ - ປົດລັອກ CloudStream - ລັອກດ້ວຍໄບໂອເມທຣິກ (ລາຍນິ້ວມື/ໃບໜ້າ) - ການຢືນຢັນດ້ວຍລະຫັດຜ່ານ/PIN - ອຸປະກອນນີ້ບໍ່ຮອງຮັບການຢືນຢັນດ້ວຍໄບໂອເມທຣິກ - ປົດລັອກແອັບດ້ວຍລາຍນິ້ວມື, ໃບໜ້າ, PIN, ຮູບແບບ ຫຼື ລະຫັດຜ່ານ. - ຫຼັງຈາກພະຍາຍາມລົ້ມເຫຼວຫຼາຍຄັ້ງ, ໜ້າຕ່າງຈະປິດລົງ. ໃຫ້ເລີ່ມແອັບໃໝ່ເພື່ອລອງອີກຄັ້ງ. - ຂໍ້ມູນ CloudStream ຂອງທ່ານຖືກສຳຮອງໄວ້ແລ້ວ. ເຖິງແມ່ນວ່າມັນຈະເກີດຂຶ້ນໄດ້ຍາກ, ແຕ່ອຸປະກອນແຕ່ລະເຄື່ອງອາດເຮັດວຽກຕ່າງກັນ. ໃນກໍລະນີທີ່ທ່ານຖືກລັອກບໍ່ໃຫ້ເຂົ້າເຖິງແອັບ, ໃຫ້ລ້າງຂໍ້ມູນແອັບທັງໝົດ ແລະ ກູ້ຄືນຈາກໄຟລ໌ສຳຮອງ. ພວກເຮົາຂໍອະໄພໃນຄວາມບໍ່ສະດວກ. - ຕັ້ງຄ່າໃໝ່ - CloudStream Wiki - ເຂົ້າໄປທີ່ %s ໃນມືຖື ຫຼື ຄອມພິວເຕີຂອງທ່ານ ແລະ ໃສ່ລະຫັດຂ້າງເທິງ - ບໍ່ສາມາດຮັບລະຫັດ PIN ຂອງອຸປະກອນໄດ້, ລອງຢືນຢັນຕົວຕົນພາຍໃນເຄື່ອງ - ລະຫັດ PIN ໝົດອາຍຸແລ້ວ! - ລະຫັດຈະໝົດອາຍຸໃນ %1$dm %2$ds - ວັນທີວາງຈຳໜ່າຍ (ໃໝ່ ຫາ ເກົ່າ) - ວັນທີວາງຈຳໜ່າຍ (ເກົ່າ ຫາ ໃໝ່) - ເຊື່ອງຊື່ປຸ່ມຄວບຄຸມໃນເຄື່ອງຫຼິ້ນ - ຕົວຢ່າງຮູບໃນແຖບເລື່ອນເວລາ - ເປີດສະແດງຮູບຕົວຢ່າງເທິງແຖບເລື່ອນເວລາ - ຍັງບໍ່ໄດ້ໂຫຼດຄຳບັນຍາຍ - ຕຳແໜ່ງໂຟນເດີສຳຮອງຂໍ້ມູນ - ກຳນົດເອງ - ຢືນຢັນກ່ອນອອກ - ສະແດງໜ້າຕ່າງຢືນຢັນກ່ອນອອກຈາກແອັບ - ສະແດງ - ບໍ່ສະແດງ - ຂະໜາດເສັ້ນຂອບ - ເປີດໃຊ້ torrent ໃນ ຕັ້ງຄ່າ/ຜູ້ໃຫ້ບໍລິການ/ສື່ທີ່ມັກ - ເລີ່ມແອັບໃໝ່ ແລະ ກົດຍອມຮັບ Stream Torrent ເພື່ອດຳເນີນການຕໍ່. - ການຖອດລະຫັດດ້ວຍຊອບແວ - ການຖອດລະຫັດດ້ວຍຊອບແວຊ່ວຍໃຫ້ຫຼິ້ນໄຟລ໌ທີ່ອຸປະກອນບໍ່ຮອງຮັບໄດ້, ແຕ່ອາດເຮັດໃຫ້ກະຕຸກ ຫຼື ບໍ່ສະຖຽນໃນຄວາມລະອຽດສູງ. - ລະດັບສຽງເກີນ 100% ແລ້ວ - ເລື່ອນຂຶ້ນອີກຄັ້ງເພື່ອໃຫ້ສຽງດັງເກີນ 100% - ອັບເດດປລັກອິນ - ອັບເດດປລັກອິນດ້ວຍຕົນເອງ - ເລີ່ມຂະບວນການອັບເດດປລັກອິນ! - ອັບເດດ %d ປລັກອິນສຳເລັດແລ້ວ - ບໍ່ມີປລັກອິນໃດຖືກອັບເດດ. - ການແຈ້ງເຕືອນເຄື່ອງຫຼິ້ນ - ການແຈ້ງເຕືອນເຄື່ອງຫຼິ້ນສຳລັບຄວບຄຸມການຫຼິ້ນຈາກພື້ນຫຼັງ - ຝັງໃນຕົວ - ອອນລາຍ - ເຮັດໃຫ້ຄຳບັນຍາຍທັງໝົດເປັນຕົວເຂັ້ມ - ເຮັດໃຫ້ຄຳບັນຍາຍທັງໝົດເປັນຕົວອຽງ - ຄວາມມົນຂອງພື້ນຫຼັງ - ຈຳນວນລາຍການທີ່ສາມາດດາວໂຫຼດພ້ອມກັນໄດ້ - ດາວໂຫຼດຂະໜານ - ການເຊື່ອມຕໍ່ພ້ອມກັນ - ຈຳນວນການເຊື່ອມຕໍ່ພ້ອມກັນທີ່ແຕ່ລະການດາວໂຫຼດສາມາດໃຊ້ໄດ້ - ໄປທີ່ລາຍການດາວໂຫຼດ - ບໍ່ມີການເຊື່ອມຕໍ່ອິນເຕີເນັດ. \n\nກະລຸນາເຊື່ອມຕໍ່ອິນເຕີເນັດແລ້ວລອງໃໝ່, ຫຼື ເບິ່ງໄຟລ໌ທີ່ດາວໂຫຼດໄວ້ໃນຂະນະທີ່ອອບລາຍ. - ປ່ຽນຂອບເຂດຂອງໜ້າຈໍ - Overscan - ປ່ຽນຂະໜາດຂອງໂປສເຕີ - ຂະໜາດໂປສເຕີ - ກົດຄ້າງເພື່ອສະຫຼັບຄວາມໄວ - ກົດຄ້າງໄວ້ເພື່ອຫຼິ້ນດ້ວຍຄວາມໄວ 2 ເທົ່າ - ແກ້ໄຂຮູບໂປຣໄຟລ໌ - ໃສ່ URL ຮູບໂປຣໄຟລ໌ - ບໍ່ພົບ URL - URL ຫຼື ຮູບພາບບໍ່ຖືກຕ້ອງ - ອັບເດດຮູບພາບສຳເລັດແລ້ວ - ໝາຍວ່າເບິ່ງແລ້ວຈົນຮອດຕອນນີ້ - ເອົາເຄື່ອງໝາຍເບິ່ງແລ້ວອອກຈົນຮອດຕອນນີ້ - ໂຫຼດໃໝ່ແລ້ວ - ໂຫຼດຜູ້ໃຫ້ບໍລິການໃໝ່ - ຊື່ - ຊື່ແຫຼ່ງຂໍ້ມູນ - ຄວາມລະອຽດ ແລະ ຊື່ - ດາວໂຫຼດທັງໝົດ - ຍົກເລີກທັງໝົດ - ເຈົ້າຕ້ອງການດາວໂຫຼດຕອນ %s ບໍ່? - ເຈົ້າຕ້ອງການຍົກເລີກການດາວໂຫຼດທີ່ຄ້າງຢູ່ໃນຄິວທັງໝົດບໍ່? - ການຈັດວາງຄຳບັນຍາຍ - ລຸ່ມຊ້າຍ - ລຸ່ມກາງ - ລຸ່ມຂວາ - ກາງຊ້າຍ - ກາງ - ກາງຂວາ - ເທິງຊ້າຍ - ເທິງກາງ - ເທິງຂວາ - - ກຳລັງດາວໂຫຼດ %d ລາຍການ - - - %d ລາຍການຢູ່ໃນຄິວ - - ຖ່າຍທອດສົດ - diff --git a/app/src/main/res/values-b+sq/strings.xml b/app/src/main/res/values-sq/strings.xml similarity index 98% rename from app/src/main/res/values-b+sq/strings.xml rename to app/src/main/res/values-sq/strings.xml index d083809bb..a13f87bfa 100644 --- a/app/src/main/res/values-b+sq/strings.xml +++ b/app/src/main/res/values-sq/strings.xml @@ -117,7 +117,7 @@ Madhësia e shkrimit Kërko duke përdorur ofruesit Kërko duke përdorur tipet - %d Banane të dhëna për developer-at + %d Banane të dhëna per developer-at Asnjë Banane e dhënë Zgjedhje automatike e gjuhës Shkarko gjuhët @@ -127,7 +127,7 @@ Vazhdo shikimin Hiq Më shumë informacion - @string/home_play + \@string/home_play Një VPN mund të nevojitet që ky ofrues të funksionojë në rregull Ky ofrues është Torrent, rekomandohet një VPN Metadata nuk ofrohet nga kjo faqe, ngarkimi i videos do të dështojë nëse nuk ekziston në këtë faqe. @@ -216,7 +216,7 @@ %1$s %2$d%3$s Asnje sezon Episodi - Episode + Episodet %1$d-%2$d %1$d %2$s Vazhdon në %s @@ -269,7 +269,7 @@ NSFW Tjera Film - Serial + Seri Vizatimorë Anime OVA @@ -679,7 +679,7 @@ Përditëso shtesat Përditëso shtesat manualisht Filloi procesi i përditësimit të shtesave! - %d Shtes(a) u përditësuan me sukses + %d Shtes(at) u përditësuan me sukses! Nuk u përditësua asnjë shtesë. Njoftimet e luajtësit Njoftimi për kontrollin e luajtjes nga sfondi @@ -735,13 +735,4 @@ %d shkarkime në radhë Live - Cilësimet e profil-it - Fshih burimet me prioritet negativ - Fshih burimet me error-e - - %d link i fshehur - %d linqe të fshehura - - Shfaq orën në kohë reale - Shfaq një orë në krye të ekranit. Aplikohet në faqen kryesore dhe në luajtës diff --git a/app/src/main/res/values/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 36c1cfe11..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 @@ -157,8 +156,6 @@ Slide up or down on the left or right side to change brightness or volume Autoplay next episode Start the next episode when the current one ends - Start videos paused - Reduce stress when selecting sources Double tap to seek Double tap to pause Player seek amount (Seconds) @@ -377,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 @@ -461,8 +456,8 @@ Random Coming soon… Cam - CamRip - CamHD + Cam + Cam HQ HD TS @@ -627,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 @@ -736,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 @@ -795,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/lo-LA/changelogs/2.txt b/fastlane/metadata/android/lo-LA/changelogs/2.txt deleted file mode 100644 index a9c33c045..000000000 --- a/fastlane/metadata/android/lo-LA/changelogs/2.txt +++ /dev/null @@ -1 +0,0 @@ -- ເພີ່ມບັນທຶກການປ່ຽນແປງແລ້ວ! diff --git a/fastlane/metadata/android/lo-LA/full_description.txt b/fastlane/metadata/android/lo-LA/full_description.txt deleted file mode 100644 index 6c1396482..000000000 --- a/fastlane/metadata/android/lo-LA/full_description.txt +++ /dev/null @@ -1,11 +0,0 @@ -CloudStream-3 ຊ່ວຍໃຫ້ທ່ານສາມາດເບິ່ງ ແລະ ດາວໂຫຼດຮູບເງົາ, ຊີຣີສ໌ ແລະ ອານິເມະ. - -ແອັບນີ້ບໍ່ມີໂຄສະນາ ແລະ ບໍ່ມີການເກັບກຳຂໍ້ມູນການໃຊ້ງານ ແລະ - -ຮອງຮັບເວັບໄຊຕົວຢ່າງຮູບເງົາ ແລະ ເວັບໄຊຮູບເງົາຫຼາຍແຫ່ງ, ແລະ ອື່ນໆ ເຊັ່ນ: - -ລາຍການທີ່ບັນທຶກໄວ້ - -ດາວໂຫຼດຄຳບັນຍາຍ - -ຮອງຮັບ Chromecast diff --git a/fastlane/metadata/android/lo-LA/short_description.txt b/fastlane/metadata/android/lo-LA/short_description.txt deleted file mode 100644 index 507aa8a70..000000000 --- a/fastlane/metadata/android/lo-LA/short_description.txt +++ /dev/null @@ -1 +0,0 @@ -ເບິ່ງ ແລະ ດາວໂຫຼດຮູບເງົາ, ຊີຣີສ໌ ແລະ ອານິເມະ. diff --git a/fastlane/metadata/android/lo-LA/title.txt b/fastlane/metadata/android/lo-LA/title.txt deleted file mode 100644 index dde89d58f..000000000 --- a/fastlane/metadata/android/lo-LA/title.txt +++ /dev/null @@ -1 +0,0 @@ -CloudStream diff --git a/fastlane/metadata/android/sq/full_description.txt b/fastlane/metadata/android/sq/full_description.txt index a7881e62e..f9d6e6f9e 100644 --- a/fastlane/metadata/android/sq/full_description.txt +++ b/fastlane/metadata/android/sq/full_description.txt @@ -1,7 +1,7 @@ CloudStream-3 ju lejon të shikoni dhe shkarkoni filma, seriale televizive dhe anime. Aplikacioni vjen pa asnjë reklamë dhe analitikë -dhe suporton faqe të shumta trailer-ash dhe filmash, dhe më shumë, p.sh. +dhe suporton faqe të shumta trailerash dhe filmash dhe më shumë, p.sh. Të ruajturat diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4652cf938..fdfaee7b2 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" @@ -31,7 +31,6 @@ kotlinxAtomicfu = "0.33.0" kotlinxCollectionsImmutable = "0.4.0" kotlinxCoroutines = "1.11.0" kotlinxDatetime = "0.8.0" -kotlinxIOCore = "0.9.1" kotlinxSerializationJson = "1.11.0" ksoup = "0.2.6" ktor = "3.5.0" @@ -50,19 +49,14 @@ qrcodeKotlin = "4.5.0" rhino = { strictly = "1.8.1" } # Requires minSdk 26 or later beginning at version 1.9.0 safefile = "0.0.8" shimmer = "0.5.0" +tmdbJava = "2.13.0" torrentserver = "7861970" 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" } @@ -106,7 +99,6 @@ kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collec kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } -kotlinx-io-core = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = "kotlinxIOCore" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } ksoup = { module = "com.fleeksoft.ksoup:ksoup", version.ref = "ksoup" } ktor-http = { module = "io.ktor:ktor-http", version.ref = "ktor" } @@ -137,22 +129,13 @@ qrcode-kotlin = { module = "io.github.g0dkar:qrcode-kotlin", version.ref = "qrco rhino = { module = "org.mozilla:rhino", version.ref = "rhino" } safefile = { module = "com.github.LagradOst:SafeFile", version.ref = "safefile" } shimmer = { module = "com.facebook.shimmer:shimmer", version.ref = "shimmer" } +tmdb-java = { module = "com.uwetrottmann.tmdb2:tmdb-java", version.ref = "tmdbJava" } torrentserver = { module = "com.github.recloudstream:torrentserver", version.ref = "torrentserver" } tvprovider = { module = "androidx.tvprovider:tvprovider", version.ref = "tvprovider" } 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/api/jvm/library.api b/library/api/jvm/library.api index 195cd49fe..1b39b3906 100644 --- a/library/api/jvm/library.api +++ b/library/api/jvm/library.api @@ -6613,7 +6613,6 @@ public final class com/lagradost/cloudstream3/metaproviders/SyncRedirector { } public final class com/lagradost/cloudstream3/metaproviders/TmdbLink { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbLink$Companion; public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;)V public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; @@ -6633,21 +6632,6 @@ public final class com/lagradost/cloudstream3/metaproviders/TmdbLink { public fun toString ()Ljava/lang/String; } -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbLink$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbLink$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbLink; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbLink;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbLink$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - public class com/lagradost/cloudstream3/metaproviders/TmdbProvider : com/lagradost/cloudstream3/MainAPI { public fun ()V public fun fetchContentRating (Ljava/lang/Integer;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -6666,684 +6650,6 @@ public class com/lagradost/cloudstream3/metaproviders/TmdbProvider : com/lagrado public fun search (Ljava/lang/String;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; } -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember$Companion; - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember; - public fun equals (Ljava/lang/Object;)Z - public final fun getCharacter ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public final fun getProfilePath ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCastMember$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating$Companion; - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating; - public fun equals (Ljava/lang/Object;)Z - public final fun getCountry ()Ljava/lang/String; - public final fun getRating ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRating$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings$Companion; - public fun ()V - public fun (Ljava/util/List;)V - public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; - public fun equals (Ljava/lang/Object;)Z - public final fun getResults ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits$Companion; - public fun ()V - public fun (Ljava/util/List;)V - public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; - public fun equals (Ljava/lang/Object;)Z - public final fun getCast ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode$Companion; - public fun ()V - public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;)V - public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/Integer; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/Integer; - public final fun component5 ()Ljava/lang/Integer; - public final fun component6 ()Ljava/lang/String; - public final fun component7 ()Ljava/lang/String; - public final fun component8 ()Ljava/lang/Double; - public final fun component9 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; - public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode; - public fun equals (Ljava/lang/Object;)Z - public final fun getAirDate ()Ljava/lang/String; - public final fun getEpisodeNumber ()Ljava/lang/Integer; - public final fun getExternalIds ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; - public final fun getId ()Ljava/lang/Integer; - public final fun getName ()Ljava/lang/String; - public final fun getOverview ()Ljava/lang/String; - public final fun getSeasonNumber ()Ljava/lang/Integer; - public final fun getStillPath ()Ljava/lang/String; - public final fun getVoteAverage ()Ljava/lang/Double; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbEpisode$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre$Companion; - public fun ()V - public fun (Ljava/lang/Integer;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/Integer; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/Integer;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre;Ljava/lang/Integer;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()Ljava/lang/Integer; - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbGenre$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds$Companion; - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/Integer;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/Integer; - public final fun copy (Ljava/lang/String;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; - public fun equals (Ljava/lang/Object;)Z - public final fun getImdbId ()Ljava/lang/String; - public final fun getTvdbId ()Ljava/lang/Integer; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail$Companion; - public fun ()V - public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;)V - public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/Integer; - public final fun component10 ()Ljava/lang/String; - public final fun component11 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; - public final fun component12 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; - public final fun component13 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; - public final fun component14 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; - public final fun component15 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; - public final fun component16 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Ljava/lang/String; - public final fun component7 ()Ljava/lang/Double; - public final fun component8 ()Ljava/util/List; - public final fun component9 ()Ljava/lang/Integer; - public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/String;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail; - public fun equals (Ljava/lang/Object;)Z - public final fun getCredits ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; - public final fun getDisplayTitle ()Ljava/lang/String; - public final fun getExternalIds ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; - public final fun getGenres ()Ljava/util/List; - public final fun getId ()Ljava/lang/Integer; - public final fun getImdbId ()Ljava/lang/String; - public final fun getOriginalTitle ()Ljava/lang/String; - public final fun getOverview ()Ljava/lang/String; - public final fun getPosterPath ()Ljava/lang/String; - public final fun getRecommendations ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; - public final fun getReleaseDate ()Ljava/lang/String; - public final fun getReleaseDates ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; - public final fun getRuntime ()Ljava/lang/Integer; - public final fun getSimilar ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; - public final fun getTitle ()Ljava/lang/String; - public final fun getVideos ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; - public final fun getVoteAverage ()Ljava/lang/Double; - public final fun getYear ()Ljava/lang/Integer; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMovieDetail$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult$Companion; - public fun ()V - public fun (Ljava/util/List;)V - public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getResults ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbMultiResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult$Companion; - public fun ()V - public fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;)V - public synthetic fun (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun component2 ()Ljava/lang/Integer; - public final fun component3 ()Ljava/lang/Integer; - public final fun copy (Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Ljava/util/List;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getResults ()Ljava/util/List; - public final fun getTotalPages ()Ljava/lang/Integer; - public final fun getTotalResults ()Ljava/lang/Integer; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry$Companion; - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/Integer;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/Integer; - public final fun copy (Ljava/lang/String;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry; - public fun equals (Ljava/lang/Object;)Z - public final fun getCertification ()Ljava/lang/String; - public final fun getType ()Ljava/lang/Integer; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateEntry$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult$Companion; - public fun ()V - public fun (Ljava/lang/String;Ljava/util/List;)V - public synthetic fun (Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/util/List; - public final fun copy (Ljava/lang/String;Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getCountry ()Ljava/lang/String; - public final fun getReleaseDates ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDateResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates$Companion; - public fun ()V - public fun (Ljava/util/List;)V - public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; - public fun equals (Ljava/lang/Object;)Z - public final fun getResults ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbReleaseDates$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult$Companion; - public fun ()V - public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/Integer; - public final fun component10 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Ljava/lang/String; - public final fun component7 ()Ljava/lang/Double; - public final fun component8 ()Ljava/lang/String; - public final fun component9 ()Ljava/lang/String; - public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult; - public fun equals (Ljava/lang/Object;)Z - public final fun getDisplayTitle ()Ljava/lang/String; - public final fun getFirstAirDate ()Ljava/lang/String; - public final fun getId ()Ljava/lang/Integer; - public final fun getMediaType ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public final fun getOriginalName ()Ljava/lang/String; - public final fun getOriginalTitle ()Ljava/lang/String; - public final fun getPosterPath ()Ljava/lang/String; - public final fun getReleaseDate ()Ljava/lang/String; - public final fun getTitle ()Ljava/lang/String; - public final fun getVoteAverage ()Ljava/lang/Double; - public final fun getYear ()Ljava/lang/Integer; - public fun hashCode ()I - public final fun isTv ()Z - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSearchResult$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail$Companion; - public fun ()V - public fun (Ljava/lang/Integer;Ljava/util/List;)V - public synthetic fun (Ljava/lang/Integer;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/Integer; - public final fun component2 ()Ljava/util/List; - public final fun copy (Ljava/lang/Integer;Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail;Ljava/lang/Integer;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail; - public fun equals (Ljava/lang/Object;)Z - public final fun getEpisodes ()Ljava/util/List; - public final fun getSeasonNumber ()Ljava/lang/Integer; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonDetail$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary$Companion; - public fun ()V - public fun (Ljava/lang/Integer;Ljava/lang/Integer;)V - public synthetic fun (Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/Integer; - public final fun component2 ()Ljava/lang/Integer; - public final fun copy (Ljava/lang/Integer;Ljava/lang/Integer;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary; - public fun equals (Ljava/lang/Object;)Z - public final fun getEpisodeCount ()Ljava/lang/Integer; - public final fun getSeasonNumber ()Ljava/lang/Integer; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbSeasonSummary$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail$Companion; - public fun ()V - public fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;)V - public synthetic fun (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/Integer; - public final fun component10 ()Ljava/util/List; - public final fun component11 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; - public final fun component12 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; - public final fun component13 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; - public final fun component14 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; - public final fun component15 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; - public final fun component16 ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Ljava/lang/String; - public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Ljava/lang/String; - public final fun component7 ()Ljava/lang/Double; - public final fun component8 ()Ljava/util/List; - public final fun component9 ()Ljava/util/List; - public final fun copy (Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Double;Ljava/util/List;Ljava/util/List;Ljava/util/List;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail; - public fun equals (Ljava/lang/Object;)Z - public final fun getContentRatings ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbContentRatings; - public final fun getCredits ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbCredits; - public final fun getDisplayTitle ()Ljava/lang/String; - public final fun getEpisodeRunTime ()Ljava/util/List; - public final fun getExternalIds ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbIds; - public final fun getFirstAirDate ()Ljava/lang/String; - public final fun getGenres ()Ljava/util/List; - public final fun getId ()Ljava/lang/Integer; - public final fun getName ()Ljava/lang/String; - public final fun getOriginalName ()Ljava/lang/String; - public final fun getOverview ()Ljava/lang/String; - public final fun getPosterPath ()Ljava/lang/String; - public final fun getRecommendations ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; - public final fun getSeasons ()Ljava/util/List; - public final fun getSimilar ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbPageResult; - public final fun getVideos ()Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; - public final fun getVoteAverage ()Ljava/lang/Double; - public final fun getYear ()Ljava/lang/Integer; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbTvDetail$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo$Companion; - public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo; - public fun equals (Ljava/lang/Object;)Z - public final fun getKey ()Ljava/lang/String; - public final fun getSite ()Ljava/lang/String; - public final fun getType ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideo$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos { - public static final field Companion Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos$Companion; - public fun ()V - public fun (Ljava/util/List;)V - public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;Ljava/util/List;ILjava/lang/Object;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; - public fun equals (Ljava/lang/Object;)Z - public final fun getResults ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final synthetic class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class com/lagradost/cloudstream3/metaproviders/TmdbProvider$TmdbVideos$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - public class com/lagradost/cloudstream3/metaproviders/TraktProvider : com/lagradost/cloudstream3/MainAPI { public fun ()V public fun getHasMainPage ()Z @@ -7929,10 +7235,6 @@ public final class com/lagradost/cloudstream3/mvvm/ArchComponentExtKt { public static final fun throwAbleToResource (Ljava/lang/Throwable;)Lcom/lagradost/cloudstream3/mvvm/Resource; } -public final class com/lagradost/cloudstream3/mvvm/ArchComponentExt_jvmCommonKt { - public static final fun platformThrowAbleToResource (Ljava/lang/Throwable;)Lcom/lagradost/cloudstream3/mvvm/Resource; -} - public final class com/lagradost/cloudstream3/mvvm/DebugException : java/lang/Exception { public fun (Ljava/lang/String;)V } @@ -8136,10 +7438,6 @@ public final class com/lagradost/cloudstream3/utils/Coroutines { public final fun threadSafeListOf ([Ljava/lang/Object;)Ljava/util/List; } -public final class com/lagradost/cloudstream3/utils/Coroutines_jvmCommonKt { - public static final fun getWorkerDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher; -} - public final class com/lagradost/cloudstream3/utils/Coroutines_jvmKt { public static final fun runOnMainThreadNative (Lkotlin/jvm/functions/Function0;)V } @@ -8327,6 +7625,44 @@ public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser { public final fun parse (Ljava/lang/String;Ljava/lang/String;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$HlsMultivariantPlaylist; } +public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$C { + public static final field CENC_TYPE_cbc1 Ljava/lang/String; + public static final field CENC_TYPE_cbcs Ljava/lang/String; + public static final field CENC_TYPE_cenc Ljava/lang/String; + public static final field CENC_TYPE_cens Ljava/lang/String; + public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$C; + public static final field ROLE_FLAG_ALTERNATE I + public static final field ROLE_FLAG_CAPTION I + public static final field ROLE_FLAG_COMMENTARY I + public static final field ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND I + public static final field ROLE_FLAG_DESCRIBES_VIDEO I + public static final field ROLE_FLAG_DUB I + public static final field ROLE_FLAG_EASY_TO_READ I + public static final field ROLE_FLAG_EMERGENCY I + public static final field ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY I + public static final field ROLE_FLAG_MAIN I + public static final field ROLE_FLAG_SIGN I + public static final field ROLE_FLAG_SUBTITLE I + public static final field ROLE_FLAG_SUPPLEMENTARY I + public static final field ROLE_FLAG_TRANSCRIBES_DIALOG I + public static final field ROLE_FLAG_TRICK_PLAY I + public static final field SELECTION_FLAG_AUTOSELECT I + public static final field SELECTION_FLAG_DEFAULT I + public static final field SELECTION_FLAG_FORCED I + public static final field TRACK_TYPE_AUDIO I + public static final field TRACK_TYPE_CAMERA_MOTION I + public static final field TRACK_TYPE_DEFAULT I + public static final field TRACK_TYPE_IMAGE I + public static final field TRACK_TYPE_METADATA I + public static final field TRACK_TYPE_NONE I + public static final field TRACK_TYPE_TEXT I + public static final field TRACK_TYPE_UNKNOWN I + public static final field TRACK_TYPE_VIDEO I + public final fun getCLEARKEY_UUID ()Ljava/util/UUID; + public final fun getPLAYREADY_UUID ()Ljava/util/UUID; + public final fun getWIDEVINE_UUID ()Ljava/util/UUID; +} + public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$DrmInitData { public fun (Ljava/lang/String;[Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;)V public final fun component1 ()Ljava/lang/String; @@ -8592,7 +7928,7 @@ public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$ParserExce public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$PsshAtomUtil { public static final field INSTANCE Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$PsshAtomUtil; - public final fun buildPsshAtom (Lkotlin/uuid/Uuid;[Lkotlin/uuid/Uuid;[B)[B + public final fun buildPsshAtom (Ljava/util/UUID;[Ljava/util/UUID;[B)[B } public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$Rendition { @@ -8613,19 +7949,19 @@ public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$Rendition } public final class com/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData { - public fun (Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[B)V - public synthetic fun (Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[BILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun component1 ()Lkotlin/uuid/Uuid; + public fun (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[B)V + public synthetic fun (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[BILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/UUID; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()[B - public final fun copy (Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[B)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; - public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;Lkotlin/uuid/Uuid;Ljava/lang/String;Ljava/lang/String;[BILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; + public final fun copy (Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[B)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; + public static synthetic fun copy$default (Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData;Ljava/util/UUID;Ljava/lang/String;Ljava/lang/String;[BILjava/lang/Object;)Lcom/lagradost/cloudstream3/utils/HlsPlaylistParser$SchemeData; public fun equals (Ljava/lang/Object;)Z public final fun getData ()[B public final fun getLicenseServerUrl ()Ljava/lang/String; public final fun getMimeType ()Ljava/lang/String; - public final fun getUuid ()Lkotlin/uuid/Uuid; + public final fun getUuid ()Ljava/util/UUID; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 59a36ce7f..18579eb0c 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -59,12 +59,12 @@ kotlin { implementation(libs.kotlinx.atomicfu) implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.datetime) - implementation(libs.kotlinx.io.core) implementation(libs.kotlinx.serialization.json) // JSON Parser implementation(libs.ksoup) // HTML Parser implementation(libs.ktor.http) implementation(libs.nicehttp) // HTTP Library implementation(libs.rhino) // Run JavaScript + implementation(libs.tmdb.java) // TMDB API v3 Wrapper Made with RetroFit implementation(libs.bundles.cryptography) // Cryptography } 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/metaproviders/TmdbProvider.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt index 88dda631a..89f935da3 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TmdbProvider.kt @@ -1,9 +1,7 @@ package com.lagradost.cloudstream3.metaproviders -import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.Actor -import com.lagradost.cloudstream3.ActorData import com.lagradost.cloudstream3.Episode import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.HomePageList @@ -22,8 +20,6 @@ import com.lagradost.cloudstream3.SearchResponseList import com.lagradost.cloudstream3.TvSeriesLoadResponse import com.lagradost.cloudstream3.TvSeriesSearchResponse import com.lagradost.cloudstream3.TvType -import com.lagradost.cloudstream3.addDate -import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.newEpisode import com.lagradost.cloudstream3.newHomePageResponse import com.lagradost.cloudstream3.newMovieLoadResponse @@ -32,22 +28,35 @@ import com.lagradost.cloudstream3.newTvSeriesLoadResponse import com.lagradost.cloudstream3.newTvSeriesSearchResponse import com.lagradost.cloudstream3.runAllAsync import com.lagradost.cloudstream3.toNewSearchResponseList -import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable +import com.uwetrottmann.tmdb2.Tmdb +import com.uwetrottmann.tmdb2.entities.AppendToResponse +import com.uwetrottmann.tmdb2.entities.BaseMovie +import com.uwetrottmann.tmdb2.entities.BaseTvShow +import com.uwetrottmann.tmdb2.entities.CastMember +import com.uwetrottmann.tmdb2.entities.ContentRating +import com.uwetrottmann.tmdb2.entities.Movie +import com.uwetrottmann.tmdb2.entities.ReleaseDate +import com.uwetrottmann.tmdb2.entities.ReleaseDatesResult +import com.uwetrottmann.tmdb2.entities.TvSeason +import com.uwetrottmann.tmdb2.entities.TvShow +import com.uwetrottmann.tmdb2.entities.Videos +import com.uwetrottmann.tmdb2.enumerations.AppendToResponseItem +import com.uwetrottmann.tmdb2.enumerations.VideoType +import retrofit2.awaitResponse +import retrofit2.Response +import java.util.Calendar /** * episode and season starting from 1 * they are null if movie - */ -@Serializable + * */ data class TmdbLink( - @JsonProperty("imdbID") @SerialName("imdbID") val imdbID: String?, - @JsonProperty("tmdbID") @SerialName("tmdbID") val tmdbID: Int?, - @JsonProperty("episode") @SerialName("episode") val episode: Int?, - @JsonProperty("season") @SerialName("season") val season: Int?, - @JsonProperty("movieName") @SerialName("movieName") val movieName: String? = null, + @JsonProperty("imdbID") val imdbID: String?, + @JsonProperty("tmdbID") val tmdbID: Int?, + @JsonProperty("episode") val episode: Int?, + @JsonProperty("season") val season: Int?, + @JsonProperty("movieName") val movieName: String? = null, ) open class TmdbProvider : MainAPI() { @@ -58,184 +67,19 @@ open class TmdbProvider : MainAPI() { open val useMetaLoadResponse = false open val apiName = "TMDB" - // As some sites don't support s0 + // As some sites doesn't support s0 open val disableSeasonZero = true override val hasMainPage = true override val providerType = ProviderType.MetaProvider - private val tmdbApiKey = "e6333b32409e02a4a6eba6fb7ff866bb" - private val tmdbApiUrl = "https://api.themoviedb.org/3" - - @Serializable - data class TmdbIds( - @JsonProperty("imdb_id") @SerialName("imdb_id") val imdbId: String? = null, - @JsonProperty("tvdb_id") @SerialName("tvdb_id") val tvdbId: Int? = null, - ) - - @Serializable - data class TmdbGenre( - @JsonProperty("id") @SerialName("id") val id: Int? = null, - @JsonProperty("name") @SerialName("name") val name: String? = null, - ) - - @Serializable - data class TmdbCastMember( - @JsonProperty("name") @SerialName("name") val name: String? = null, - @JsonProperty("character") @SerialName("character") val character: String? = null, - @JsonProperty("profile_path") @SerialName("profile_path") val profilePath: String? = null, - ) - - @Serializable - data class TmdbCredits( - @JsonProperty("cast") @SerialName("cast") val cast: List? = null, - ) - - @Serializable - data class TmdbVideo( - @JsonProperty("key") @SerialName("key") val key: String? = null, - @JsonProperty("site") @SerialName("site") val site: String? = null, - @JsonProperty("type") @SerialName("type") val type: String? = null, - ) - - @Serializable - data class TmdbVideos( - @JsonProperty("results") @SerialName("results") val results: List? = null, - ) - - // Shared between movie and tv search results - @Serializable - data class TmdbSearchResult( - @JsonProperty("id") @SerialName("id") val id: Int? = null, - @JsonProperty("title") @SerialName("title") val title: String? = null, // movies - @JsonProperty("original_title") @SerialName("original_title") val originalTitle: String? = null, - @JsonProperty("name") @SerialName("name") val name: String? = null, // tv - @JsonProperty("original_name") @SerialName("original_name") val originalName: String? = null, - @JsonProperty("poster_path") @SerialName("poster_path") val posterPath: String? = null, - @JsonProperty("vote_average") @SerialName("vote_average") val voteAverage: Double? = null, - @JsonProperty("release_date") @SerialName("release_date") val releaseDate: String? = null, - @JsonProperty("first_air_date") @SerialName("first_air_date") val firstAirDate: String? = null, - @JsonProperty("media_type") @SerialName("media_type") val mediaType: String? = null, // for multi-search - ) { - @get:JsonIgnore val isTv get() = name != null || mediaType == "tv" - @get:JsonIgnore val displayTitle get() = title ?: originalTitle ?: name ?: originalName ?: "" - @get:JsonIgnore val year get() = (releaseDate ?: firstAirDate)?.take(4)?.toIntOrNull() - } - - @Serializable - data class TmdbPageResult( - @JsonProperty("results") @SerialName("results") val results: List? = null, - @JsonProperty("total_pages") @SerialName("total_pages") val totalPages: Int? = null, - @JsonProperty("total_results") @SerialName("total_results") val totalResults: Int? = null, - ) - - @Serializable - data class TmdbMultiResult( - @JsonProperty("results") @SerialName("results") val results: List? = null, - ) - - @Serializable - data class TmdbEpisode( - @JsonProperty("id") @SerialName("id") val id: Int? = null, - @JsonProperty("name") @SerialName("name") val name: String? = null, - @JsonProperty("overview") @SerialName("overview") val overview: String? = null, - @JsonProperty("episode_number") @SerialName("episode_number") val episodeNumber: Int? = null, - @JsonProperty("season_number") @SerialName("season_number") val seasonNumber: Int? = null, - @JsonProperty("still_path") @SerialName("still_path") val stillPath: String? = null, - @JsonProperty("air_date") @SerialName("air_date") val airDate: String? = null, - @JsonProperty("vote_average") @SerialName("vote_average") val voteAverage: Double? = null, - @JsonProperty("external_ids") @SerialName("external_ids") val externalIds: TmdbIds? = null, - ) - - @Serializable - data class TmdbSeasonDetail( - @JsonProperty("season_number") @SerialName("season_number") val seasonNumber: Int? = null, - @JsonProperty("episodes") @SerialName("episodes") val episodes: List? = null, - ) - - @Serializable - data class TmdbSeasonSummary( - @JsonProperty("season_number") @SerialName("season_number") val seasonNumber: Int? = null, - @JsonProperty("episode_count") @SerialName("episode_count") val episodeCount: Int? = null, - ) - - @Serializable - data class TmdbContentRating( - @JsonProperty("iso_3166_1") @SerialName("iso_3166_1") val country: String? = null, - @JsonProperty("rating") @SerialName("rating") val rating: String? = null, - ) - - @Serializable - data class TmdbContentRatings( - @JsonProperty("results") @SerialName("results") val results: List? = null, - ) - - @Serializable - data class TmdbReleaseDateEntry( - @JsonProperty("certification") @SerialName("certification") val certification: String? = null, - @JsonProperty("type") @SerialName("type") val type: Int? = null, - ) - - @Serializable - data class TmdbReleaseDateResult( - @JsonProperty("iso_3166_1") @SerialName("iso_3166_1") val country: String? = null, - @JsonProperty("release_dates") @SerialName("release_dates") val releaseDates: List? = null, - ) - - @Serializable - data class TmdbReleaseDates( - @JsonProperty("results") @SerialName("results") val results: List? = null, - ) - - @Serializable - data class TmdbTvDetail( - @JsonProperty("id") @SerialName("id") val id: Int? = null, - @JsonProperty("name") @SerialName("name") val name: String? = null, - @JsonProperty("original_name") @SerialName("original_name") val originalName: String? = null, - @JsonProperty("overview") @SerialName("overview") val overview: String? = null, - @JsonProperty("poster_path") @SerialName("poster_path") val posterPath: String? = null, - @JsonProperty("first_air_date") @SerialName("first_air_date") val firstAirDate: String? = null, - @JsonProperty("vote_average") @SerialName("vote_average") val voteAverage: Double? = null, - @JsonProperty("genres") @SerialName("genres") val genres: List? = null, - @JsonProperty("episode_run_time") @SerialName("episode_run_time") val episodeRunTime: List? = null, - @JsonProperty("seasons") @SerialName("seasons") val seasons: List? = null, - @JsonProperty("external_ids") @SerialName("external_ids") val externalIds: TmdbIds? = null, - @JsonProperty("videos") @SerialName("videos") val videos: TmdbVideos? = null, - @JsonProperty("credits") @SerialName("credits") val credits: TmdbCredits? = null, - @JsonProperty("recommendations") @SerialName("recommendations") val recommendations: TmdbPageResult? = null, - @JsonProperty("similar") @SerialName("similar") val similar: TmdbPageResult? = null, - @JsonProperty("content_ratings") @SerialName("content_ratings") val contentRatings: TmdbContentRatings? = null, - ) { - @get:JsonIgnore val displayTitle get() = name ?: originalName ?: "" - @get:JsonIgnore val year get() = firstAirDate?.take(4)?.toIntOrNull() - } - - @Serializable - data class TmdbMovieDetail( - @JsonProperty("id") @SerialName("id") val id: Int? = null, - @JsonProperty("title") @SerialName("title") val title: String? = null, - @JsonProperty("original_title") @SerialName("original_title") val originalTitle: String? = null, - @JsonProperty("overview") @SerialName("overview") val overview: String? = null, - @JsonProperty("poster_path") @SerialName("poster_path") val posterPath: String? = null, - @JsonProperty("release_date") @SerialName("release_date") val releaseDate: String? = null, - @JsonProperty("vote_average") @SerialName("vote_average") val voteAverage: Double? = null, - @JsonProperty("genres") @SerialName("genres") val genres: List? = null, - @JsonProperty("runtime") @SerialName("runtime") val runtime: Int? = null, - @JsonProperty("imdb_id") @SerialName("imdb_id") val imdbId: String? = null, - @JsonProperty("external_ids") @SerialName("external_ids") val externalIds: TmdbIds? = null, - @JsonProperty("videos") @SerialName("videos") val videos: TmdbVideos? = null, - @JsonProperty("credits") @SerialName("credits") val credits: TmdbCredits? = null, - @JsonProperty("recommendations") @SerialName("recommendations") val recommendations: TmdbPageResult? = null, - @JsonProperty("similar") @SerialName("similar") val similar: TmdbPageResult? = null, - @JsonProperty("release_dates") @SerialName("release_dates") val releaseDates: TmdbReleaseDates? = null, - ) { - @get:JsonIgnore val displayTitle get() = title ?: originalTitle ?: "" - @get:JsonIgnore val year get() = releaseDate?.take(4)?.toIntOrNull() - } + // Fuck it, public private api key because github actions won't co-operate. + // Please no stealy. + private val tmdb = Tmdb("e6333b32409e02a4a6eba6fb7ff866bb") private fun getImageUrl(link: String?): String? { - link ?: return null - return if (link.startsWith("/")) "https://image.tmdb.org/t/p/w500$link" else link + if (link == null) return null + return if (link.startsWith("/")) "https://image.tmdb.org/t/p/w500/$link" else link } private fun getUrl(id: Int?, tvShow: Boolean): String { @@ -243,197 +87,199 @@ open class TmdbProvider : MainAPI() { else "https://www.themoviedb.org/movie/${id ?: -1}" } - private suspend fun getApi(path: String, extraParams: Map = emptyMap()): String { - val params = buildMap { - put("api_key", tmdbApiKey) - putAll(extraParams) - } - return app.get( - url = "$tmdbApiUrl$path", - params = params, - ).text - } - - private fun TmdbSearchResult.toSearchResponse() = if (isTv) { - newTvSeriesSearchResponse( - name = displayTitle, + private fun BaseTvShow.toSearchResponse(): TvSeriesSearchResponse { + return newTvSeriesSearchResponse( + name = this.name ?: this.original_name, url = getUrl(id, true), type = TvType.TvSeries, - fix = false, + fix = false ) { this.id = this@toSearchResponse.id - this.posterUrl = getImageUrl(posterPath) - this.score = Score.from10(voteAverage) - this.year = this@toSearchResponse.year + this.posterUrl = getImageUrl(poster_path) + this.score = Score.from10(vote_average) + this.year = first_air_date?.let { + Calendar.getInstance().apply { + time = it + }.get(Calendar.YEAR) + } } - } else { - newMovieSearchResponse( - name = displayTitle, + } + + private fun BaseMovie.toSearchResponse(): MovieSearchResponse { + return newMovieSearchResponse( + name = this.title ?: this.original_title, url = getUrl(id, false), type = TvType.Movie, - fix = false, + fix = false ) { this.id = this@toSearchResponse.id - this.posterUrl = getImageUrl(posterPath) - this.score = Score.from10(voteAverage) - this.year = this@toSearchResponse.year - } - } - - private fun List?.toActors(): List>? { - return this?.mapNotNull { - it ?: return@mapNotNull null - Pair( - Actor(it.name ?: return@mapNotNull null, getImageUrl(it.profilePath)), - it.character, - ) - } - } - - private fun TmdbVideos?.toTrailers(): List? { - val skipTypes = setOf("Opening Credits", "Featurette") - return this?.results - ?.filter { it.type !in skipTypes } - ?.sortedBy { it.type } - ?.mapNotNull { - when (it.site?.trim()?.lowercase()) { - "youtube" -> "https://www.youtube.com/watch?v=${it.key}" - else -> null - } + this.posterUrl = getImageUrl(poster_path) + this.score = Score.from10(vote_average) + this.year = release_date?.let { + Calendar.getInstance().apply { + time = it + }.get(Calendar.YEAR) } + } } - open suspend fun fetchContentRating(id: Int?, country: String): String? { - id ?: return null - // Try TV content ratings first - val tvRating = parseJson( - getApi("/tv/$id/content_ratings") - ).results?.firstOrNull { it.country == country }?.rating - if (tvRating != null) return tvRating - - // Fall back to movie release dates - return parseJson( - getApi("/movie/$id/release_dates") - ).results?.firstOrNull { it.country == country } - ?.releaseDates?.firstOrNull { !it.certification.isNullOrBlank() } - ?.certification - } - - private suspend fun TmdbTvDetail.toLoadResponse(): TvSeriesLoadResponse { - val episodes = mutableListOf() - val validSeasons = seasons?.filter { !disableSeasonZero || (it.seasonNumber ?: 0) != 0 } - ?: emptyList() - - for (season in validSeasons) { - val seasonNum = season.seasonNumber ?: continue - val fullSeason = parseJson( - getApi("/tv/$id/season/$seasonNum", mapOf("append_to_response" to "external_ids")) + private fun List?.toActors(): List>? { + return this?.mapNotNull { + Pair( + Actor(it?.name ?: return@mapNotNull null, getImageUrl(it.profile_path)), + it.character ) + } + } + + private suspend fun TvShow.toLoadResponse(): TvSeriesLoadResponse { + val tvSeasonsService = tmdb.tvSeasonsService() + val episodes = mutableListOf() + + val validSeasons = this.seasons?.filter { !disableSeasonZero || (it.season_number ?: 0) != 0 } ?: emptyList() + for (season in validSeasons) { + val seasonNumber = season.season_number ?: continue + + val response: Response = tmdb.tvSeasonsService() + .season(this.id, seasonNumber, "external_ids,images,episodes") + .awaitResponse() + + val fullSeason = response.body() ?: continue fullSeason.episodes?.forEach { episode -> episodes += newEpisode( TmdbLink( - episode.externalIds?.imdbId ?: externalIds?.imdbId, - id, - episode.episodeNumber, - episode.seasonNumber, - displayTitle, + episode.external_ids?.imdb_id ?: this.external_ids?.imdb_id, + this.id, + episode.episode_number, + episode.season_number, + this.name ?: this.original_name ).toJson() ) { this.name = episode.name - this.season = episode.seasonNumber - this.episode = episode.episodeNumber - this.score = Score.from10(episode.voteAverage) + this.season = episode.season_number + this.episode = episode.episode_number + this.score = Score.from10(episode.vote_average) this.description = episode.overview - this.posterUrl = getImageUrl(episode.stillPath) - this.addDate(episode.airDate) + this.date = episode.air_date?.time + this.posterUrl = getImageUrl(episode.still_path) } } } return newTvSeriesLoadResponse( - displayTitle, + this.name ?: this.original_name, getUrl(id, true), TvType.TvSeries, - episodes, + episodes ) { - posterUrl = getImageUrl(posterPath) - this.year = this@toLoadResponse.year + posterUrl = getImageUrl(poster_path) + year = first_air_date?.let { + Calendar.getInstance().apply { + time = it + }.get(Calendar.YEAR) + } plot = overview - addImdbId(externalIds?.imdbId) + addImdbId(external_ids?.imdb_id) tags = genres?.mapNotNull { it.name } - duration = episodeRunTime?.average()?.toInt() - score = Score.from10(voteAverage) + duration = episode_run_time?.average()?.toInt() + score = Score.from10(vote_average) addTrailer(videos.toTrailers()) recommendations = (this@toLoadResponse.recommendations ?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() } addActors(credits?.cast?.toList().toActors()) - contentRating = contentRatings?.results?.firstOrNull { it.country == "US" }?.rating - ?: fetchContentRating(id, "US") + contentRating = fetchContentRating(id, "US") } } - private suspend fun TmdbMovieDetail.toLoadResponse(): MovieLoadResponse { + private fun Videos?.toTrailers(): List? { + return this?.results?.filter { it.type != VideoType.OPENING_CREDITS && it.type != VideoType.FEATURETTE } + ?.sortedBy { it.type?.ordinal ?: 10000 } + ?.mapNotNull { + when (it.site?.trim()?.lowercase()) { + "youtube" -> { // TODO FILL SITES + "https://www.youtube.com/watch?v=${it.key}" + } + else -> null + } + } + } + + private suspend fun Movie.toLoadResponse(): MovieLoadResponse { return newMovieLoadResponse( - displayTitle, - getUrl(id, false), - TvType.Movie, - TmdbLink( - imdbId ?: externalIds?.imdbId, - id, + this.title ?: this.original_title, getUrl(id, false), TvType.Movie, TmdbLink( + this.imdb_id, + this.id, null, null, - displayTitle, + this.title ?: this.original_title, ).toJson() ) { - posterUrl = getImageUrl(posterPath) - this.year = this@toLoadResponse.year + posterUrl = getImageUrl(poster_path) + year = release_date?.let { + Calendar.getInstance().apply { + time = it + }.get(Calendar.YEAR) + } plot = overview - addImdbId(imdbId ?: externalIds?.imdbId) + addImdbId(external_ids?.imdb_id) tags = genres?.mapNotNull { it.name } duration = runtime - score = Score.from10(voteAverage) + score = Score.from10(vote_average) addTrailer(videos.toTrailers()) + recommendations = (this@toLoadResponse.recommendations ?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() } addActors(credits?.cast?.toList().toActors()) - contentRating = releaseDates?.results - ?.firstOrNull { it.country == "US" } - ?.releaseDates?.firstOrNull { !it.certification.isNullOrBlank() } - ?.certification + + contentRating = fetchContentRating(id, "US") } } override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { + + // SAME AS DISCOVER IT SEEMS +// val popularSeries = tmdb.tvService().popular(page, "en-US").execute().body()?.results?.map { +// it.toSearchResponse() +// } ?: listOf() +// +// val popularMovies = +// tmdb.moviesService().popular(page, "en-US", "840").execute().body()?.results?.map { +// it.toSearchResponse() +// } ?: listOf() + var discoverMovies: List = listOf() var discoverSeries: List = listOf() var topMovies: List = listOf() var topSeries: List = listOf() runAllAsync( { - discoverMovies = parseJson( - getApi("/discover/movie", mapOf("page" to "$page")) - ).results?.map { it.toSearchResponse() as MovieSearchResponse } ?: listOf() - }, - { - discoverSeries = parseJson( - getApi("/discover/tv", mapOf("page" to "$page")) - ).results?.map { it.toSearchResponse() as TvSeriesSearchResponse } ?: listOf() - }, - { - topMovies = parseJson( - getApi("/movie/top_rated", mapOf("page" to "$page", "language" to "en-US", "region" to "US")) - ).results?.map { it.toSearchResponse() as MovieSearchResponse } ?: listOf() - }, - { - topSeries = parseJson( - getApi("/tv/top_rated", mapOf("page" to "$page", "language" to "en-US")) - ).results?.map { it.toSearchResponse() as TvSeriesSearchResponse } ?: listOf() - }, + discoverMovies = tmdb.discoverMovie().page(page).build().awaitResponse().body()?.results?.map { + it.toSearchResponse() + } ?: listOf() + }, { + discoverSeries = tmdb.discoverTv().page(page).build().awaitResponse().body()?.results?.map { + it.toSearchResponse() + } ?: listOf() + }, { + // https://en.wikipedia.org/wiki/ISO_3166-1 + topMovies = + tmdb.moviesService().topRated(page, "en-US", "US").awaitResponse() + .body()?.results?.map { + it.toSearchResponse() + } ?: listOf() + }, { + topSeries = + tmdb.tvService().topRated(page, "en-US").awaitResponse().body()?.results?.map { + it.toSearchResponse() + } ?: listOf() + } ) return newHomePageResponse( listOf( +// HomePageList("Popular Series", popularSeries), +// HomePageList("Popular Movies", popularMovies), HomePageList("Popular Movies", discoverMovies), HomePageList("Popular Series", discoverSeries), HomePageList("Top Movies", topMovies), @@ -442,14 +288,47 @@ open class TmdbProvider : MainAPI() { ) } - open fun loadFromImdb(imdb: String, seasons: List): LoadResponse? = null - open fun loadFromTmdb(tmdbId: Int, seasons: List): LoadResponse? = null - open fun loadFromImdb(imdb: String): LoadResponse? = null - open fun loadFromTmdb(tmdbId: Int): LoadResponse? = null + open fun loadFromImdb(imdb: String, seasons: List): LoadResponse? { + return null + } + open fun loadFromTmdb(tmdb: Int, seasons: List): LoadResponse? { + return null + } + + open fun loadFromImdb(imdb: String): LoadResponse? { + return null + } + + open fun loadFromTmdb(tmdb: Int): LoadResponse? { + return null + } + + open suspend fun fetchContentRating(id: Int?, country: String): String? { + id ?: return null + + val contentRatings = tmdb.tvService().content_ratings(id).awaitResponse().body()?.results + return if (!contentRatings.isNullOrEmpty()) { + contentRatings.firstOrNull { it: ContentRating -> + it.iso_3166_1 == country + }?.rating + } else { + val releaseDates = tmdb.moviesService().releaseDates(id).awaitResponse().body()?.results + val certification = releaseDates?.firstOrNull { it: ReleaseDatesResult -> + it.iso_3166_1 == country + }?.release_dates?.firstOrNull { it: ReleaseDate -> + !it.certification.isNullOrBlank() + }?.certification + + certification + } + } + + // Possible to add recommendations and such here. override suspend fun load(url: String): LoadResponse? { // https://www.themoviedb.org/movie/7445-brothers // https://www.themoviedb.org/tv/71914-the-wheel-of-time + val idRegex = Regex("""themoviedb\.org/(.*)/(\d+)""") val found = idRegex.find(url) @@ -458,64 +337,86 @@ open class TmdbProvider : MainAPI() { ?: throw ErrorLoadingException("No id found") return if (useMetaLoadResponse) { - if (isTvSeries) { - val detail = parseJson( - getApi( - "/tv/$id", - mapOf( - "language" to "en-US", - "append_to_response" to "external_ids,videos,credits,recommendations,similar,content_ratings", + return if (isTvSeries) { + val body = tmdb.tvService() + .tv( + id, + "en-US", + AppendToResponse( + AppendToResponseItem.EXTERNAL_IDS, + AppendToResponseItem.VIDEOS ) ) - ) - detail.toLoadResponse() + .awaitResponse().body() + val response = body?.toLoadResponse() + if (response != null) { + if (response.recommendations.isNullOrEmpty()) + tmdb.tvService().recommendations(id, 1, "en-US").awaitResponse().body() + ?.let { + it.results?.map { res -> res.toSearchResponse() } + }?.let { list -> + response.recommendations = list + } + + if (response.actors.isNullOrEmpty()) + tmdb.tvService().credits(id, "en-US").awaitResponse().body()?.let { + response.addActors(it.cast?.toActors()) + } + } + + response } else { - val detail = parseJson( - getApi( - "/movie/$id", - mapOf( - "language" to "en-US", - "append_to_response" to "external_ids,videos,credits,recommendations,similar,release_dates", + val body = tmdb.moviesService() + .summary( + id, + "en-US", + AppendToResponse( + AppendToResponseItem.EXTERNAL_IDS, + AppendToResponseItem.VIDEOS ) ) - ) - detail.toLoadResponse() + .awaitResponse().body() + val response = body?.toLoadResponse() + if (response != null) { + if (response.recommendations.isNullOrEmpty()) + tmdb.moviesService().recommendations(id, 1, "en-US").awaitResponse().body() + ?.let { + it.results?.map { res -> res.toSearchResponse() } + }?.let { list -> + response.recommendations = list + } + + if (response.actors.isNullOrEmpty()) + tmdb.moviesService().credits(id).awaitResponse().body()?.let { + response.addActors(it.cast?.toActors()) + } + } + response } } else { loadFromTmdb(id)?.let { return it } if (isTvSeries) { - val externalIds = parseJson(getApi("/tv/$id/external_ids")) - val imdbId = externalIds.imdbId - if (imdbId != null) { - val fromImdb = loadFromImdb(imdbId) - if (fromImdb != null) return fromImdb - } - val seasons = parseJson(getApi("/tv/$id")).seasons ?: listOf() - if (imdbId != null) { - loadFromImdb(imdbId, seasons) ?: loadFromTmdb(id, seasons) - } else { - loadFromTmdb(id, seasons) + tmdb.tvService().externalIds(id).awaitResponse().body()?.imdb_id?.let { + val fromImdb = loadFromImdb(it) + val result = if (fromImdb == null) { + val details = tmdb.tvService().tv(id, "en-US").awaitResponse().body() + loadFromImdb(it, details?.seasons ?: listOf()) + ?: loadFromTmdb(id, details?.seasons ?: listOf()) + } else fromImdb + + result } } else { - val imdbId = parseJson(getApi("/movie/$id")).imdbId - if (imdbId != null) loadFromImdb(imdbId) else null + tmdb.moviesService().externalIds(id).awaitResponse() + .body()?.imdb_id?.let { loadFromImdb(it) } } } } override suspend fun search(query: String, page: Int): SearchResponseList? { - return parseJson( - getApi( - "/search/multi", - mapOf( - "query" to query, - "page" to "$page", - "language" to "en-US", - "include_adult" to "$includeAdult", - ) - ) - ).results?.mapNotNull { - if (it.mediaType == "person") null else it.toSearchResponse() - }?.toNewSearchResponseList() + return tmdb.searchService().multi(query, page, "en-US", "US", includeAdult).awaitResponse() + .body()?.results?.mapNotNull { + it.movie?.toSearchResponse() ?: it.tvShow?.toSearchResponse() + }?.toNewSearchResponseList() } -} +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index 73de880db..4cd8eefa6 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -4,11 +4,15 @@ import androidx.annotation.AnyThread import com.lagradost.api.Log import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.utils.AppDebug -import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.WorkerThread import kotlinx.coroutines.* +import java.io.InterruptedIOException +import java.net.SocketTimeoutException +import java.net.UnknownHostException +import javax.net.ssl.SSLHandshakeException import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext +import kotlin.reflect.full.NoSuchPropertyException const val DEBUG_EXCEPTION = "THIS IS A DEBUG EXCEPTION!" const val DEBUG_PRINT = "DEBUG PRINT" @@ -57,7 +61,7 @@ sealed class Resource { companion object { fun fromResult(result: Result) : Resource { val value = result.getOrNull() - return if (value != null) { + return if(value != null) { Success(value) } else { throwAbleToResource(result.exceptionOrNull() ?: Exception("this should not be possible")) @@ -68,6 +72,7 @@ sealed class Resource { fun logError(throwable: Throwable) { Log.d("ApiError", "-------------------------------------------------------------------") + Log.d("ApiError", "safeApiCall: " + throwable.localizedMessage) Log.d("ApiError", "safeApiCall: " + throwable.message) throwable.printStackTrace() Log.d("ApiError", "-------------------------------------------------------------------") @@ -124,21 +129,16 @@ suspend fun suspendSafeApiCall(apiCall: suspend () -> T): T? { } fun Throwable.getAllMessages(): String { - return (this.message ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "") + return (this.localizedMessage ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "") } fun Throwable.getStackTracePretty(showMessage: Boolean = true): String { - val prefix = if (showMessage) this.message?.let { "\n$it" } ?: "" else "" - return prefix + this.stackTraceToString() - .lines() - .mapNotNull { line -> - val trimmed = line.trim() - if (!trimmed.startsWith("at ")) return@mapNotNull null - // pull "File.kt:42" out of "at com.pkg.Class.method(File.kt:42)" - Regex("""\(([^)]+)\)$""").find(trimmed)?.groupValues?.get(1) - ?.replace(":", " ") - } - .joinToString("\n") + val prefix = if (showMessage) this.localizedMessage?.let { "\n$it" } ?: "" else "" + return prefix + this.stackTrace.joinToString( + separator = "\n" + ) { + "${it.fileName} ${it.lineNumber}" + } } fun safeFail(throwable: Throwable): Resource { @@ -162,26 +162,50 @@ fun CoroutineScope.launchSafe( return this.launch(context, start, obj) } -expect fun platformThrowAbleToResource(throwable: Throwable): Resource - fun throwAbleToResource( throwable: Throwable ): Resource { return when (throwable) { + is NoSuchMethodException, is NoSuchFieldException, is NoSuchMethodError, is NoSuchFieldError, is NoSuchPropertyException -> { + Resource.Failure( + false, + "App or extension is outdated, update the app or try pre-release.\n${throwable.message}" // todo add exact version? + ) + } + is NullPointerException -> { - val traceLine = throwable.stackTraceToString() - .lines() - .firstOrNull { it.contains("provider.kt", ignoreCase = true) } - ?.let { Regex("""\(([^)]+)\)$""").find(it)?.groupValues?.get(1) } - if (traceLine != null) { - return Resource.Failure( - false, - "NullPointerException at $traceLine\nSite might have updated or added Cloudflare/DDOS protection" - ) + for (line in throwable.stackTrace) { + if (line?.fileName?.endsWith("provider.kt", ignoreCase = true) == true) { + return Resource.Failure( + false, + "NullPointerException at ${line.fileName} ${line.lineNumber}\nSite might have updated or added Cloudflare/DDOS protection" + ) + } } safeFail(throwable) } + is SocketTimeoutException, is InterruptedIOException -> { + Resource.Failure( + true, + "Connection Timeout\nPlease try again later." + ) + } +// is HttpException -> { +// Resource.Failure( +// false, +// throwable.statusCode, +// null, +// throwable.message ?: "HttpException" +// ) +// } + is UnknownHostException -> { + Resource.Failure( + true, + "Cannot connect to server, try again later.\n${throwable.message}" + ) + } + is ErrorLoadingException -> { Resource.Failure( true, @@ -193,13 +217,20 @@ fun throwAbleToResource( Resource.Failure(false, "This operation is not implemented.") } + is SSLHandshakeException -> { + Resource.Failure( + true, + (throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS." + ) + } + is CancellationException -> { throwable.cause?.let { throwAbleToResource(it) } ?: safeFail(throwable) } - else -> platformThrowAbleToResource(throwable) + else -> safeFail(throwable) } } @@ -207,9 +238,9 @@ fun throwAbleToResource( suspend fun safeApiCall( @WorkerThread apiCall: suspend () -> T, ): Resource { - return apiCall.ioWork { work -> + return withContext(Dispatchers.IO) { try { - Resource.Success(work.invoke()) + Resource.Success(apiCall.invoke()) } catch (throwable: Throwable) { logError(throwable) throwAbleToResource(throwable) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt index 4a269c525..4536eb88b 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.* @AnyThread expect fun runOnMainThreadNative(@MainThread work: (() -> Unit)) -expect val workerDispatcher: CoroutineDispatcher internal expect annotation class WorkerThread() object Coroutines { @@ -25,7 +24,7 @@ object Coroutines { @AnyThread fun T.ioSafe(@WorkerThread work: suspend (CoroutineScope.(T) -> Unit)): Job { val value = this - return CoroutineScope(workerDispatcher).launchSafe { + return CoroutineScope(Dispatchers.IO).launchSafe { work(value) } } @@ -33,7 +32,7 @@ object Coroutines { @AnyThread suspend fun V.ioWorkSafe(@WorkerThread work: suspend (CoroutineScope.(V) -> T)): T? { val value = this - return withContext(workerDispatcher) { + return withContext(Dispatchers.IO) { try { work(value) } catch (e: Exception) { @@ -46,7 +45,7 @@ object Coroutines { @AnyThread suspend fun V.ioWork(@WorkerThread work: suspend (CoroutineScope.(V) -> T)): T { val value = this - return withContext(workerDispatcher) { + return withContext(Dispatchers.IO) { work(value) } } 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/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt index 3edd0f410..cd5d752b3 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -21,10 +21,9 @@ package com.lagradost.cloudstream3.utils import com.lagradost.cloudstream3.base64DecodeArray import io.ktor.http.Url -import kotlinx.io.Buffer -import kotlinx.io.IOException -import kotlinx.io.readByteArray -import kotlin.uuid.Uuid +import java.io.IOException +import java.nio.ByteBuffer +import java.util.UUID @Suppress("unused") object HlsPlaylistParser { @@ -187,13 +186,13 @@ object HlsPlaylistParser { data class SchemeData( /** - * The [Uuid] of the DRM scheme, or [C.UUID_NIL] if the data is universal (i.e. + * The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is universal (i.e. * applies to all schemes). */ - val uuid: Uuid, + val uuid: UUID, /** The URL of the server to which license requests should be made. May be null if unknown. */ val licenseServerUrl: String? = null, - /** The mimeType of [data]. */ + /** The mimeType of {@link #data}. */ val mimeType: String, /** The initialization data. May be null for scheme support checks only. */ val data: ByteArray @@ -455,14 +454,14 @@ object HlsPlaylistParser { // "." or "..", remove the appropriate segments of the path. if (i == segmentStart + 1 && url[segmentStart] == '.') { // Given "abc/def/./ghi", remove "./" to get "abc/def/ghi". - url.deleteRange(segmentStart, nextSegmentStart) + url.delete(segmentStart, nextSegmentStart) limit -= nextSegmentStart - segmentStart i = segmentStart } else if (i == segmentStart + 2 && url[segmentStart] == '.' && url[segmentStart + 1] == '.') { // Given "abc/def/../ghi", remove "def/../" to get "abc/ghi". val prevSegmentStart = url.lastIndexOf("/", segmentStart - 2) + 1 val removeFrom = if (prevSegmentStart > offset) prevSegmentStart else offset - url.deleteRange(removeFrom, nextSegmentStart) + url.delete(removeFrom, nextSegmentStart) limit -= nextSegmentStart - removeFrom segmentStart = prevSegmentStart i = prevSegmentStart @@ -533,31 +532,31 @@ object HlsPlaylistParser { } } - private object C { + object C { /** - * [Uuid] for the ClearKey DRM scheme. + * UUID for the ClearKey DRM scheme. * * * ClearKey is supported on Android devices running Android 5.0 (API Level 21) and up. */ - val CLEARKEY_UUID = Uuid.fromLongs(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL) + val CLEARKEY_UUID = UUID(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL) /** - * [Uuid] for the Widevine DRM scheme. + * UUID for the Widevine DRM scheme. * * * Widevine is supported on Android devices running Android 4.3 (API Level 18) and up. */ - val WIDEVINE_UUID = Uuid.fromLongs(-0x121074568629b532L, -0x5c37d8232ae2de13L) + val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L) /** - * [Uuid] for the PlayReady DRM scheme. + * UUID for the PlayReady DRM scheme. * * * PlayReady is supported on all AndroidTV devices. Note that most other Android devices do not * provide PlayReady support. */ - val PLAYREADY_UUID = Uuid.fromLongs(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL) + val PLAYREADY_UUID = UUID(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL) /** "cenc" scheme type name as defined in ISO/IEC 23001-7:2016. */ @@ -1067,7 +1066,7 @@ object HlsPlaylistParser { object PsshAtomUtil { fun buildPsshAtom( - systemId: Uuid, keyIds: Array?, data: ByteArray? + systemId: UUID, keyIds: Array?, data: ByteArray? ): ByteArray { val dataLength = data?.size ?: 0 var psshBoxLength: Int = @@ -1075,30 +1074,26 @@ object HlsPlaylistParser { if (keyIds != null) { psshBoxLength += 4 /* KID_count */ + (keyIds.size * 16) /* KIDs */ } - val buffer = Buffer() - buffer.writeInt(psshBoxLength) - buffer.writeInt(Mp4Box.TYPE_pssh) - buffer.writeInt(if (keyIds != null) 0x01000000 else 0 /* version=(buildV1Atom ? 1 : 0), flags=0 */) - systemId.toLongs { mostSignificantBits, leastSignificantBits -> - buffer.writeLong(mostSignificantBits) - buffer.writeLong(leastSignificantBits) - } + val psshBox: ByteBuffer = ByteBuffer.allocate(psshBoxLength) + psshBox.putInt(psshBoxLength) + psshBox.putInt(Mp4Box.TYPE_pssh) + psshBox.putInt(if (keyIds != null) 0x01000000 else 0 /* version=(buildV1Atom ? 1 : 0), flags=0 */) + psshBox.putLong(systemId.mostSignificantBits) + psshBox.putLong(systemId.leastSignificantBits) if (keyIds != null) { - buffer.writeInt(keyIds.size) + psshBox.putInt(keyIds.size) for (keyId in keyIds) { - keyId.toLongs { mostSignificantBits, leastSignificantBits -> - buffer.writeLong(mostSignificantBits) - buffer.writeLong(leastSignificantBits) - } + psshBox.putLong(keyId.mostSignificantBits) + psshBox.putLong(keyId.leastSignificantBits) } } if (data != null && data.size != 0) { - buffer.writeInt(data.size) - buffer.write(data) + psshBox.putInt(data.size) + psshBox.put(data) } else { - buffer.writeInt(0) + psshBox.putInt(0) } - return buffer.readByteArray() + return psshBox.array() } } diff --git a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExtTest.kt b/library/src/commonTest/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExtTest.kt deleted file mode 100644 index 970fe728d..000000000 --- a/library/src/commonTest/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExtTest.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.lagradost.cloudstream3.mvvm - -import kotlin.test.Test -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class ArchComponentExtTest { - - @Test - fun getStackTracePrettyStripsFullyQualifiedClassNames() { - val throwable = try { - throw IllegalStateException("Exception") - } catch (t: Throwable) { - t - } - - val pretty = throwable.getStackTracePretty() - - // Should not contain the fully qualified package/class prefix - assertFalse( - pretty.contains("com.lagradost.cloudstream3.mvvm.ArchComponentExtTest"), - "Expected no fully qualified class names, got:\n$pretty" - ) - - // Should still reference the source file itself - assertTrue( - pretty.contains("ArchComponentExtTest.kt"), - "Expected pretty stack trace to reference a .kt file, got:\n$pretty" - ) - } -} diff --git a/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.jvmCommon.kt b/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.jvmCommon.kt deleted file mode 100644 index f2ff5e3ac..000000000 --- a/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.jvmCommon.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.lagradost.cloudstream3.mvvm - -import java.io.InterruptedIOException -import java.net.SocketTimeoutException -import java.net.UnknownHostException -import javax.net.ssl.SSLHandshakeException -import kotlin.reflect.full.NoSuchPropertyException - -actual fun platformThrowAbleToResource(throwable: Throwable): Resource { - return when (throwable) { - is NoSuchMethodException, is NoSuchFieldException, is NoSuchMethodError, is NoSuchFieldError, is NoSuchPropertyException -> { - Resource.Failure( - false, - "App or extension is outdated, update the app or try pre-release.\n${throwable.message}" // todo add exact version? - ) - } - is SocketTimeoutException, is InterruptedIOException -> { - Resource.Failure( - true, - "Connection Timeout\nPlease try again later." - ) - } - is UnknownHostException -> { - Resource.Failure( - true, - "Cannot connect to server, try again later.\n${throwable.message}" - ) - } - is SSLHandshakeException -> { - Resource.Failure( - true, - (throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS." - ) - } - else -> safeFail(throwable) - } -} diff --git a/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt b/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt index 03a5be6c2..2ae886f59 100644 --- a/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt +++ b/library/src/jvmCommonMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.jvmCommon.kt @@ -1,7 +1,3 @@ package com.lagradost.cloudstream3.utils -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers - -actual val workerDispatcher: CoroutineDispatcher = Dispatchers.IO internal actual typealias WorkerThread = androidx.annotation.WorkerThread diff --git a/library/src/webMain/kotlin/com/lagradost/api/ContextHelper.web.kt b/library/src/webMain/kotlin/com/lagradost/api/ContextHelper.web.kt deleted file mode 100644 index 83cae11a5..000000000 --- a/library/src/webMain/kotlin/com/lagradost/api/ContextHelper.web.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.lagradost.api - -actual fun getContext(): Any? = null -actual fun setContext(context: Any?) = Unit diff --git a/library/src/webMain/kotlin/com/lagradost/api/Log.kt b/library/src/webMain/kotlin/com/lagradost/api/Log.kt deleted file mode 100644 index be185f0ca..000000000 --- a/library/src/webMain/kotlin/com/lagradost/api/Log.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.lagradost.api - -actual object Log { - actual fun d(tag: String, message: String) { - // There is no console.debug in Kotlin/JS (Yet) - // We will switch this when it is added. - console.log("[$tag] $message") - } - - actual fun i(tag: String, message: String) { - console.info("[$tag] $message") - } - - actual fun w(tag: String, message: String) { - console.warn("[$tag] $message") - } - - actual fun e(tag: String, message: String) { - console.error("[$tag] $message") - } -} diff --git a/library/src/webMain/kotlin/com/lagradost/cloudstream3/extractors/YoutubeExtractor.web.kt b/library/src/webMain/kotlin/com/lagradost/cloudstream3/extractors/YoutubeExtractor.web.kt deleted file mode 100644 index 07463bd56..000000000 --- a/library/src/webMain/kotlin/com/lagradost/cloudstream3/extractors/YoutubeExtractor.web.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.lagradost.cloudstream3.extractors - -import com.lagradost.cloudstream3.SubtitleFile -import com.lagradost.cloudstream3.utils.ExtractorApi -import com.lagradost.cloudstream3.utils.ExtractorLink - -/** - * Web/JS target shim. - * - * NewPipeExtractor is JVM-only, so the full extraction logic used on - * Android/desktop can't run here. This stub keeps the multiplatform - * source set compiling; it performs no extraction and simply reports - * that no links were found on this target. - * - * If/when a JS-compatible YouTube extraction path exists, replace the - * body of getUrl() with that implementation. - */ -actual open class YoutubeExtractor actual constructor() : ExtractorApi() { - - actual override val mainUrl = "https://www.youtube.com" - actual override val name = "YouTube" - actual override val requiresReferer = false - - actual override suspend fun getUrl( - url: String, - referer: String?, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit, - ) { - // No-op on web target: NewPipeExtractor is unavailable here. - } -} diff --git a/library/src/webMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.web.kt b/library/src/webMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.web.kt deleted file mode 100644 index f108bf7fb..000000000 --- a/library/src/webMain/kotlin/com/lagradost/cloudstream3/mvvm/ArchComponentExt.web.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.lagradost.cloudstream3.mvvm - -actual fun platformThrowAbleToResource(throwable: Throwable): Resource { - val message = throwable.message.orEmpty() - return when { - message.contains("timeout", ignoreCase = true) -> { - Resource.Failure( - true, - "Connection Timeout\nPlease try again later." - ) - } - - message.contains("NetworkError", ignoreCase = true) || - message.contains("Failed to fetch", ignoreCase = true) || - message.contains("ERR_NAME_NOT_RESOLVED", ignoreCase = true) -> { - Resource.Failure( - true, - "Cannot connect to server, try again later.\n${throwable.message}" - ) - } - - message.contains("SSL", ignoreCase = true) || - message.contains("certificate", ignoreCase = true) -> { - Resource.Failure( - true, - (throwable.message ?: "SSL error") + "\nTry a VPN or DNS." - ) - } - - else -> safeFail(throwable) - } -} diff --git a/library/src/webMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.web.kt b/library/src/webMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.web.kt deleted file mode 100644 index 3ea59f6cc..000000000 --- a/library/src/webMain/kotlin/com/lagradost/cloudstream3/utils/Coroutines.web.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.lagradost.cloudstream3.utils - -import androidx.annotation.AnyThread -import androidx.annotation.MainThread -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers - -actual val workerDispatcher: CoroutineDispatcher = Dispatchers.Default -internal actual annotation class WorkerThread() - -@AnyThread -actual fun runOnMainThreadNative(@MainThread work: () -> Unit) { - work.invoke() -} diff --git a/library/src/webTest/kotlin/com/lagradost/cloudstream3/utils/Ignore.web.kt b/library/src/webTest/kotlin/com/lagradost/cloudstream3/utils/Ignore.web.kt deleted file mode 100644 index 0381a1944..000000000 --- a/library/src/webTest/kotlin/com/lagradost/cloudstream3/utils/Ignore.web.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.lagradost.cloudstream3.utils - -import kotlin.test.Ignore - -actual typealias IgnoreOnWeb = Ignore 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 c0f784fc1..000000000 --- a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/DevicePreferenceStore.kt +++ /dev/null @@ -1,203 +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 startPaused = preferences.getBoolean("start_paused_key", false) - val skipOpEnabled = preferences.getBoolean("enable_skip_op_from_database", true) - val autoRotateEnabled = preferences.getBoolean("auto_rotate_video_key", true) - val rotateButtonEnabled = preferences.getBoolean("rotate_video_key", false) - val previewBarEnabled = preferences.getBoolean("preview_seekbar_key", true) - val softwareDecoding = preferences.getInt("software_decoding_key2", -1) - val extraBrightnessEnabled = preferences.getBoolean("extra_brightness_key", false) - val swipeHorizontalEnabled = preferences.getBoolean("swipe_enabled_key", true) - val swipeVerticalEnabled = preferences.getBoolean("swipe_vertical_enabled_key", true) - val doubleTapToSeekEnabled = preferences.getBoolean("double_tap_enabled_key", false) - val doubleTapToPauseEnabled = preferences.getBoolean("double_tap_pause_enabled_key", false) - val doubleTapTime = preferences.getInt("double_tap_seek_time_key2", 10) - val bufferDiskMB = preferences.getInt("video_buffer_disk_key", 0) - val bufferRamMB = preferences.getInt("video_buffer_size_key", 0) - val bufferTimeSec = preferences.getInt("video_buffer_length_key", 0) - val tvSeekOnTime = preferences.getInt("android_tv_interface_on_seek_key", 10) - val tvSeekOffTime = preferences.getInt("android_tv_interface_off_seek_key", 10) -} - -class GeneralPreferences(preferences: PreferenceStore) { - val locale = preferences.getString("app_locale", "") - val bananas = preferences.getInt("benene_count", 0) - val parallelDownloads = preferences.getInt("download_parallel_key", 3) - val concurrentConnections = preferences.getInt("download_concurrent_key", 3) - val batterOptimization = preferences.getBoolean("battery_optimisation_key", false) - - /** Please note that it used R.array.dns_pref before, this was a bug and caused this setting to be language sensitive **/ - val dns = preferences.getInt("dns_key", 0) - - /** Very weird setKey based usage, just having this setting might not do it */ - val jsdelivrProxy = preferences.getBoolean("jsdelivr_proxy_key", false) - - /** This should honesty be refactored to a single setting */ - val downloadPath = preferences.getString("download_path_key", "") - val downloadPathVisual = preferences.getString("download_path_key_visual", "") -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/lagradost/cloudstream4/compose/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 e3ff47625..000000000 --- a/shared/src/commonMain/kotlin/com/mihon/presentation/settings/Search.kt +++ /dev/null @@ -1,165 +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.remember -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.persistentListOf -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, - deferredItems: @Composable () -> 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) - } - - var items by remember { mutableStateOf>>(persistentListOf()) } - if (items.isEmpty() && searchKey.isNotEmpty()) { - items = deferredItems() - } - - 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