diff --git a/.github/downloads.jpg b/.github/downloads.jpg new file mode 100644 index 00000000..ca14a664 Binary files /dev/null and b/.github/downloads.jpg differ diff --git a/.github/home.jpg b/.github/home.jpg new file mode 100644 index 00000000..72370d3c Binary files /dev/null and b/.github/home.jpg differ diff --git a/.github/locales.py b/.github/locales.py deleted file mode 100644 index 7d6d6b90..00000000 --- a/.github/locales.py +++ /dev/null @@ -1,63 +0,0 @@ -import re -import glob -import requests -import lxml.etree as ET # builtin library doesn't preserve comments - - -SETTINGS_PATH = "app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt" -START_MARKER = "/* begin language list */" -END_MARKER = "/* end language list */" -XML_NAME = "app/src/main/res/values-" -ISO_MAP_URL = "https://raw.githubusercontent.com/haliaeetus/iso-639/master/data/iso_639-1.min.json" -INDENT = " "*4 - -iso_map = requests.get(ISO_MAP_URL, timeout=300).json() - -# Load settings file -src = open(SETTINGS_PATH, "r", encoding='utf-8').read() -before_src, rest = src.split(START_MARKER) -rest, after_src = rest.split(END_MARKER) - -# Load already added langs -languages = {} -for lang in re.finditer(r'Triple\("(.*)", "(.*)", "(.*)"\)', rest): - flag, name, iso = lang.groups() - languages[iso] = (flag, name) - -# Add not yet added langs -for folder in glob.glob(f"{XML_NAME}*"): - iso = folder[len(XML_NAME):] - if iso not in languages.keys(): - entry = iso_map.get(iso.lower(),{'nativeName':iso}) - languages[iso] = ("", entry['nativeName'].split(',')[0]) - -# Create triples -triples = [] -for iso in sorted(languages.keys()): - flag, name = languages[iso] - triples.append(f'{INDENT}Triple("{flag}", "{name}", "{iso}"),') - -# Update settings file -open(SETTINGS_PATH, "w+",encoding='utf-8').write( - before_src + - START_MARKER + - "\n" + - "\n".join(triples) + - "\n" + - END_MARKER + - after_src -) - -# Go through each values.xml file and fix escaped \@string -for file in glob.glob(f"{XML_NAME}*/strings.xml"): - try: - tree = ET.parse(file) - for child in tree.getroot(): - if child.text.startswith("\\@string/"): - print(f"[{file}] fixing {child.attrib['name']}") - child.text = child.text.replace("\\@string/", "@string/") - with open(file, 'wb') as fp: - fp.write(b'\n') - tree.write(fp, encoding="utf-8", method="xml", pretty_print=True, xml_declaration=False) - except ET.ParseError as ex: - print(f"[{file}] {ex}") diff --git a/.github/player.jpg b/.github/player.jpg new file mode 100644 index 00000000..f6959cf3 Binary files /dev/null and b/.github/player.jpg differ diff --git a/.github/results.jpg b/.github/results.jpg new file mode 100644 index 00000000..4dbc9b8d Binary files /dev/null and b/.github/results.jpg differ diff --git a/.github/search.jpg b/.github/search.jpg new file mode 100644 index 00000000..784bec89 Binary files /dev/null and b/.github/search.jpg differ diff --git a/.github/workflows/issue_action.yml b/.github/workflows/issue_action.yml index 108cec82..28b737b3 100644 --- a/.github/workflows/issue_action.yml +++ b/.github/workflows/issue_action.yml @@ -15,7 +15,6 @@ jobs: app_id: ${{ secrets.GH_APP_ID }} private_key: ${{ secrets.GH_APP_KEY }} - name: Similarity analysis - id: similarity uses: actions-cool/issues-similarity-analysis@v1 with: token: ${{ steps.generate_token.outputs.token }} @@ -25,18 +24,6 @@ jobs: ### Your issue looks similar to these issues: Please close if duplicate. comment-body: '${index}. ${similarity} #${number}' - - name: Label if possible duplicate - if: steps.similarity.outputs.similar-issues-found =='true' - uses: actions/github-script@v6 - with: - github-token: ${{ steps.generate_token.outputs.token }} - script: | - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ["possible duplicate"] - }) - uses: actions/checkout@v2 - name: Automatically close issues that dont follow the issue template uses: lucasbento/auto-close-issues@v1.0.2 @@ -66,18 +53,6 @@ jobs: Please do not report any provider bugs here. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the [discord](https://discord.gg/5Hus6fM). Found provider name: `${{ steps.provider_check.outputs.name }}` - - name: Label if mentions provider - if: steps.provider_check.outputs.name != 'none' - uses: actions/github-script@v6 - with: - github-token: ${{ steps.generate_token.outputs.token }} - script: | - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ["possible provider issue"] - }) - name: Add eyes reaction to all issues uses: actions-cool/emoji-helper@v1.0.0 with: diff --git a/.github/workflows/update_locales.yml b/.github/workflows/update_locales.yml deleted file mode 100644 index 628e9bc9..00000000 --- a/.github/workflows/update_locales.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Fix locale issues - -on: - workflow_dispatch: - push: - paths: - - '**.xml' - branches: - - master - -concurrency: - group: "locale" - cancel-in-progress: true - -jobs: - create: - runs-on: ubuntu-latest - steps: - - name: Generate access token - id: generate_token - uses: tibdex/github-app-token@v1 - with: - app_id: ${{ secrets.GH_APP_ID }} - private_key: ${{ secrets.GH_APP_KEY }} - repository: "recloudstream/cloudstream" - - uses: actions/checkout@v2 - with: - token: ${{ steps.generate_token.outputs.token }} - - name: Install dependencies - run: | - pip3 install lxml - - name: Edit files - run: | - python3 .github/locales.py - - name: Commit to the repo - run: | - git config --local user.email "111277985+recloudstream[bot]@users.noreply.github.com" - git config --local user.name "recloudstream[bot]" - git add . - # "echo" returns true so the build succeeds, even if no changed files - git commit -m 'chore(locales): fix locale issues' || echo - git push diff --git a/README.md b/README.md index e3d033ba..5e961c61 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,40 @@ [![Discord](https://invidget.switchblade.xyz/5Hus6fM)](https://discord.gg/5Hus6fM) -### Features: +***Features:*** + **AdFree**, No ads whatsoever + No tracking/analytics + Bookmarks + Download and stream movies, tv-shows and anime + Chromecast -### Supported languages: - - Translation status - +***Screenshots:*** + + + + +***The list of supported languages:*** +* 🇱🇧 Arabic +* 🇧🇬 Bulgarian +* 🇭🇷 Croatian +* 🇨🇿 Czech +* 🇳🇱 Dutch +* 🇬🇧 English +* 🇫🇷 French +* 🇩🇪 German +* 🇬🇷 Greek +* 🇮🇳 Hindi +* 🇮🇩 Indonesian +* 🇮🇹 Italian +* 🇲🇰 Macedonian +* 🇮🇳 Malayalam +* 🇳🇴 Norsk +* 🇵🇱 Polish +* 🇧🇷 Portuguese (Brazil) +* 🇷🇴 Romanian +* 🇪🇸 Spanish +* 🇸🇪 Swedish +* 🇵🇭 Tagalog +* 🇹🇷 Turkish +* 🇻🇳 Vietnamese + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0bd56fe7..4a1fcada 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -39,16 +39,16 @@ android { } } - compileSdk = 33 + compileSdk = 31 buildToolsVersion = "30.0.3" defaultConfig { applicationId = "com.lagradost.cloudstream3" minSdk = 21 - targetSdk = 33 + targetSdk = 30 - versionCode = 57 - versionName = "4.0.0" + versionCode = 54 + versionName = "3.2.2" resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") @@ -155,12 +155,10 @@ dependencies { // implementation("androidx.leanback:leanback-paging:1.1.0-alpha09") // Exoplayer - implementation("com.google.android.exoplayer:exoplayer:2.18.2") - implementation("com.google.android.exoplayer:extension-cast:2.18.2") - implementation("com.google.android.exoplayer:extension-mediasession:2.18.2") - implementation("com.google.android.exoplayer:extension-okhttp:2.18.2") - // Use the Jellyfin ffmpeg extension for easy ffmpeg audio decoding in exoplayer. Thank you Jellyfin <3 -// implementation("org.jellyfin.exoplayer:exoplayer-ffmpeg-extension:2.18.2+1") + implementation("com.google.android.exoplayer:exoplayer:2.18.1") + implementation("com.google.android.exoplayer:extension-cast:2.18.1") + implementation("com.google.android.exoplayer:extension-mediasession:2.18.1") + implementation("com.google.android.exoplayer:extension-okhttp:2.18.1") //implementation("com.google.android.exoplayer:extension-leanback:2.14.0") @@ -186,15 +184,14 @@ dependencies { //implementation("com.github.TorrentStream:TorrentStream-Android:2.7.0") // Downloading - implementation("androidx.work:work-runtime:2.8.0") - implementation("androidx.work:work-runtime-ktx:2.8.0") + implementation("androidx.work:work-runtime:2.7.1") + implementation("androidx.work:work-runtime-ktx:2.7.1") // Networking // implementation("com.squareup.okhttp3:okhttp:4.9.2") // implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1") - implementation("com.github.Blatzar:NiceHttp:0.4.2") - // To fix SSL fuckery on android 9 - implementation("org.conscrypt:conscrypt-android:2.2.1") + implementation("com.github.Blatzar:NiceHttp:0.3.4") + // Util to skip the URI file fuckery 🙏 implementation("com.github.tachiyomiorg:unifile:17bec43") @@ -222,9 +219,6 @@ dependencies { // Library/extensions searching with Levenshtein distance implementation("me.xdrop:fuzzywuzzy:1.4.0") - - // color pallette for images -> colors - implementation("androidx.palette:palette-ktx:1.0.0") } tasks.register("androidSourcesJar", Jar::class) { @@ -255,4 +249,4 @@ tasks.withType().configureEach { } } } -} +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt index 92042d60..2a3e13e5 100644 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt @@ -1,8 +1,9 @@ package com.lagradost.cloudstream3 import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.SubtitleHelper -import com.lagradost.cloudstream3.utils.TestingUtils import kotlinx.coroutines.runBlocking import org.junit.Assert import org.junit.Test @@ -15,11 +16,142 @@ import org.junit.runner.RunWith */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { + //@Test + //fun useAppContext() { + // // Context of the app under test. + // val appContext = InstrumentationRegistry.getInstrumentation().targetContext + // assertEquals("com.lagradost.cloudstream3", appContext.packageName) + //} + private fun getAllProviders(): List { - println("Providers: ${APIHolder.allProviders.size}") return APIHolder.allProviders //.filter { !it.usesWebView } } + private suspend fun loadLinks(api: MainAPI, url: String?): Boolean { + Assert.assertNotNull("Api ${api.name} has invalid url on episode", url) + if (url == null) return true + var linksLoaded = 0 + try { + val success = api.loadLinks(url, false, {}) { link -> + Assert.assertTrue( + "Api ${api.name} returns link with invalid Quality", + Qualities.values().map { it.value }.contains(link.quality) + ) + Assert.assertTrue( + "Api ${api.name} returns link with invalid url ${link.url}", + link.url.length > 4 + ) + linksLoaded++ + } + if (success) { + return linksLoaded > 0 + } + Assert.assertTrue("Api ${api.name} has returns false on .loadLinks", success) + } catch (e: Exception) { + if (e.cause is NotImplementedError) { + Assert.fail("Provider has not implemented .loadLinks") + } + logError(e) + } + return true + } + + private suspend fun testSingleProviderApi(api: MainAPI): Boolean { + val searchQueries = listOf("over", "iron", "guy") + var correctResponses = 0 + var searchResult: List? = null + for (query in searchQueries) { + val response = try { + api.search(query) + } catch (e: Exception) { + if (e.cause is NotImplementedError) { + Assert.fail("Provider has not implemented .search") + } + logError(e) + null + } + if (!response.isNullOrEmpty()) { + correctResponses++ + if (searchResult == null) { + searchResult = response + } + } + } + + if (correctResponses == 0 || searchResult == null) { + System.err.println("Api ${api.name} did not return any valid search responses") + return false + } + + try { + var validResults = false + for (result in searchResult) { + Assert.assertEquals( + "Invalid apiName on response on ${api.name}", + result.apiName, + api.name + ) + val load = api.load(result.url) ?: continue + Assert.assertEquals( + "Invalid apiName on load on ${api.name}", + load.apiName, + result.apiName + ) + Assert.assertTrue( + "Api ${api.name} on load does not contain any of the supportedTypes", + api.supportedTypes.contains(load.type) + ) + when (load) { + is AnimeLoadResponse -> { + val gotNoEpisodes = + load.episodes.keys.isEmpty() || load.episodes.keys.any { load.episodes[it].isNullOrEmpty() } + + if (gotNoEpisodes) { + println("Api ${api.name} got no episodes on ${load.url}") + continue + } + + val url = (load.episodes[load.episodes.keys.first()])?.first()?.data + validResults = loadLinks(api, url) + if (!validResults) continue + } + is MovieLoadResponse -> { + val gotNoEpisodes = load.dataUrl.isBlank() + if (gotNoEpisodes) { + println("Api ${api.name} got no movie on ${load.url}") + continue + } + + validResults = loadLinks(api, load.dataUrl) + if (!validResults) continue + } + is TvSeriesLoadResponse -> { + val gotNoEpisodes = load.episodes.isEmpty() + if (gotNoEpisodes) { + println("Api ${api.name} got no episodes on ${load.url}") + continue + } + + validResults = loadLinks(api, load.episodes.first().data) + if (!validResults) continue + } + } + break + } + if(!validResults) { + System.err.println("Api ${api.name} did not load on any") + } + + return validResults + } catch (e: Exception) { + if (e.cause is NotImplementedError) { + Assert.fail("Provider has not implemented .load") + } + logError(e) + return false + } + } + @Test fun providersExist() { Assert.assertTrue(getAllProviders().isNotEmpty()) @@ -27,7 +159,6 @@ class ExampleInstrumentedTest { } @Test - @Throws(AssertionError::class) fun providerCorrectData() { val isoNames = SubtitleHelper.languages.map { it.ISO_639_1 } Assert.assertFalse("ISO does not contain any languages", isoNames.isNullOrEmpty()) @@ -50,20 +181,65 @@ class ExampleInstrumentedTest { fun providerCorrectHomepage() { runBlocking { getAllProviders().amap { api -> - TestingUtils.testHomepage(api, ::println) + if (api.hasMainPage) { + try { + val homepage = api.getMainPage() + when { + homepage == null -> { + System.err.println("Homepage provider ${api.name} did not correctly load homepage!") + } + homepage.items.isEmpty() -> { + System.err.println("Homepage provider ${api.name} does not contain any items!") + } + homepage.items.any { it.list.isEmpty() } -> { + System.err.println ("Homepage provider ${api.name} does not have any items on result!") + } + } + } catch (e: Exception) { + if (e.cause is NotImplementedError) { + Assert.fail("Provider marked as hasMainPage, while in reality is has not been implemented") + } + logError(e) + } + } } } println("Done providerCorrectHomepage") } +// @Test +// fun testSingleProvider() { +// testSingleProviderApi(ThenosProvider()) +// } + @Test - fun testAllProvidersCorrect() { + fun providerCorrect() { runBlocking { - TestingUtils.getDeferredProviderTests( - this, - getAllProviders(), - ::println - ) { _, _ -> } + val invalidProvider = ArrayList>() + val providers = getAllProviders() + providers.amap { api -> + try { + println("Trying $api") + if (testSingleProviderApi(api)) { + println("Success $api") + } else { + System.err.println("Error $api") + invalidProvider.add(Pair(api, null)) + } + } catch (e: Exception) { + logError(e) + invalidProvider.add(Pair(api, e)) + } + } + if(invalidProvider.isEmpty()) { + println("No Invalid providers! :D") + } else { + println("Invalid providers are: ") + for (provider in invalidProvider) { + println("${provider.first}") + } + } } + println("Done providerCorrect") } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 563c82f8..47676059 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,11 +10,7 @@ - - - - - + - - - - - - - - - - @@ -124,30 +110,6 @@ - - - - - - - - - - - - - - - - - - - - - @@ -183,10 +145,6 @@ android:name=".ui.ControllerActivity" android:exported="false" /> - - 0L && pos > 0L) DataStoreHelper.setViewPos(getKey(resumeApp.lastId), pos, dur) removeKey(resumeApp.lastId) @@ -166,23 +152,6 @@ object CommonActivity { } } } - - // Ask for notification permissions on Android 13 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && - ContextCompat.checkSelfPermission( - act, - Manifest.permission.POST_NOTIFICATIONS - ) != PackageManager.PERMISSION_GRANTED - ) { - val requestPermissionLauncher = act.registerForActivityResult( - ActivityResultContracts.RequestPermission() - ) { isGranted: Boolean -> - Log.d(TAG, "Notification permission: $isGranted") - } - requestPermissionLauncher.launch( - Manifest.permission.POST_NOTIFICATIONS - ) - } } private fun Activity.enterPIPMode() { @@ -449,4 +418,4 @@ object CommonActivity { } return null } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt deleted file mode 100644 index 045a7963..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.lagradost.cloudstream3 - -import android.view.LayoutInflater -import androidx.annotation.LayoutRes -import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.ui.HeaderViewDecoration - -fun setHeaderDecoration(view: RecyclerView, @LayoutRes headerViewRes: Int) { - val headerView = LayoutInflater.from(view.context).inflate(headerViewRes, null) - view.addItemDecoration(HeaderViewDecoration(headerView)) -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index c20786c3..4b271192 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -13,14 +13,12 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi -import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.player.SubtitleData -import com.lagradost.cloudstream3.utils.* +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.AppUtils.toJson -import com.lagradost.cloudstream3.utils.Coroutines.mainWork import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf +import com.lagradost.cloudstream3.utils.ExtractorLink import okhttp3.Interceptor -import org.mozilla.javascript.Scriptable import java.text.SimpleDateFormat import java.util.* import kotlin.math.absoluteValue @@ -83,7 +81,6 @@ object APIHolder { synchronized(allProviders) { initMap() return apiMap?.get(apiName)?.let { apis.getOrNull(it) } - // Leave the ?. null check, it can crash regardless ?: allProviders.firstOrNull { it.name == apiName } } } @@ -162,53 +159,6 @@ object APIHolder { return null } - private var trackerCache: HashMap = hashMapOf() - - /** - * Get anime tracker information based on title, year and type. - * Both titles are attempted to be matched with both Romaji and English title. - * Uses the consumet api. - * - * @param titles uses first index to search, but if you have multiple titles and want extra guarantee to match you can also have that - * @param types Optional parameter to narrow down the scope to Movies, TV, etc. See TrackerType.getTypes() - * @param year Optional parameter to only get anime with a specific year - **/ - suspend fun getTracker( - titles: List, - types: Set?, - year: Int? - ): Tracker? { - return try { - require(titles.isNotEmpty()) { "titles must no be empty when calling getTracker" } - - val mainTitle = titles[0] - val search = - trackerCache[mainTitle] - ?: app.get("https://api.consumet.org/meta/anilist/$mainTitle") - .parsedSafe()?.also { - trackerCache[mainTitle] = it - } ?: return null - - val res = search.results?.find { media -> - val matchingYears = year == null || media.releaseDate == year - val matchingTitles = media.title?.let { title -> - titles.any { userTitle -> - title.isMatchingTitles(userTitle) - } - } ?: false - - val matchingTypes = types?.any { it.name.equals(media.type, true) } == true - matchingTitles && matchingTypes && matchingYears - } ?: return null - - Tracker(res.malId, res.aniId, res.image, res.cover) - } catch (t: Throwable) { - logError(t) - null - } - } - - fun Context.getApiSettings(): HashSet { //val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) @@ -286,6 +236,7 @@ object APIHolder { } private fun Context.getHasTrailers(): Boolean { + if (isTvSettings()) return false val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) return settingsManager.getBoolean(this.getString(R.string.show_trailers_key), true) } @@ -293,17 +244,11 @@ object APIHolder { fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List { // We are getting the weirdest crash ever done: // java.lang.ClassCastException: com.lagradost.cloudstream3.TvType cannot be cast to com.lagradost.cloudstream3.TvType - // Trying fixing using classloader fuckery - val oldLoader = Thread.currentThread().contextClassLoader - Thread.currentThread().contextClassLoader = TvType::class.java.classLoader - + // enumValues() might be the cause, hence I am trying TvType.values() val default = TvType.values() .sorted() .filter { it != TvType.NSFW } .map { it.ordinal } - - Thread.currentThread().contextClassLoader = oldLoader - val defaultSet = default.map { it.toString() }.toSet() val currentPrefMedia = try { PreferenceManager.getDefaultSharedPreferences(this) @@ -367,57 +312,6 @@ object APIHolder { } } -/* -// THIS IS WORK IN PROGRESS API -interface ITag { - val name: UiText -} - -data class SimpleTag(override val name: UiText, val data: String) : ITag - -enum class SelectType { - SingleSelect, - MultiSelect, - MultiSelectAndExclude, -} - -enum class SelectValue { - Selected, - Excluded, -} - -interface GenreSelector { - val title: UiText - val id : Int -} - -data class TagSelector( - override val title: UiText, - override val id : Int, - val tags: Set, - val defaultTags : Set = setOf(), - val selectType: SelectType = SelectType.SingleSelect, -) : GenreSelector - -data class BoolSelector( - override val title: UiText, - override val id : Int, - - val defaultValue : Boolean = false, -) : GenreSelector - -data class InputField( - override val title: UiText, - override val id : Int, - - val hint : UiText? = null, -) : GenreSelector - -// This response describes how a user might filter the homepage or search results -data class GenreResponse( - val searchSelectors : List, - val filterSelectors: List = searchSelectors -) */ /* 0 = Site not good @@ -559,20 +453,6 @@ abstract class MainAPI { open val hasMainPage = false open val hasQuickSearch = false - /** - * A set of which ids the provider can open with getLoadUrl() - * If the set contains SyncIdName.Imdb then getLoadUrl() can be started with - * an Imdb class which inherits from SyncId. - * - * getLoadUrl() is then used to get page url based on that ID. - * - * Example: - * "tt6723592" -> getLoadUrl(ImdbSyncId("tt6723592")) -> "mainUrl/imdb/tt6723592" -> load("mainUrl/imdb/tt6723592") - * - * This is used to launch pages from personal lists or recommendations using IDs. - **/ - open val supportedSyncNames = setOf() - open val supportedTypes = setOf( TvType.Movie, TvType.TvSeries, @@ -643,14 +523,6 @@ abstract class MainAPI { open fun getVideoInterceptor(extractorLink: ExtractorLink): Interceptor? { return null } - - /** - * Get the load() url based on a sync ID like IMDb or MAL. - * Only contains SyncIds based on supportedSyncUrls. - **/ - open suspend fun getLoadUrl(name: SyncIdName, id: String): String? { - return null - } } /** Might need a different implementation for desktop*/ @@ -736,19 +608,6 @@ fun fixTitle(str: String): String { .replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else it } } } -/** - * Get rhino context in a safe way as it needs to be initialized on the main thread. - * Make sure you get the scope using: val scope: Scriptable = rhino.initSafeStandardObjects() - * Use like the following: rhino.evaluateString(scope, js, "JavaScript", 1, null) - **/ -suspend fun getRhinoContext(): org.mozilla.javascript.Context { - return Coroutines.mainWork { - val rhino = org.mozilla.javascript.Context.enter() - rhino.initSafeStandardObjects() - rhino.optimizationLevel = -1 - rhino - } -} /** https://www.imdb.com/title/tt2861424/ -> tt2861424 */ fun imdbUrlToId(url: String): String? { @@ -1273,43 +1132,18 @@ interface LoadResponse { fun getDurationFromString(input: String?): Int? { val cleanInput = input?.trim()?.replace(" ", "") ?: return null - //Use first as sometimes the text passes on the 2 other Regex, but failed to provide accurate return value - Regex("(\\d+\\shr)|(\\d+\\shour)|(\\d+\\smin)|(\\d+\\ssec)").findAll(input).let { values -> - var seconds = 0 - values.forEach { - val time_text = it.value - if (time_text.isNotBlank()) { - val time = time_text.filter { s -> s.isDigit() }.trim().toInt() - val scale = time_text.filter { s -> !s.isDigit() }.trim() - //println("Scale: $scale") - val timeval = when (scale) { - "hr", "hour" -> time * 60 * 60 - "min" -> time * 60 - "sec" -> time - else -> 0 - } - seconds += timeval - } - } - if (seconds > 0) { - return seconds / 60 - } - } Regex("([0-9]*)h.*?([0-9]*)m").find(cleanInput)?.groupValues?.let { values -> if (values.size == 3) { val hours = values[1].toIntOrNull() val minutes = values[2].toIntOrNull() - if (minutes != null && hours != null) { - return hours * 60 + minutes - } + return if (minutes != null && hours != null) { + hours * 60 + minutes + } else null } } Regex("([0-9]*)m").find(cleanInput)?.groupValues?.let { values -> if (values.size == 2) { - val return_value = values[1].toIntOrNull() - if (return_value != null) { - return return_value - } + return values[1].toIntOrNull() } } return null @@ -1327,7 +1161,7 @@ fun LoadResponse?.isAnimeBased(): Boolean { fun TvType?.isEpisodeBased(): Boolean { if (this == null) return false - return (this == TvType.TvSeries || this == TvType.Anime || this == TvType.AsianDrama) + return (this == TvType.TvSeries || this == TvType.Anime) } @@ -1351,7 +1185,6 @@ interface EpisodeResponse { var showStatus: ShowStatus? var nextAiring: NextAiring? var seasonNames: List? - fun getLatestEpisodes(): Map } @JvmName("addSeasonNamesString") @@ -1420,18 +1253,7 @@ data class AnimeLoadResponse( override var nextAiring: NextAiring? = null, override var seasonNames: List? = null, override var backgroundPosterUrl: String? = null, -) : LoadResponse, EpisodeResponse { - override fun getLatestEpisodes(): Map { - return episodes.map { (status, episodes) -> - val maxSeason = episodes.maxOfOrNull { it.season ?: Int.MIN_VALUE } - .takeUnless { it == Int.MIN_VALUE } - status to episodes - .filter { it.season == maxSeason } - .maxOfOrNull { it.episode ?: Int.MIN_VALUE } - .takeUnless { it == Int.MIN_VALUE } - }.toMap() - } -} +) : LoadResponse, EpisodeResponse /** * If episodes already exist appends the list. @@ -1629,17 +1451,7 @@ data class TvSeriesLoadResponse( override var nextAiring: NextAiring? = null, override var seasonNames: List? = null, override var backgroundPosterUrl: String? = null, -) : LoadResponse, EpisodeResponse { - override fun getLatestEpisodes(): Map { - val maxSeason = - episodes.maxOfOrNull { it.season ?: Int.MIN_VALUE }.takeUnless { it == Int.MIN_VALUE } - val max = episodes - .filter { it.season == maxSeason } - .maxOfOrNull { it.episode ?: Int.MIN_VALUE } - .takeUnless { it == Int.MIN_VALUE } - return mapOf(DubStatus.None to max) - } -} +) : LoadResponse, EpisodeResponse suspend fun MainAPI.newTvSeriesLoadResponse( name: String, @@ -1671,61 +1483,3 @@ fun fetchUrls(text: String?): List { fun String?.toRatingInt(): Int? = this?.replace(" ", "")?.trim()?.toDoubleOrNull()?.absoluteValue?.times(1000f)?.toInt() - -data class Tracker( - val malId: Int? = null, - val aniId: String? = null, - val image: String? = null, - val cover: String? = null, -) - -data class Title( - @JsonProperty("romaji") val romaji: String? = null, - @JsonProperty("english") val english: String? = null, -) { - fun isMatchingTitles(title: String?): Boolean { - if (title == null) return false - return english.equals(title, true) || romaji.equals(title, true) - } -} - -data class Results( - @JsonProperty("id") val aniId: String? = null, - @JsonProperty("malId") val malId: Int? = null, - @JsonProperty("title") val title: Title? = null, - @JsonProperty("releaseDate") val releaseDate: Int? = null, - @JsonProperty("type") val type: String? = null, - @JsonProperty("image") val image: String? = null, - @JsonProperty("cover") val cover: String? = null, -) - -data class AniSearch( - @JsonProperty("results") val results: ArrayList? = arrayListOf() -) - -/** - * used for the getTracker() method - **/ -enum class TrackerType { - MOVIE, - TV, - TV_SHORT, - ONA, - OVA, - SPECIAL, - MUSIC; - - companion object { - fun getTypes(type: TvType): Set { - return when (type) { - TvType.Movie -> setOf(MOVIE) - TvType.AnimeMovie -> setOf(MOVIE) - TvType.TvSeries -> setOf(TV, TV_SHORT) - TvType.Anime -> setOf(TV, TV_SHORT, ONA, OVA) - TvType.OVA -> setOf(OVA, SPECIAL, ONA) - TvType.Others -> setOf(MUSIC) - else -> emptySet() - } - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index d054f504..ff74d6cc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -1,25 +1,21 @@ package com.lagradost.cloudstream3 import android.content.ComponentName -import android.content.Context import android.content.Intent import android.content.res.ColorStateList import android.content.res.Configuration -import android.net.Uri -import android.os.Build import android.os.Bundle -import android.util.AttributeSet import android.util.Log -import android.view.* -import android.widget.Toast +import android.view.KeyEvent +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager import androidx.activity.result.ActivityResultLauncher import androidx.annotation.IdRes import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.ViewModelProvider import androidx.navigation.NavController import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hierarchy @@ -32,9 +28,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.google.android.gms.cast.framework.* -import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.navigationrail.NavigationRailView -import com.google.android.material.snackbar.Snackbar import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import com.lagradost.cloudstream3.APIHolder.allProviders import com.lagradost.cloudstream3.APIHolder.apis @@ -49,78 +43,58 @@ import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent import com.lagradost.cloudstream3.CommonActivity.onUserLeaveHint import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.updateLocale -import com.lagradost.cloudstream3.mvvm.* +import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.network.initClient import com.lagradost.cloudstream3.plugins.PluginManager -import com.lagradost.cloudstream3.plugins.PluginManager.loadAllOnlinePlugins import com.lagradost.cloudstream3.plugins.PluginManager.loadSinglePlugin import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString -import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringPlayer import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo -import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching -import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringSearch import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.inAppAuths import com.lagradost.cloudstream3.ui.APIRepository -import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO -import com.lagradost.cloudstream3.ui.home.HomeViewModel -import com.lagradost.cloudstream3.ui.player.BasicLink -import com.lagradost.cloudstream3.ui.player.GeneratorPlayer -import com.lagradost.cloudstream3.ui.player.LinkGenerator -import com.lagradost.cloudstream3.ui.result.ResultViewModel2 -import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST -import com.lagradost.cloudstream3.ui.result.setImage -import com.lagradost.cloudstream3.ui.result.setText -import com.lagradost.cloudstream3.ui.search.SearchFragment import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv import com.lagradost.cloudstream3.ui.settings.SettingsGeneral import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions -import com.lagradost.cloudstream3.utils.* -import com.lagradost.cloudstream3.utils.AppUtils.html import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable -import com.lagradost.cloudstream3.utils.AppUtils.isNetworkAvailable import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.loadRepository import com.lagradost.cloudstream3.utils.AppUtils.loadResult -import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult -import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup import com.lagradost.cloudstream3.utils.Coroutines.ioSafe -import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching +import com.lagradost.cloudstream3.utils.Event +import com.lagradost.cloudstream3.utils.IOnBackPressed import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate -import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.UIHelper.changeStatusBarState import com.lagradost.cloudstream3.utils.UIHelper.checkWrite import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute -import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.requestRW +import com.lagradost.cloudstream3.utils.USER_PROVIDER_API +import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API import com.lagradost.nicehttp.Requests import com.lagradost.nicehttp.ResponseParser import kotlinx.android.synthetic.main.activity_main.* -import kotlinx.android.synthetic.main.bottom_resultview_preview.* import kotlinx.android.synthetic.main.fragment_result_swipe.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import okhttp3.ConnectionSpec +import okhttp3.OkHttpClient +import okhttp3.internal.applyConnectionSpec import java.io.File import java.net.URI -import java.net.URLDecoder import java.nio.charset.Charset import kotlin.reflect.KClass -import kotlin.system.exitProcess //https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898 @@ -141,15 +115,13 @@ val VLC_COMPONENT = ComponentName(VLC_PACKAGE, "$VLC_PACKAGE.gui.video.VideoPlay val MPV_COMPONENT = ComponentName(MPV_PACKAGE, "$MPV_PACKAGE.MPVActivity") //TODO REFACTOR AF -open class ResultResume( +data class ResultResume( val packageString: String, val action: String = Intent.ACTION_VIEW, val position: String? = null, val duration: String? = null, var launcher: ActivityResultLauncher? = null, ) { - val defaultTime = -1L - val lastId get() = "${packageString}_last_open_id" suspend fun launch(id: Int?, callback: suspend Intent.() -> Unit) { val intent = Intent(action) @@ -163,50 +135,21 @@ open class ResultResume( callback.invoke(intent) launcher?.launch(intent) } - - open fun getPosition(intent: Intent?): Long { - return defaultTime - } - - open fun getDuration(intent: Intent?): Long { - return defaultTime - } } -val VLC = object : ResultResume( +val VLC = ResultResume( VLC_PACKAGE, - // Android 13 intent restrictions fucks up specifically launching the VLC player - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - "org.videolan.vlc.player.result" - } else { - Intent.ACTION_VIEW - }, + "org.videolan.vlc.player.result", "extra_position", "extra_duration", -) { - override fun getPosition(intent: Intent?): Long { - return intent?.getLongExtra(this.position, defaultTime) ?: defaultTime - } +) - override fun getDuration(intent: Intent?): Long { - return intent?.getLongExtra(this.duration, defaultTime) ?: defaultTime - } -} - -val MPV = object : ResultResume( +val MPV = ResultResume( MPV_PACKAGE, //"is.xyz.mpv.MPVActivity.result", // resume not working :pensive: - position = "position", + position = "position", duration = "duration", -) { - override fun getPosition(intent: Intent?): Long { - return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong() ?: defaultTime - } - - override fun getDuration(intent: Intent?): Long { - return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong() ?: defaultTime - } -} +) val WEB_VIDEO = ResultResume(WEB_VIDEO_CAST_PACKAGE) @@ -245,30 +188,14 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { companion object { const val TAG = "MAINACT" - /** - * Setting this will automatically enter the query in the search - * next time the search fragment is opened. - * This variable will clear itself after one use. Null does nothing. - * - * This is a very bad solution but I was unable to find a better one. - **/ - private var nextSearchQuery: String? = null - /** * Fires every time a new batch of plugins have been loaded, no guarantee about how often this is run and on which thread - * Boolean signifies if stuff should be force reloaded (true if force reload, false if reload when necessary). - * - * The force reloading are used for plugin development to instantly reload the page on deployWithAdb * */ val afterPluginsLoadedEvent = Event() val mainPluginsLoadedEvent = Event() // homepage api, used to speed up time to load for homepage val afterRepositoryLoadedEvent = Event() - // kinda shitty solution, but cant com main->home otherwise for popups - val bookmarksUpdatedEvent = Event() - - /** * @return true if the str has launched an app task (be it successful or not) * @param isWebview does not handle providers and opening download page if true. Can still add repos and login. @@ -279,11 +206,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { isWebview: Boolean ): Boolean = with(activity) { - // TODO MUCH BETTER HANDLING - - // Invalid URIs can crash - fun safeURI(uri: String) = normalSafeApiCall { URI(uri) } - if (str != null && this != null) { if (str.startsWith("https://cs.repo")) { val realUrl = "https://" + str.substringAfter("?") @@ -319,50 +241,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { return true } } - // This specific intent is used for the gradle deployWithAdb - // https://github.com/recloudstream/gradle/blob/master/src/main/kotlin/com/lagradost/cloudstream3/gradle/tasks/DeployWithAdbTask.kt#L46 - if (str == "$appString:") { - PluginManager.hotReloadAllLocalPlugins(activity) - } - } else if (safeURI(str)?.scheme == appStringRepo) { + } else if (URI(str).scheme == appStringRepo) { val url = str.replaceFirst(appStringRepo, "https") loadRepository(url) return true - } else if (safeURI(str)?.scheme == appStringSearch) { - nextSearchQuery = - URLDecoder.decode(str.substringAfter("$appStringSearch://"), "UTF-8") - - // Use both navigation views to support both layouts. - // It might be better to use the QuickSearch. - nav_view?.selectedItemId = R.id.navigation_search - nav_rail_view?.selectedItemId = R.id.navigation_search - } else if (safeURI(str)?.scheme == appStringPlayer) { - val uri = Uri.parse(str) - val name = uri.getQueryParameter("name") - val url = URLDecoder.decode(uri.authority, "UTF-8") - - navigate( - R.id.global_to_navigation_player, - GeneratorPlayer.newInstance( - LinkGenerator( - listOf(BasicLink(url, name)), - extract = true, - ) - ) - ) - } else if (safeURI(str)?.scheme == appStringResumeWatching) { - val id = - str.substringAfter("$appStringResumeWatching://").toIntOrNull() - ?: return false - ioSafe { - val resumeWatchingCard = - HomeViewModel.getResumeWatching()?.firstOrNull { it.id == id } - ?: return@ioSafe - activity.loadSearchResult( - resumeWatchingCard, - START_ACTION_RESUME_LATEST - ) - } } else if (!isWebview) { if (str.startsWith(DOWNLOAD_NAVIGATE_TO)) { this.navigate(R.id.navigation_downloads) @@ -381,16 +263,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } } - var lastPopup: SearchResponse? = null - fun loadPopup(result: SearchResponse) { - lastPopup = result - viewModel.load( - this, result.url, result.apiName, false, if (getApiDubstatusSettings() - .contains(DubStatus.Dubbed) - ) DubStatus.Dubbed else DubStatus.Subbed, null - ) - } - override fun onColorSelected(dialogId: Int, color: Int) { onColorSelectedEvent.invoke(Pair(dialogId, color)) } @@ -422,7 +294,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { val isNavVisible = listOf( R.id.navigation_home, R.id.navigation_search, - R.id.navigation_library, R.id.navigation_downloads, R.id.navigation_settings, R.id.navigation_download_child, @@ -436,30 +307,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { R.id.navigation_settings_general, R.id.navigation_settings_extensions, R.id.navigation_settings_plugins, - R.id.navigation_test_providers, ).contains(destination.id) - - val dontPush = listOf( - R.id.navigation_home, - R.id.navigation_search, - R.id.navigation_results_phone, - R.id.navigation_results_tv, - R.id.navigation_player, - ).contains(destination.id) - - nav_host_fragment?.apply { - val params = layoutParams as ConstraintLayout.LayoutParams - - params.setMargins( - if (!dontPush && isTvSettings()) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0, - params.topMargin, - params.rightMargin, - params.bottomMargin - ) - layoutParams = params - } - val landscape = when (resources.configuration.orientation) { Configuration.ORIENTATION_LANDSCAPE -> { true @@ -474,11 +323,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { nav_view?.isVisible = isNavVisible && !landscape nav_rail_view?.isVisible = isNavVisible && landscape - - // Hide library on TV since it is not supported yet :( - val isTrueTv = isTrueTvSettings() - nav_view?.menu?.findItem(R.id.navigation_library)?.isVisible = !isTrueTv - nav_rail_view?.menu?.findItem(R.id.navigation_library)?.isVisible = !isTrueTv } //private var mCastSession: CastSession? = null @@ -531,11 +375,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { override fun onPause() { super.onPause() - - // Start any delayed updates - if (ApkInstaller.delayedInstaller?.startInstallation() == true) { - Toast.makeText(this, R.string.update_started, Toast.LENGTH_LONG).show() - } try { if (isCastApiAvailable()) { mSessionManager.removeSessionManagerListener(mSessionManagerListener) @@ -566,34 +405,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { onUserLeaveHint(this) } - private fun showConfirmExitDialog() { - val builder: AlertDialog.Builder = AlertDialog.Builder(this) - builder.setTitle(R.string.confirm_exit_dialog) - builder.apply { - // Forceful exit since back button can actually go back to setup - setPositiveButton(R.string.yes) { _, _ -> exitProcess(0) } - setNegativeButton(R.string.no) { _, _ -> } - } - builder.show().setDefaultFocus() - } - private fun backPressed() { this.window?.navigationBarColor = this.colorFromAttribute(R.attr.primaryGrayBackground) this.updateLocale() + super.onBackPressed() this.updateLocale() - - val navHostFragment = - supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment - val navController = navHostFragment?.navController - val isAtHome = - navController?.currentDestination?.matchDestination(R.id.navigation_home) == true - - if (isAtHome && isTrueTvSettings()) { - showConfirmExitDialog() - } else { - super.onBackPressed() - } } override fun onBackPressed() { @@ -681,37 +498,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } } - lateinit var viewModel: ResultViewModel2 - - override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? { - viewModel = - ViewModelProvider(this)[ResultViewModel2::class.java] - - return super.onCreateView(name, context, attrs) - } - - private fun hidePreviewPopupDialog() { - viewModel.clear() - bottomPreviewPopup.dismissSafe(this) - } - - var bottomPreviewPopup: BottomSheetDialog? = null - private fun showPreviewPopupDialog(): BottomSheetDialog { - val ret = (bottomPreviewPopup ?: run { - val builder = - BottomSheetDialog(this) - builder.setContentView(R.layout.bottom_resultview_preview) - builder.setOnDismissListener { - bottomPreviewPopup = null - viewModel.clear() - } - builder.setCanceledOnTouchOutside(true) - builder.show() - builder - }) - bottomPreviewPopup = ret - return ret - } override fun onCreate(savedInstanceState: Bundle?) { app.initClient(this) @@ -742,7 +528,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) - updateTv() + if (isTvSettings()) { setContentView(R.layout.activity_main_tv) } else { @@ -751,35 +537,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { changeStatusBarState(isEmulatorSettings()) - // Automatically enable jsdelivr if cant connect to raw.githubusercontent.com - if (this.getKey(getString(R.string.jsdelivr_proxy_key)) == null && isNetworkAvailable()) { - main { - if (checkGithubConnectivity()) { - this.setKey(getString(R.string.jsdelivr_proxy_key), false) - } else { - this.setKey(getString(R.string.jsdelivr_proxy_key), true) - val parentView: View = findViewById(android.R.id.content) - Snackbar.make(parentView, R.string.jsdelivr_enabled, Snackbar.LENGTH_LONG) - .let { snackbar -> - snackbar.setAction(R.string.revert) { - setKey(getString(R.string.jsdelivr_proxy_key), false) - } - snackbar.setBackgroundTint(colorFromAttribute(R.attr.primaryGrayBackground)) - snackbar.setTextColor(colorFromAttribute(R.attr.textColor)) - snackbar.setActionTextColor(colorFromAttribute(R.attr.colorPrimary)) - snackbar.show() - } - } - - } - } - - - if (PluginManager.checkSafeModeFile()) { - normalSafeApiCall { - showToast(this, R.string.safe_mode_file, Toast.LENGTH_LONG) - } - } else if (lastError == null) { + if (lastError == null) { ioSafe { getKey(USER_SELECTED_HOMEPAGE_API)?.let { homeApi -> mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi)) @@ -795,21 +553,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { ) { PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity) } else { - loadAllOnlinePlugins(this@MainActivity) - } - - //Automatically download not existing plugins - if (settingsManager.getBoolean( - getString(R.string.auto_download_plugins_key), - false - ) - ) { - PluginManager.downloadNotExistingPluginsAndLoad(this@MainActivity) + PluginManager.loadAllOnlinePlugins(this@MainActivity) } } ioSafe { - PluginManager.loadAllLocalPlugins(this@MainActivity, false) + PluginManager.loadAllLocalPlugins(this@MainActivity) } } } else { @@ -826,81 +575,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { setNegativeButton("Ok") { _, _ -> } } - builder.show().setDefaultFocus() + builder.show() } - observeNullable(viewModel.page) { resource -> - if (resource == null) { - bottomPreviewPopup.dismissSafe(this) - return@observeNullable - } - when (resource) { - is Resource.Failure -> { - showToast(this, R.string.error) - hidePreviewPopupDialog() - } - is Resource.Loading -> { - showPreviewPopupDialog().apply { - resultview_preview_loading?.isVisible = true - resultview_preview_result?.isVisible = false - resultview_preview_loading_shimmer?.startShimmer() - } - } - is Resource.Success -> { - val d = resource.value - showPreviewPopupDialog().apply { - resultview_preview_loading?.isVisible = false - resultview_preview_result?.isVisible = true - resultview_preview_loading_shimmer?.stopShimmer() - - resultview_preview_title?.text = d.title - - resultview_preview_meta_type.setText(d.typeText) - resultview_preview_meta_year.setText(d.yearText) - resultview_preview_meta_duration.setText(d.durationText) - resultview_preview_meta_rating.setText(d.ratingText) - - resultview_preview_description?.setText(d.plotText) - resultview_preview_poster?.setImage( - d.posterImage ?: d.posterBackgroundImage - ) - - resultview_preview_poster?.setOnClickListener { - //viewModel.updateWatchStatus(WatchType.PLANTOWATCH) - val value = viewModel.watchStatus.value ?: WatchType.NONE - - this@MainActivity.showBottomDialog( - WatchType.values().map { getString(it.stringRes) }.toList(), - value.ordinal, - this@MainActivity.getString(R.string.action_add_to_bookmarks), - showApply = false, - {}) { - viewModel.updateWatchStatus(WatchType.values()[it]) - bookmarksUpdatedEvent(true) - } - } - - if (!isTvSettings()) // dont want this clickable on tv layout - resultview_preview_description?.setOnClickListener { view -> - view.context?.let { ctx -> - val builder: AlertDialog.Builder = - AlertDialog.Builder(ctx, R.style.AlertDialogCustom) - builder.setMessage(d.plotText.asString(ctx).html()) - .setTitle(d.plotHeaderText.asString(ctx)) - .show() - } - } - - resultview_preview_more_info?.setOnClickListener { - hidePreviewPopupDialog() - lastPopup?.let { - loadSearchResult(it) - } - } - } - } - } - } // ioSafe { // val plugins = @@ -942,17 +619,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment val navController = navHostFragment.navController - - navController.addOnDestinationChangedListener { _: NavController, navDestination: NavDestination, bundle: Bundle? -> - // Intercept search and add a query - if (navDestination.matchDestination(R.id.navigation_search) && !nextSearchQuery.isNullOrBlank()) { - bundle?.apply { - this.putString(SearchFragment.SEARCH_QUERY, nextSearchQuery) - nextSearchQuery = null - } - } - } - //val navController = findNavController(R.id.nav_host_fragment) /*navOptions = NavOptions.Builder() @@ -966,12 +632,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { nav_view?.setupWithNavController(navController) val nav_rail = findViewById(R.id.nav_rail_view) nav_rail?.setupWithNavController(navController) - if (isTvSettings()) { - nav_rail?.background?.alpha = 200 - } else { - nav_rail?.background?.alpha = 255 - } nav_rail?.setOnItemSelectedListener { item -> onNavDestinationSelected( item, @@ -1140,22 +801,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { // Used to check current focus for TV // main { // while (true) { -// delay(5000) +// delay(1000) // println("Current focus: $currentFocus") -// showToast(this, currentFocus.toString(), Toast.LENGTH_LONG) // } // } } - - suspend fun checkGithubConnectivity(): Boolean { - return try { - app.get( - "https://raw.githubusercontent.com/recloudstream/.github/master/connectivitycheck", - timeout = 5 - ).text.trim() == "ok" - } catch (t: Throwable) { - false - } - } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt index b0051ba7..18602664 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt @@ -2,11 +2,10 @@ package com.lagradost.cloudstream3.extractors import android.util.Log import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.ExtractorApi -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.Qualities +import com.lagradost.cloudstream3.base64Decode +import com.lagradost.cloudstream3.utils.* -open class AStreamHub : ExtractorApi() { +class AStreamHub : ExtractorApi() { override val name = "AStreamHub" override val mainUrl = "https://astreamhub.com" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt index c782b29d..fe46791b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt @@ -4,7 +4,7 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64Decode import com.lagradost.cloudstream3.utils.* -open class Acefile : ExtractorApi() { +class Acefile : ExtractorApi() { override val name = "Acefile" override val mainUrl = "https://acefile.co" override val requiresReferer = false @@ -27,6 +27,7 @@ open class Acefile : ExtractorApi() { res.substringAfter("\"file\":\"").substringBefore("\","), "$mainUrl/", Qualities.Unknown.value, + headers = mapOf("range" to "bytes=0-") ) ) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt index 7a62fb52..cf16f200 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.getQualityFromName import java.net.URI -open class AsianLoad : ExtractorApi() { +class AsianLoad : ExtractorApi() { override var name = "AsianLoad" override var mainUrl = "https://asianembed.io" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt index 44e700b1..cae77322 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt @@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson -open class Blogger : ExtractorApi() { +class Blogger : ExtractorApi() { override val name = "Blogger" override val mainUrl = "https://www.blogger.com" override val requiresReferer = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt index 71fa7066..e5368bc3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt @@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper -open class BullStream : ExtractorApi() { +class BullStream : ExtractorApi() { override val name = "BullStream" override val mainUrl = "https://bullstream.xyz" override val requiresReferer = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/ByteShare.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/ByteShare.kt deleted file mode 100644 index 3e0a03c0..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/ByteShare.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.lagradost.cloudstream3.extractors - -import com.lagradost.cloudstream3.utils.* - -open class ByteShare : ExtractorApi() { - override val name = "ByteShare" - override val mainUrl = "https://byteshare.net" - override val requiresReferer = false - - override suspend fun getUrl(url: String, referer: String?): List { - val sources = mutableListOf() - sources.add( - ExtractorLink( - name, - name, - url.replace("/embed/", "/download/"), - "", - Qualities.Unknown.value, - ) - ) - return sources - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Cda.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Cda.kt deleted file mode 100644 index 6a2f399d..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Cda.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.lagradost.cloudstream3.extractors - -import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.utils.Qualities -import com.lagradost.cloudstream3.utils.ExtractorApi -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 -import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson -import android.util.Log -import java.net.URLDecoder - -open class Cda: ExtractorApi() { - override var mainUrl = "https://ebd.cda.pl" - override var name = "Cda" - override val requiresReferer = false - - - override suspend fun getUrl(url: String, referer: String?): List? { - val mediaId = url - .split("/").last() - .split("?").first() - val doc = app.get("https://ebd.cda.pl/647x500/$mediaId", headers=mapOf( - "Referer" to "https://ebd.cda.pl/647x500/$mediaId", - "User-Agent" to USER_AGENT, - "Cookie" to "cda.player=html5" - )).document - val dataRaw = doc.selectFirst("[player_data]")?.attr("player_data") ?: return null - val playerData = tryParseJson(dataRaw) ?: return null - return listOf(ExtractorLink( - name, - name, - getFile(playerData.video.file), - referer = "https://ebd.cda.pl/647x500/$mediaId", - quality = Qualities.Unknown.value - )) - } - - private fun rot13(a: String): String { - return a.map { - when { - it in 'A'..'M' || it in 'a'..'m' -> it + 13 - it in 'N'..'Z' || it in 'n'..'z' -> it - 13 - else -> it - } - }.joinToString("") - } - - private fun cdaUggc(a: String): String { - val decoded = rot13(a) - return if (decoded.endsWith("adc.mp4")) decoded.replace("adc.mp4",".mp4") - else decoded - } - - private fun cdaDecrypt(b: String): String { - var a = b - .replace("_XDDD", "") - .replace("_CDA", "") - .replace("_ADC", "") - .replace("_CXD", "") - .replace("_QWE", "") - .replace("_Q5", "") - .replace("_IKSDE", "") - a = URLDecoder.decode(a, "UTF-8") - a = a.map { char -> - if (32 < char.toInt() && char.toInt() < 127) { - return@map String.format("%c", 33 + (char.toInt() + 14) % 94) - } else { - return@map char - } - }.joinToString("") - a = a - .replace(".cda.mp4", "") - .replace(".2cda.pl", ".cda.pl") - .replace(".3cda.pl", ".cda.pl") - return if (a.contains("/upstream")) "https://" + a.replace("/upstream", ".mp4/upstream") - else "https://${a}.mp4" - } - - private fun getFile(a: String) = when { - a.startsWith("uggc") -> cdaUggc(a) - !a.startsWith("http") -> cdaDecrypt(a) - else -> a - } - - data class VideoPlayerData( - val file: String, - val qualities: Map = mapOf(), - val quality: String?, - val ts: Int?, - val hash2: String? - ) - - data class PlayerData( - val video: VideoPlayerData - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Dailymotion.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Dailymotion.kt deleted file mode 100644 index 4b7cb19f..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Dailymotion.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.lagradost.cloudstream3.extractors - -import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.SubtitleFile -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson -import com.lagradost.cloudstream3.utils.ExtractorApi -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 -import com.lagradost.cloudstream3.utils.Qualities -import java.net.URL - -open class Dailymotion : ExtractorApi() { - override val mainUrl = "https://www.dailymotion.com" - override val name = "Dailymotion" - override val requiresReferer = false - - @Suppress("RegExpSimplifiable") - private val videoIdRegex = "^[kx][a-zA-Z0-9]+\$".toRegex() - - // https://www.dailymotion.com/video/k3JAHfletwk94ayCVIu - // https://www.dailymotion.com/embed/video/k3JAHfletwk94ayCVIu - override suspend fun getUrl( - url: String, - referer: String?, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val embedUrl = getEmbedUrl(url) ?: return - val doc = app.get(embedUrl).document - val prefix = "window.__PLAYER_CONFIG__ = " - val configStr = doc.selectFirst("script:containsData($prefix)")?.data() ?: return - val config = tryParseJson(configStr.substringAfter(prefix)) ?: return - val id = getVideoId(embedUrl) ?: return - val dmV1st = config.dmInternalData.v1st - val dmTs = config.dmInternalData.ts - val metaDataUrl = - "$mainUrl/player/metadata/video/$id?locale=en&dmV1st=$dmV1st&dmTs=$dmTs&is_native_app=0" - val cookies = mapOf( - "v1st" to dmV1st, - "dmvk" to config.context.dmvk, - "ts" to dmTs.toString() - ) - val metaData = app.get(metaDataUrl, referer = embedUrl, cookies = cookies) - .parsedSafe() ?: return - metaData.qualities.forEach { (_, video) -> - video.forEach { - getStream(it.url, this.name, callback) - } - } - } - - private fun getEmbedUrl(url: String): String? { - if (url.contains("/embed/")) { - return url - } - val vid = getVideoId(url) ?: return null - return "$mainUrl/embed/video/$vid" - } - - private fun getVideoId(url: String): String? { - val path = URL(url).path - val id = path.substringAfter("video/") - if (id.matches(videoIdRegex)) { - return id - } - return null - } - - private suspend fun getStream( - streamLink: String, - name: String, - callback: (ExtractorLink) -> Unit - ) { - return generateM3u8( - name, - streamLink, - "", - ).forEach(callback) - } - data class Config( - val context: Context, - val dmInternalData: InternalData - ) - - data class InternalData( - val ts: Int, - val v1st: String - ) - - data class Context( - @JsonProperty("access_token") val accessToken: String?, - val dmvk: String, - ) - - data class MetaData( - val qualities: Map> - ) - - data class VideoLink( - val type: String, - val url: String - ) - -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt index 0d94eb08..7ec1fb22 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt @@ -38,9 +38,6 @@ class DoodWsExtractor : DoodLaExtractor() { override var mainUrl = "https://dood.ws" } -class DoodYtExtractor : DoodLaExtractor() { - override var mainUrl = "https://dood.yt" -} open class DoodLaExtractor : ExtractorApi() { override var name = "DoodStream" diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt index 45a06dcc..35569663 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.httpsify -open class Embedgram : ExtractorApi() { +class Embedgram : ExtractorApi() { override val name = "Embedgram" override val mainUrl = "https://embedgram.com" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt index 3e38b446..eddbf6df 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt @@ -16,7 +16,26 @@ open class Evoload : ExtractorApi() { override suspend fun getUrl(url: String, referer: String?): List { - val id = url.replace("https://evoload.io/e/", "") // wanted media id + val lang = url.substring(0, 2) + val flag = + if (lang == "vo") { + " \uD83C\uDDEC\uD83C\uDDE7" + } + else if (lang == "vf"){ + " \uD83C\uDDE8\uD83C\uDDF5" + } else { + "" + } + + val cleaned_url = if (lang == "ht") { // if url doesn't contain a flag and the url starts with http:// + url + } else { + url.substring(2, url.length) + } + //println(lang) + //println(cleaned_url) + + val id = cleaned_url.replace("https://evoload.io/e/", "") // wanted media id val csrv_token = app.get("https://csrv.evosrv.com/captcha?m412548=").text // whatever that is val captchaPass = app.get("https://cd2.evosrv.com/html/jsx/e.jsx").text.take(300).split("captcha_pass = '")[1].split("\'")[0] //extract the captcha pass from the js response (located in the 300 first chars) val payload = mapOf("code" to id, "csrv_token" to csrv_token, "pass" to captchaPass) @@ -25,9 +44,9 @@ open class Evoload : ExtractorApi() { return listOf( ExtractorLink( name, - name, + name + flag, link, - url, + cleaned_url, Qualities.Unknown.value, ) ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt index e8f8c49a..0d5a5c78 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt @@ -5,50 +5,35 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 -import com.lagradost.cloudstream3.utils.getAndUnpack -import org.jsoup.nodes.Document -open class Fastream: ExtractorApi() { +class Fastream: ExtractorApi() { override var mainUrl = "https://fastream.to" override var name = "Fastream" override val requiresReferer = false - suspend fun getstream( - response: Document, - sources: ArrayList): Boolean{ + + + override suspend fun getUrl(url: String, referer: String?): List? { + val id = Regex("emb\\.html\\?(.*)\\=(enc|)").find(url)?.destructured?.component1() ?: return emptyList() + val sources = mutableListOf() + val response = app.post("$mainUrl/dl", + data = mapOf( + Pair("op","embed"), + Pair("file_code",id), + Pair("auto","1") + )).document response.select("script").amap { script -> - if (script.data().contains(Regex("eval\\(function\\(p,a,c,k,e,[rd]"))) { - val unpacked = getAndUnpack(script.data()) - //val m3u8regex = Regex("((https:|http:)\\/\\/.*\\.m3u8)") - val newm3u8link = unpacked.substringAfter("file:\"").substringBefore("\"") - //val m3u8link = m3u8regex.find(unpacked)?.value ?: return@forEach + if (script.data().contains("sources")) { + val m3u8regex = Regex("((https:|http:)\\/\\/.*\\.m3u8)") + val m3u8 = m3u8regex.find(script.data())?.value ?: return@amap generateM3u8( name, - newm3u8link, + m3u8, mainUrl ).forEach { link -> sources.add(link) } } } - return true - } - - override suspend fun getUrl(url: String, referer: String?): List { - val sources = ArrayList() - val idregex = Regex("emb.html\\?(.*)=") - if (url.contains(Regex("(emb.html.*fastream)"))) { - val id = idregex.find(url)?.destructured?.component1() ?: "" - val response = app.post("https://fastream.to/dl", allowRedirects = false, - data = mapOf( - "op" to "embed", - "file_code" to id, - "auto" to "1" - ) - ).document - getstream(response, sources) - } - val response = app.get(url, referer = url).document - getstream(response, sources) return sources } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt index 84fd0552..5c8af1c5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt @@ -1,57 +1,38 @@ package com.lagradost.cloudstream3.extractors -import com.lagradost.cloudstream3.SubtitleFile +import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* -import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 +import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson - -class Ztreamhub : Filesim() { - override val mainUrl: String = "https://ztreamhub.com" //Here 'cause works - override val name = "Zstreamhub" -} -class FileMoon : Filesim() { - override val mainUrl = "https://filemoon.to" - override val name = "FileMoon" -} - -class FileMoonSx : Filesim() { - override val mainUrl = "https://filemoon.sx" - override val name = "FileMoonSx" -} - -open class Filesim : ExtractorApi() { +class Filesim : ExtractorApi() { override val name = "Filesim" override val mainUrl = "https://files.im" override val requiresReferer = false - override suspend fun getUrl( - url: String, - referer: String?, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val response = app.get(url, referer = mainUrl).document - response.select("script[type=text/javascript]").map { script -> - if (script.data().contains(Regex("eval\\(function\\(p,a,c,k,e,[rd]"))) { - val unpackedscript = getAndUnpack(script.data()) - val m3u8Regex = Regex("file.\\\"(.*?m3u8.*?)\\\"") - val m3u8 = m3u8Regex.find(unpackedscript)?.destructured?.component1() ?: "" - if (m3u8.isNotEmpty()) { - generateM3u8( - name, - m3u8, - mainUrl - ).forEach(callback) + override suspend fun getUrl(url: String, referer: String?): List { + val sources = mutableListOf() + with(app.get(url).document) { + this.select("script").map { script -> + if (script.data().contains("eval(function(p,a,c,k,e,d)")) { + val data = getAndUnpack(script.data()).substringAfter("sources:[").substringBefore("]") + tryParseJson>("[$data]")?.map { + M3u8Helper.generateM3u8( + name, + it.file, + "$mainUrl/", + ).forEach { m3uData -> sources.add(m3uData) } + } } } } + return sources } - /* private data class ResponseSource( + private data class ResponseSource( @JsonProperty("file") val file: String, @JsonProperty("type") val type: String?, @JsonProperty("label") val label: String? - ) */ + ) } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt index 52c45096..af02ee8a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt @@ -3,9 +3,10 @@ package com.lagradost.cloudstream3.extractors import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.Qualities -open class GMPlayer : ExtractorApi() { +class GMPlayer : ExtractorApi() { override val name = "GM Player" override val mainUrl = "https://gmplayer.xyz" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt index 2adc00d5..f25cb5ba 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt @@ -6,11 +6,6 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* -class Vanfem : GuardareStream() { - override var name = "Vanfem" - override var mainUrl = "https://vanfem.com/" -} - class CineGrabber : GuardareStream() { override var name = "CineGrabber" override var mainUrl = "https://cinegrabber.com" diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt index 6a4945bb..52fc5532 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt @@ -1,53 +1,46 @@ package com.lagradost.cloudstream3.extractors import com.fasterxml.jackson.annotation.JsonProperty -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.getQualityFromName -open class Linkbox : ExtractorApi() { +class Linkbox : ExtractorApi() { override val name = "Linkbox" override val mainUrl = "https://www.linkbox.to" override val requiresReferer = true - override suspend fun getUrl( - url: String, - referer: String?, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val id = Regex("""(?:/f/|/file/|\?id=)(\w+)""").find(url)?.groupValues?.get(1) - app.get("$mainUrl/api/file/detail?itemId=$id", referer = url) - .parsedSafe()?.data?.itemInfo?.resolutionList?.map { link -> - callback.invoke( - ExtractorLink( - name, - name, - link.url ?: return@map null, - url, - getQualityFromName(link.resolution) - ) + override suspend fun getUrl(url: String, referer: String?): List { + val id = url.substringAfter("id=") + val sources = mutableListOf() + + app.get("$mainUrl/api/open/get_url?itemId=$id", referer=url).parsedSafe()?.data?.rList?.map { link -> + sources.add( + ExtractorLink( + name, + name, + link.url, + url, + getQualityFromName(link.resolution) ) - } + ) + } + + return sources } - data class Resolutions( - @JsonProperty("url") val url: String? = null, - @JsonProperty("resolution") val resolution: String? = null, - ) - - data class ItemInfo( - @JsonProperty("resolutionList") val resolutionList: ArrayList? = arrayListOf(), + data class RList( + @JsonProperty("url") val url: String, + @JsonProperty("resolution") val resolution: String?, ) data class Data( - @JsonProperty("itemInfo") val itemInfo: ItemInfo? = null, + @JsonProperty("rList") val rList: List?, ) data class Responses( - @JsonProperty("data") val data: Data? = null, + @JsonProperty("data") val data: Data?, ) } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt new file mode 100644 index 00000000..29d98557 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt @@ -0,0 +1,7 @@ +package com.lagradost.cloudstream3.extractors + +open class Mcloud : WcoStream() { + override var name = "Mcloud" + override var mainUrl = "https://mcloud.to" + override val requiresReferer = true +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt index 93a280ed..68a4a103 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt @@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.getAndUnpack -open class Mp4Upload : ExtractorApi() { +class Mp4Upload : ExtractorApi() { override var name = "Mp4Upload" override var mainUrl = "https://www.mp4upload.com" private val srcRegex = Regex("""player\.src\("(.*?)"""") diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt index 44657196..0c0b5c68 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.getQualityFromName import java.net.URI -open class MultiQuality : ExtractorApi() { +class MultiQuality : ExtractorApi() { override var name = "MultiQuality" override var mainUrl = "https://gogo-play.net" private val sourceRegex = Regex("""file:\s*['"](.*?)['"],label:\s*['"](.*?)['"]""") diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mvidoo.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mvidoo.kt index 9e5f5e74..c00df942 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mvidoo.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mvidoo.kt @@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities -open class Mvidoo : ExtractorApi() { +class Mvidoo : ExtractorApi() { override val name = "Mvidoo" override val mainUrl = "https://mvidoo.com" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Okrulink.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Okrulink.kt deleted file mode 100644 index 37bb09e3..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Okrulink.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.lagradost.cloudstream3.extractors - -import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.ExtractorApi -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.Qualities - -data class Okrulinkdata ( - @JsonProperty("status" ) var status : String? = null, - @JsonProperty("url" ) var url : String? = null -) - -open class Okrulink: ExtractorApi() { - override var mainUrl = "https://okru.link" - override var name = "Okrulink" - override val requiresReferer = false - - override suspend fun getUrl(url: String, referer: String?): List { - val sources = mutableListOf() - val key = url.substringAfter("html?t=") - val request = app.post("https://apizz.okru.link/decoding", allowRedirects = false, - data = mapOf("video" to key) - ).parsedSafe() - if (request?.url != null) { - sources.add( - ExtractorLink( - name, - name, - request.url!!, - "", - Qualities.Unknown.value, - isM3u8 = false - ) - ) - } - return sources - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt index 45ec4c2f..de469b22 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt @@ -14,7 +14,7 @@ import org.jsoup.Jsoup * overrideMainUrl is necessary for for other vidstream clones like vidembed.cc * If they diverge it'd be better to make them separate. * */ -open class Pelisplus(val mainUrl: String) { +class Pelisplus(val mainUrl: String) { val name: String = "Vidstream" private fun getExtractorUrl(id: String): String { diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt index 2b286abb..9a031556 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt @@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson -open class PlayLtXyz: ExtractorApi() { +class PlayLtXyz: ExtractorApi() { override val name: String = "PlayLt" override val mainUrl: String = "https://play.playlt.xyz" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Sendvid.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Sendvid.kt deleted file mode 100644 index 514b802d..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Sendvid.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.lagradost.cloudstream3.extractors - -import com.lagradost.cloudstream3.SubtitleFile -import com.lagradost.cloudstream3.utils.* -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 - -open class Sendvid : ExtractorApi() { - override var name = "Sendvid" - override val mainUrl = "https://sendvid.com" - override val requiresReferer = false - override suspend fun getUrl( - url: String, - referer: String?, - subtitleCallback: (SubtitleFile) -> Unit, - callback: (ExtractorLink) -> Unit - ) { - val doc = app.get(url).document - val urlString = doc.select("head meta[property=og:video:secure_url]").attr("content") - if (urlString.contains("m3u8")) { - generateM3u8( - name, - urlString, - mainUrl, - ).forEach(callback) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt index cc34781c..849f5fc8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt @@ -8,7 +8,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName -open class Solidfiles : ExtractorApi() { +class Solidfiles : ExtractorApi() { override val name = "Solidfiles" override val mainUrl = "https://www.solidfiles.com" override val requiresReferer = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt index cac31328..913b410b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt @@ -77,10 +77,6 @@ class StreamSB10 : StreamSB() { override var mainUrl = "https://sbplay2.xyz" } -class StreamSB11 : StreamSB() { - override var mainUrl = "https://sbbrisk.com" -} - // This is a modified version of https://github.com/jmir1/aniyomi-extensions/blob/master/src/en/genoanime/src/eu/kanade/tachiyomi/animeextension/en/genoanime/extractors/StreamSBExtractor.kt // The following code is under the Apache License 2.0 https://github.com/jmir1/aniyomi-extensions/blob/master/LICENSE open class StreamSB : ExtractorApi() { @@ -134,7 +130,7 @@ open class StreamSB : ExtractorApi() { it.value.replace(Regex("(embed-|/e/)"), "") }.first() // val master = "$mainUrl/sources48/6d6144797752744a454267617c7c${bytesToHex.lowercase()}7c7c4e61755a56456f34385243727c7c73747265616d7362/6b4a33767968506e4e71374f7c7c343837323439333133333462353935333633373836643638376337633462333634663539343137373761333635313533333835333763376333393636363133393635366136323733343435323332376137633763373337343732363536313664373336327c7c504d754478413835306633797c7c73747265616d7362" - val master = "$mainUrl/sources15/" + bytesToHex("||$id||||streamsb".toByteArray()) + "/" + val master = "$mainUrl/sources48/" + bytesToHex("||$id||||streamsb".toByteArray()) + "/" val headers = mapOf( "watchsb" to "sbstream", ) @@ -160,4 +156,4 @@ open class StreamSB : ExtractorApi() { ) } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt index ece8dc4b..af436ff3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt @@ -5,15 +5,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities -class StreamTapeNet : StreamTape() { - override var mainUrl = "https://streamtape.net" -} - -class ShaveTape : StreamTape(){ - override var mainUrl = "https://shavetape.cash" -} - -open class StreamTape : ExtractorApi() { +class StreamTape : ExtractorApi() { override var name = "StreamTape" override var mainUrl = "https://streamtape.com" override val requiresReferer = false @@ -24,8 +16,7 @@ open class StreamTape : ExtractorApi() { override suspend fun getUrl(url: String, referer: String?): List? { with(app.get(url)) { linkRegex.find(this.text)?.let { - val extractedUrl = - "https:${it.groups[1]!!.value + it.groups[2]!!.value.substring(3)}" + val extractedUrl = "https:${it.groups[1]!!.value + it.groups[2]!!.value.substring(3,)}" return listOf( ExtractorLink( name, diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt index c7689c58..2765ae17 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.JsUnpacker import com.lagradost.cloudstream3.utils.Qualities import java.net.URI -open class Streamhub : ExtractorApi() { +class Streamhub : ExtractorApi() { override var mainUrl = "https://streamhub.to" override var name = "Streamhub" override val requiresReferer = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamplay.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamplay.kt index e6bbfeba..3f5e5bd6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamplay.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamplay.kt @@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import java.net.URI -open class Streamplay : ExtractorApi() { +class Streamplay : ExtractorApi() { override val name = "Streamplay" override val mainUrl = "https://streamplay.to" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt index dd49d994..955345aa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt @@ -11,7 +11,7 @@ data class Files( @JsonProperty("label") val label: String? = null, ) -open class Supervideo : ExtractorApi() { + open class Supervideo : ExtractorApi() { override var name = "Supervideo" override var mainUrl = "https://supervideo.tv" override val requiresReferer = false @@ -20,13 +20,10 @@ open class Supervideo : ExtractorApi() { val response = app.get(url).text val jstounpack = Regex("eval((.|\\n)*?)").find(response)?.groups?.get(1)?.value val unpacjed = JsUnpacker(jstounpack).unpack() - val extractedUrl = - unpacjed?.let { Regex("""sources:((.|\n)*?)image""").find(it) }?.groups?.get(1)?.value.toString() - .replace("file", """"file"""").replace("label", """"label"""") - .substringBeforeLast(",") + val extractedUrl = unpacjed?.let { Regex("""sources:((.|\n)*?)image""").find(it) }?.groups?.get(1)?.value.toString().replace("file",""""file"""").replace("label",""""label"""").substringBeforeLast(",") val parsedlinks = parseJson>(extractedUrl) parsedlinks.forEach { data -> - if (data.label.isNullOrBlank()) { // mp4 links (with labels) are slow. Use only m3u8 link. + if (data.label.isNullOrBlank()){ // mp4 links (with labels) are slow. Use only m3u8 link. M3u8Helper.generateM3u8( name, data.id, @@ -37,6 +34,8 @@ open class Supervideo : ExtractorApi() { } } } + + return extractedLinksList } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Tomatomatela.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Tomatomatela.kt index 28a2eb20..20bd69ba 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Tomatomatela.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Tomatomatela.kt @@ -1,64 +1,41 @@ package com.lagradost.cloudstream3.extractors -import com.lagradost.cloudstream3.utils.* -import com.lagradost.cloudstream3.app import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.module.kotlin.readValue -import com.lagradost.cloudstream3.USER_AGENT -import com.lagradost.cloudstream3.mapper +import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.AppUtils.parseJson - +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.Qualities class Cinestart: Tomatomatela() { - override var name: String = "Cinestart" - override val mainUrl: String = "https://cinestart.net" + override var name = "Cinestart" + override var mainUrl = "https://cinestart.net" override val details = "vr.php?v=" } -class TomatomatelalClub: Tomatomatela() { - override var name: String = "Tomatomatela" - override val mainUrl: String = "https://tomatomatela.club" -} - open class Tomatomatela : ExtractorApi() { override var name = "Tomatomatela" - override val mainUrl = "https://tomatomatela.com" + override var mainUrl = "https://tomatomatela.com" override val requiresReferer = false private data class Tomato ( @JsonProperty("status") val status: Int, - @JsonProperty("file") val file: String? + @JsonProperty("file") val file: String ) open val details = "details.php?v=" - open val embeddetails = "/embed.html#" override suspend fun getUrl(url: String, referer: String?): List? { - val link = url.replace("$mainUrl$embeddetails","$mainUrl/$details") - val sources = ArrayList() - val server = app.get(link, allowRedirects = false, - headers = mapOf( - "User-Agent" to USER_AGENT, - "Accept" to "application/json, text/javascript, */*; q=0.01", - "Accept-Language" to "en-US,en;q=0.5", - "X-Requested-With" to "XMLHttpRequest", - "DNT" to "1", - "Connection" to "keep-alive", - "Sec-Fetch-Dest" to "empty", - "Sec-Fetch-Mode" to "cors", - "Sec-Fetch-Site" to "same-origin" - + val link = url.replace("$mainUrl/embed.html#","$mainUrl/$details") + val server = app.get(link, allowRedirects = false).text + val json = parseJson(server) + if (json.status == 200) return listOf( + ExtractorLink( + name, + name, + json.file, + "", + Qualities.Unknown.value, + isM3u8 = false ) - ).parsedSafe() - if (server?.file != null) { - sources.add( - ExtractorLink( - name, - name, - server.file, - "", - Qualities.Unknown.value, - isM3u8 = false - ) - ) - } - return sources + ) + return null } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt index 09e47d03..1eb384c4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt @@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper -open class UpstreamExtractor : ExtractorApi() { +class UpstreamExtractor : ExtractorApi() { override val name: String = "Upstream" override val mainUrl: String = "https://upstream.to" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt index 86bd9e0b..5109acc3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt @@ -7,10 +7,6 @@ class Uqload1 : Uqload() { override var mainUrl = "https://uqload.com" } -class Uqload2 : Uqload() { - override var mainUrl = "https://uqload.co" -} - open class Uqload : ExtractorApi() { override val name: String = "Uqload" override val mainUrl: String = "https://www.uqload.com" @@ -19,14 +15,30 @@ open class Uqload : ExtractorApi() { override suspend fun getUrl(url: String, referer: String?): List? { - with(app.get(url)) { // raised error ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED (3003) is due to the response: "error_nofile" + val lang = url.substring(0, 2) + val flag = + if (lang == "vo") { + " \uD83C\uDDEC\uD83C\uDDE7" + } + else if (lang == "vf"){ + " \uD83C\uDDE8\uD83C\uDDF5" + } else { + "" + } + + val cleaned_url = if (lang == "ht") { // if url doesn't contain a flag and the url starts with http:// + url + } else { + url.substring(2, url.length) + } + with(app.get(cleaned_url)) { // raised error ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED (3003) is due to the response: "error_nofile" srcRegex.find(this.text)?.groupValues?.get(1)?.replace("\"", "")?.let { link -> return listOf( ExtractorLink( name, - name, + name + flag, link, - url, + cleaned_url, Qualities.Unknown.value, ) ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt index a27bf188..b910f9dd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt @@ -59,8 +59,8 @@ open class VidSrcExtractor : ExtractorApi() { if (datahash.isNotBlank()) { val links = try { app.get( - "$absoluteUrl/srcrcp/$datahash", - referer = "https://rcp.vidsrc.me/" + "$absoluteUrl/src/$datahash", + referer = "https://source.vidsrc.me/" ).url } catch (e: Exception) { "" @@ -71,7 +71,7 @@ open class VidSrcExtractor : ExtractorApi() { serverslist.amap { server -> val linkfixed = server.replace("https://vidsrc.xyz/", "https://embedsito.com/") - if (linkfixed.contains("/prorcp")) { + if (linkfixed.contains("/pro")) { val srcresponse = app.get(server, referer = absoluteUrl).text val m3u8Regex = Regex("((https:|http:)//.*\\.m3u8)") val srcm3u8 = m3u8Regex.find(srcresponse)?.value ?: return@amap diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt index 30a1d8fe..41e77967 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt @@ -11,7 +11,7 @@ class VideovardSX : WcoStream() { override var mainUrl = "https://videovard.sx" } -open class VideoVard : ExtractorApi() { +class VideoVard : ExtractorApi() { override var name = "Videovard" // Cause works for animekisa and wco override var mainUrl = "https://videovard.to" override val requiresReferer = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vido.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vido.kt deleted file mode 100644 index 67e59281..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vido.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.lagradost.cloudstream3.extractors -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.ExtractorApi -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.Qualities -import com.lagradost.cloudstream3.utils.getAndUnpack - -class Vido : ExtractorApi() { - override var name = "Vido" - override var mainUrl = "https://vido.lol" - private val srcRegex = Regex("""sources:\s*\["(.*?)"\]""") - override val requiresReferer = true - - override suspend fun getUrl(url: String, referer: String?): List? { - val methode = app.get(url.replace("/e/", "/embed-")) // fix wiflix and mesfilms - with(methode) { - if (!methode.isSuccessful) return null - //val quality = unpackedText.lowercase().substringAfter(" height=").substringBefore(" ").toIntOrNull() - srcRegex.find(this.text)?.groupValues?.get(1)?.let { link -> - return listOf( - ExtractorLink( - name, - name, - link, - url, - Qualities.Unknown.value, - true, - ) - ) - } - } - return null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt index ad3f0150..d2f3f832 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt @@ -13,42 +13,39 @@ open class VoeExtractor : ExtractorApi() { override val requiresReferer = false private data class ResponseLinks( - @JsonProperty("hls") val hls: String?, - @JsonProperty("mp4") val mp4: String?, + @JsonProperty("hls") val url: String?, @JsonProperty("video_height") val label: Int? //val type: String // Mp4 ) override suspend fun getUrl(url: String, referer: String?): List { - val html = app.get(url).text - if (html.isNotBlank()) { - val src = html.substringAfter("const sources =").substringBefore(";") - // Remove last comma, it is not proper json otherwise + val extractedLinksList: MutableList = mutableListOf() + val doc = app.get(url).text + if (doc.isNotBlank()) { + val start = "const sources =" + var src = doc.substring(doc.indexOf(start)) + src = src.substring(start.length, src.indexOf(";")) .replace("0,", "0") - // Make json use the proper quotes - .replace("'", "\"") - + .trim() //Log.i(this.name, "Result => (src) ${src}") - parseJson(src)?.let { voeLink -> - //Log.i(this.name, "Result => (voeLink) ${voeLink}") - - // Always defaults to the hls link, but returns the mp4 if null - val linkUrl = voeLink.hls ?: voeLink.mp4 - val linkLabel = voeLink.label?.toString() ?: "" + parseJson(src)?.let { voelink -> + //Log.i(this.name, "Result => (voelink) ${voelink}") + val linkUrl = voelink.url + val linkLabel = voelink.label?.toString() ?: "" if (!linkUrl.isNullOrEmpty()) { - return listOf( + extractedLinksList.add( ExtractorLink( name = this.name, source = this.name, url = linkUrl, quality = getQualityFromName(linkLabel), referer = url, - isM3u8 = voeLink.hls != null + isM3u8 = true ) ) } } } - return emptyList() + return extractedLinksList } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt index 6cc486cd..d99485ea 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt @@ -53,12 +53,6 @@ class VizcloudSite : WcoStream() { override var mainUrl = "https://vizcloud.site" } -class Mcloud : WcoStream() { - override var name = "Mcloud" - override var mainUrl = "https://mcloud.to" - override val requiresReferer = true -} - open class WcoStream : ExtractorApi() { override var name = "VidStream" // Cause works for animekisa and wco override var mainUrl = "https://vidstream.pro" diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt index c7aa989d..3c564f67 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.getQualityFromName -open class YourUpload: ExtractorApi() { +class YourUpload: ExtractorApi() { override val name = "Yourupload" override val mainUrl = "https://www.yourupload.com" override val requiresReferer = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Zorofile.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Zorofile.kt index 43c4eefb..84785b6c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Zorofile.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Zorofile.kt @@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper -open class Zorofile : ExtractorApi() { +class Zorofile : ExtractorApi() { override val name = "Zorofile" override val mainUrl = "https://zorofile.com" override val requiresReferer = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/AnilistRedirector.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/AnilistRedirector.kt new file mode 100644 index 00000000..208db14b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/AnilistRedirector.kt @@ -0,0 +1,30 @@ +package com.lagradost.cloudstream3.metaproviders + +import com.lagradost.cloudstream3.ErrorLoadingException +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi +import com.lagradost.cloudstream3.utils.SyncUtil + +object SyncRedirector { + val syncApis = SyncApis + + suspend fun redirect(url: String, preferredUrl: String): String { + for (api in syncApis) { + if (url.contains(api.mainUrl)) { + val otherApi = when (api.name) { + aniListApi.name -> "anilist" + malApi.name -> "myanimelist" + else -> return url + } + + return SyncUtil.getUrlsFromId(api.getIdFromUrl(url), otherApi).firstOrNull { realUrl -> + realUrl.contains(preferredUrl) + } ?: run { + throw ErrorLoadingException("Page does not exist on $preferredUrl") + } + } + } + return url + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt deleted file mode 100644 index 75e96bec..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.lagradost.cloudstream3.metaproviders - -import com.lagradost.cloudstream3.MainAPI -import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall -import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis -import com.lagradost.cloudstream3.syncproviders.SyncIdName - -object SyncRedirector { - val syncApis = SyncApis - private val syncIds = - listOf( - SyncIdName.MyAnimeList to Regex("""myanimelist\.net\/anime\/(\d+)"""), - SyncIdName.Anilist to Regex("""anilist\.co\/anime\/(\d+)""") - ) - - suspend fun redirect( - url: String, - providerApi: MainAPI - ): String { - // Deprecated since providers should do this instead! - - // Tries built in ID -> ProviderUrl - /* - for (api in syncApis) { - if (url.contains(api.mainUrl)) { - val otherApi = when (api.name) { - aniListApi.name -> "anilist" - malApi.name -> "myanimelist" - else -> return url - } - - SyncUtil.getUrlsFromId(api.getIdFromUrl(url), otherApi).firstOrNull { realUrl -> - realUrl.contains(providerApi.mainUrl) - }?.let { - return it - } -// ?: run { -// throw ErrorLoadingException("Page does not exist on $preferredUrl") -// } - } - } - */ - - // Tries provider solution - // This goes through all sync ids and finds supported id by said provider - return syncIds.firstNotNullOfOrNull { (syncName, syncRegex) -> - if (providerApi.supportedSyncNames.contains(syncName)) { - syncRegex.find(url)?.value?.let { - suspendSafeApiCall { - providerApi.getLoadUrl(syncName, it) - } - } - } else null - } ?: url - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt index bb15bc85..e5c03d64 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt @@ -53,10 +53,6 @@ fun LifecycleOwner.observe(liveData: LiveData, action: (t: T) -> Unit) { liveData.observe(this) { it?.let { t -> action(t) } } } -fun LifecycleOwner.observeNullable(liveData: LiveData, action: (t: T) -> Unit) { - liveData.observe(this) { action(it) } -} - inline fun some(value: T?): Some { return if (value == null) { Some.None @@ -121,21 +117,13 @@ suspend fun suspendSafeApiCall(apiCall: suspend () -> T): T? { } } -fun Throwable.getAllMessages(): String { - return (this.localizedMessage ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "") -} - -fun Throwable.getStackTracePretty(showMessage: Boolean = true): String { - 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 { - val stackTraceMsg = throwable.getStackTracePretty() + val stackTraceMsg = + (throwable.localizedMessage ?: "") + "\n\n" + throwable.stackTrace.joinToString( + separator = "\n" + ) { + "${it.fileName} ${it.lineNumber}" + } return Resource.Failure(false, null, null, stackTraceMsg) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt b/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt index 6950d961..7dc8dba7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt @@ -5,7 +5,6 @@ import android.webkit.CookieManager import androidx.annotation.AnyThread import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.debugWarning -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.nicehttp.Requests.Companion.await import com.lagradost.nicehttp.cookies import kotlinx.coroutines.runBlocking @@ -27,10 +26,7 @@ class CloudflareKiller : Interceptor { init { // Needs to clear cookies between sessions to generate new cookies. - normalSafeApiCall { - // This can throw an exception on unsupported devices :( - CookieManager.getInstance().removeAllCookies(null) - } + CookieManager.getInstance().removeAllCookies(null) } val savedCookies: MutableMap> = mutableMapOf() @@ -39,7 +35,7 @@ class CloudflareKiller : Interceptor { * Gets the headers with cookies, webview user agent included! * */ fun getCookieHeaders(url: String): Headers { - val userAgentHeaders = WebViewResolver.webViewUserAgent?.let { + val userAgentHeaders = WebViewResolver.webViewUserAgent?.let { mapOf("user-agent" to it) } ?: emptyMap() @@ -64,9 +60,7 @@ class CloudflareKiller : Interceptor { } private fun getWebViewCookie(url: String): String? { - return normalSafeApiCall { - CookieManager.getInstance()?.getCookie(url) - } + return CookieManager.getInstance()?.getCookie(url) } /** diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/DdosGuardKiller.kt b/app/src/main/java/com/lagradost/cloudstream3/network/DdosGuardKiller.kt index b5783f78..dca3ee00 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/DdosGuardKiller.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/DdosGuardKiller.kt @@ -2,7 +2,7 @@ package com.lagradost.cloudstream3.network import androidx.annotation.AnyThread import com.lagradost.cloudstream3.app -import com.lagradost.nicehttp.Requests +import com.lagradost.nicehttp.Requests.Companion.await import com.lagradost.nicehttp.cookies import kotlinx.coroutines.runBlocking import okhttp3.Interceptor @@ -41,8 +41,7 @@ class DdosGuardKiller(private val alwaysBypass: Boolean) : Interceptor { savedCookiesMap[request.url.host] // If no cookies are found fetch and save em. ?: (request.url.scheme + "://" + request.url.host + (ddosBypassPath ?: "")).let { - // Somehow app.get fails - Requests().get(it).cookies.also { cookies -> + app.get(it, cacheTime = 0).cookies.also { cookies -> savedCookiesMap[request.url.host] = cookies } } @@ -52,6 +51,6 @@ class DdosGuardKiller(private val alwaysBypass: Boolean) : Interceptor { request.newBuilder() .headers(headers) .build() - ).execute() + ).await() } } \ No newline at end of file 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 a1d84f6c..8bf1f91b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt @@ -4,19 +4,15 @@ import android.content.Context import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.USER_AGENT -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.nicehttp.Requests import com.lagradost.nicehttp.ignoreAllSSLErrors import okhttp3.Cache import okhttp3.Headers import okhttp3.Headers.Companion.toHeaders import okhttp3.OkHttpClient -import org.conscrypt.Conscrypt import java.io.File -import java.security.Security fun Requests.initClient(context: Context): OkHttpClient { - normalSafeApiCall { Security.insertProviderAt(Conscrypt.newProvider(), 1) } val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) val dns = settingsManager.getInt(context.getString(R.string.dns_pref), 0) baseClient = OkHttpClient.Builder() diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 0dee57eb..b9c775c0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -10,18 +10,14 @@ import android.util.Log import android.widget.Toast import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat -import androidx.fragment.app.FragmentActivity import com.fasterxml.jackson.annotation.JsonProperty import com.google.gson.Gson import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings import com.lagradost.cloudstream3.APIHolder.removePluginMapping -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.showToast -import com.lagradost.cloudstream3.MainAPI.Companion.settingsForProvider import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.mvvm.debugPrint import com.lagradost.cloudstream3.mvvm.logError @@ -30,8 +26,6 @@ import com.lagradost.cloudstream3.plugins.RepositoryManager.ONLINE_PLUGINS_FOLDE import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES import com.lagradost.cloudstream3.plugins.RepositoryManager.downloadPluginToFile import com.lagradost.cloudstream3.plugins.RepositoryManager.getRepoPlugins -import com.lagradost.cloudstream3.ui.result.UiText -import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData import com.lagradost.cloudstream3.utils.Coroutines.main @@ -145,10 +139,8 @@ object PluginManager { return getKey(PLUGINS_KEY_LOCAL) ?: emptyArray() } - private val CLOUD_STREAM_FOLDER = - Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/" - - private val LOCAL_PLUGINS_PATH = CLOUD_STREAM_FOLDER + "plugins" + private val LOCAL_PLUGINS_PATH = + Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/plugins" public var currentlyLoading: String? = null @@ -166,11 +158,11 @@ object PluginManager { private var loadedLocalPlugins = false private val gson = Gson() - private suspend fun maybeLoadPlugin(context: Context, file: File) { + private suspend fun maybeLoadPlugin(activity: Activity, file: File) { val name = file.name if (file.extension == "zip" || file.extension == "cs3") { loadPlugin( - context, + activity, file, PluginData(name, null, false, file.absolutePath, PLUGIN_VERSION_NOT_SET) ) @@ -200,7 +192,7 @@ object PluginManager { // var allCurrentOutDatedPlugins: Set = emptySet() - suspend fun loadSinglePlugin(context: Context, apiName: String): Boolean { + suspend fun loadSinglePlugin(activity: Activity, apiName: String): Boolean { return (getPluginsOnline().firstOrNull { // Most of the time the provider ends with Provider which isn't part of the api name it.internalName.replace("provider", "", ignoreCase = true) == apiName @@ -210,7 +202,7 @@ object PluginManager { })?.let { savedData -> // OnlinePluginData(savedData, onlineData) loadPlugin( - context, + activity, File(savedData.filePath), savedData ) @@ -227,7 +219,9 @@ object PluginManager { fun updateAllOnlinePluginsAndLoadThem(activity: Activity) { // Load all plugins as fast as possible! loadAllOnlinePlugins(activity) - afterPluginsLoadedEvent.invoke(false) + + afterPluginsLoadedEvent.invoke(true) + val urls = (getKey>(REPOSITORIES_KEY) ?: emptyArray()) + PREBUILT_REPOSITORIES @@ -258,12 +252,11 @@ object PluginManager { //updatedPlugins.add(activity.getString(R.string.single_plugin_disabled, pluginData.onlineData.second.name)) unloadPlugin(pluginData.savedData.filePath) } else if (pluginData.isOutdated) { - downloadPlugin( + downloadAndLoadPlugin( activity, pluginData.onlineData.second.url, pluginData.savedData.internalName, - File(pluginData.savedData.filePath), - true + File(pluginData.savedData.filePath) ).let { success -> if (success) updatedPlugins.add(pluginData.onlineData.second.name) @@ -272,134 +265,31 @@ object PluginManager { } main { - val uitext = txt(R.string.plugins_updated, updatedPlugins.size) - createNotification(activity, uitext, updatedPlugins) + createNotification(activity, updatedPlugins) } - // ioSafe { - afterPluginsLoadedEvent.invoke(false) - // } + // ioSafe { + afterPluginsLoadedEvent.invoke(true) + // } Log.i(TAG, "Plugin update done!") } - /** - * Automatically download plugins not yet existing on local - * 1. Gets all online data from online plugins repo - * 2. Fetch all not downloaded plugins - * 3. Download them and reload plugins - **/ - fun downloadNotExistingPluginsAndLoad(activity: Activity) { - val newDownloadPlugins = mutableListOf() - val urls = (getKey>(REPOSITORIES_KEY) - ?: emptyArray()) + PREBUILT_REPOSITORIES - val onlinePlugins = urls.toList().apmap { - getRepoPlugins(it.url)?.toList() ?: emptyList() - }.flatten().distinctBy { it.second.url } - - val providerLang = activity.getApiProviderLangSettings() - //Log.i(TAG, "providerLang => ${providerLang.toJson()}") - - // Iterate online repos and returns not downloaded plugins - val notDownloadedPlugins = onlinePlugins.mapNotNull { onlineData -> - val sitePlugin = onlineData.second - //Don't include empty urls - if (sitePlugin.url.isBlank()) { - return@mapNotNull null - } - if (sitePlugin.repositoryUrl.isNullOrBlank()) { - return@mapNotNull null - } - - //Omit already existing plugins - if (getPluginPath(activity, sitePlugin.internalName, onlineData.first).exists()) { - Log.i(TAG, "Skip > ${sitePlugin.internalName}") - return@mapNotNull null - } - - //Omit lang not selected on language setting - val lang = sitePlugin.language ?: return@mapNotNull null - //If set to 'universal', don't skip any language - if (!providerLang.contains(AllLanguagesName) && !providerLang.contains(lang)) { - return@mapNotNull null - } - //Log.i(TAG, "sitePlugin lang => $lang") - - //Omit NSFW, if disabled - sitePlugin.tvTypes?.let { tvtypes -> - if (!settingsForProvider.enableAdult) { - if (tvtypes.contains(TvType.NSFW.name)) { - return@mapNotNull null - } - } - } - val savedData = PluginData( - url = sitePlugin.url, - internalName = sitePlugin.internalName, - isOnline = true, - filePath = "", - version = sitePlugin.version - ) - OnlinePluginData(savedData, onlineData) - } - //Log.i(TAG, "notDownloadedPlugins => ${notDownloadedPlugins.toJson()}") - - notDownloadedPlugins.apmap { pluginData -> - downloadPlugin( - activity, - pluginData.onlineData.second.url, - pluginData.savedData.internalName, - pluginData.onlineData.first, - !pluginData.isDisabled - ).let { success -> - if (success) - newDownloadPlugins.add(pluginData.onlineData.second.name) - } - } - - main { - val uitext = txt(R.string.plugins_downloaded, newDownloadPlugins.size) - createNotification(activity, uitext, newDownloadPlugins) - } - - // ioSafe { - afterPluginsLoadedEvent.invoke(false) - // } - - Log.i(TAG, "Plugin download done!") - } - /** * Use updateAllOnlinePluginsAndLoadThem * */ - fun loadAllOnlinePlugins(context: Context) { + fun loadAllOnlinePlugins(activity: Activity) { // Load all plugins as fast as possible! (getPluginsOnline()).toList().apmap { pluginData -> loadPlugin( - context, + activity, File(pluginData.filePath), pluginData ) } } - /** - * Reloads all local plugins and forces a page update, used for hot reloading with deployWithAdb - **/ - fun hotReloadAllLocalPlugins(activity: FragmentActivity?) { - Log.d(TAG, "Reloading all local plugins!") - if (activity == null) return - getPluginsLocal().forEach { - unloadPlugin(it.filePath) - } - loadAllLocalPlugins(activity, true) - } - - /** - * @param forceReload see afterPluginsLoadedEvent, basically a way to load all local plugins - * and reload all pages even if they are previously valid - **/ - fun loadAllLocalPlugins(context: Context, forceReload: Boolean) { + fun loadAllLocalPlugins(activity: Activity) { val dir = File(LOCAL_PLUGINS_PATH) removeKey(PLUGINS_KEY_LOCAL) @@ -417,39 +307,24 @@ object PluginManager { Log.d(TAG, "Files in '${LOCAL_PLUGINS_PATH}' folder: $sortedPlugins") sortedPlugins?.sortedBy { it.name }?.apmap { file -> - maybeLoadPlugin(context, file) + maybeLoadPlugin(activity, file) } loadedLocalPlugins = true - afterPluginsLoadedEvent.invoke(forceReload) - } - - /** - * This can be used to override any extension loading to fix crashes! - * @return true if safe mode file is present - **/ - fun checkSafeModeFile(): Boolean { - return normalSafeApiCall { - val folder = File(CLOUD_STREAM_FOLDER) - if (!folder.exists()) return@normalSafeApiCall false - val files = folder.listFiles { _, name -> - name.equals("safe", ignoreCase = true) - } - files?.any() - } ?: false + afterPluginsLoadedEvent.invoke(true) } /** * @return True if successful, false if not * */ - private suspend fun loadPlugin(context: Context, file: File, data: PluginData): Boolean { + private suspend fun loadPlugin(activity: Activity, file: File, data: PluginData): Boolean { val fileName = file.nameWithoutExtension val filePath = file.absolutePath currentlyLoading = fileName Log.i(TAG, "Loading plugin: $data") return try { - val loader = PathClassLoader(filePath, context.classLoader) + val loader = PathClassLoader(filePath, activity.classLoader) var manifest: Plugin.Manifest loader.getResourceAsStream("manifest.json").use { stream -> if (stream == null) { @@ -493,22 +368,22 @@ object PluginManager { addAssetPath.invoke(assets, file.absolutePath) pluginInstance.resources = Resources( assets, - context.resources.displayMetrics, - context.resources.configuration + activity.resources.displayMetrics, + activity.resources.configuration ) } plugins[filePath] = pluginInstance classLoaders[loader] = pluginInstance urlPlugins[data.url ?: filePath] = pluginInstance - pluginInstance.load(context) + pluginInstance.load(activity) Log.i(TAG, "Loaded plugin ${data.internalName} successfully") currentlyLoading = null true } catch (e: Throwable) { Log.e(TAG, "Failed to load $file: ${Log.getStackTraceString(e)}") showToast( - context.getActivity(), - context.getString(R.string.plugin_load_fail).format(fileName), + activity, + activity.getString(R.string.plugin_load_fail).format(fileName), Toast.LENGTH_LONG ) currentlyLoading = null @@ -516,7 +391,7 @@ object PluginManager { } } - fun unloadPlugin(absolutePath: String) { + private fun unloadPlugin(absolutePath: String) { Log.i(TAG, "Unloading plugin: $absolutePath") val plugin = plugins[absolutePath] if (plugin == null) { @@ -567,48 +442,49 @@ object PluginManager { return File("${context.filesDir}/${ONLINE_PLUGINS_FOLDER}/${folderName}/$fileName.cs3") } - suspend fun downloadPlugin( + /** + * Used for fresh installs + * */ + suspend fun downloadAndLoadPlugin( activity: Activity, pluginUrl: String, internalName: String, - repositoryUrl: String, - loadPlugin: Boolean + repositoryUrl: String ): Boolean { val file = getPluginPath(activity, internalName, repositoryUrl) - return downloadPlugin(activity, pluginUrl, internalName, file, loadPlugin) + downloadAndLoadPlugin(activity, pluginUrl, internalName, file) + return true } - suspend fun downloadPlugin( + /** + * Used for updates. + * + * Uses a file instead of repository url, as extensions can get moved it is better to directly + * update the files instead of getting the filepath from repo url. + * */ + private suspend fun downloadAndLoadPlugin( activity: Activity, pluginUrl: String, internalName: String, file: File, - loadPlugin: Boolean ): Boolean { try { + unloadPlugin(file.absolutePath) + Log.d(TAG, "Downloading plugin: $pluginUrl to ${file.absolutePath}") // The plugin file needs to be salted with the repository url hash as to allow multiple repositories with the same internal plugin names - val newFile = downloadPluginToFile(pluginUrl, file) ?: return false - - val data = PluginData( - internalName, - pluginUrl, - true, - newFile.absolutePath, - PLUGIN_VERSION_NOT_SET - ) - - return if (loadPlugin) { - unloadPlugin(file.absolutePath) - loadPlugin( - activity, - newFile, - data + val newFile = downloadPluginToFile(pluginUrl, file) + return loadPlugin( + activity, + newFile ?: return false, + PluginData( + internalName, + pluginUrl, + true, + newFile.absolutePath, + PLUGIN_VERSION_NOT_SET ) - } else { - setPluginData(data) - true - } + ) } catch (e: Exception) { logError(e) return false @@ -616,8 +492,7 @@ object PluginManager { } suspend fun deletePlugin(file: File): Boolean { - val list = - (getPluginsLocal() + getPluginsOnline()).filter { it.filePath == file.absolutePath } + val list = (getPluginsLocal() + getPluginsOnline()).filter { it.filePath == file.absolutePath } return try { if (File(file.absolutePath).delete()) { @@ -652,14 +527,12 @@ object PluginManager { private fun createNotification( context: Context, - uitext: UiText, - extensions: List + extensionNames: List ): Notification? { try { + if (extensionNames.isEmpty()) return null - if (extensions.isEmpty()) return null - - val content = extensions.joinToString(", ") + val content = extensionNames.joinToString(", ") // main { // DON'T WANT TO SLOW IT DOWN val builder = NotificationCompat.Builder(context, EXTENSIONS_CHANNEL_ID) .setAutoCancel(false) @@ -668,8 +541,7 @@ object PluginManager { .setSilent(true) .setPriority(NotificationCompat.PRIORITY_LOW) .setColor(context.colorFromAttribute(R.attr.colorPrimary)) - .setContentTitle(uitext.asString(context)) - //.setContentTitle(context.getString(title, extensionNames.size)) + .setContentTitle(context.getString(R.string.plugins_updated, extensionNames.size)) .setSmallIcon(R.drawable.ic_baseline_extension_24) .setStyle( NotificationCompat.BigTextStyle() diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt index 742bf308..0f23782d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt @@ -2,17 +2,13 @@ package com.lagradost.cloudstream3.plugins import android.content.Context import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.AcraApplication.Companion.context import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey -import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.plugins.PluginManager.getPluginSanitizedFileName -import com.lagradost.cloudstream3.plugins.PluginManager.unloadPlugin import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson @@ -73,15 +69,6 @@ object RepositoryManager { val PREBUILT_REPOSITORIES: Array by lazy { getKey("PREBUILT_REPOSITORIES") ?: emptyArray() } - val GH_REGEX = Regex("^https://raw.githubusercontent.com/([A-Za-z0-9-]+)/([A-Za-z0-9_.-]+)/(.*)$") - - /* Convert raw.githubusercontent.com urls to cdn.jsdelivr.net if enabled in settings */ - fun convertRawGitUrl(url: String): String { - if (getKey(context!!.getString(R.string.jsdelivr_proxy_key)) != true) return url - val match = GH_REGEX.find(url) ?: return url - val (user, repo, rest) = match.destructured - return "https://cdn.jsdelivr.net/gh/$user/$repo@$rest" - } suspend fun parseRepoUrl(url: String): String? { val fixedUrl = url.trim() @@ -90,20 +77,15 @@ object RepositoryManager { } else if (fixedUrl.contains("^(cloudstreamrepo://)|(https://cs\\.repo/\\??)".toRegex())) { fixedUrl.replace("^(cloudstreamrepo://)|(https://cs\\.repo/\\??)".toRegex(), "").let { return@let if (!it.contains("^https?://".toRegex())) - "https://${it}" + "https://${it}" else fixedUrl } } else if (fixedUrl.matches("^[a-zA-Z0-9!_-]+$".toRegex())) { suspendSafeApiCall { - app.get("https://l.cloudstream.cf/${fixedUrl}", allowRedirects = false).let { - it.headers["Location"]?.let { url -> - return@suspendSafeApiCall if (!url.startsWith("https://cutt.ly/branded-domains")) url - else null - } - app.get("https://cutt.ly/${fixedUrl}", allowRedirects = false).let { it2 -> - it2.headers["Location"]?.let { url -> - return@suspendSafeApiCall if (url.startsWith("https://cutt.ly/404")) url else null - } + app.get("https://l.cloudstream.cf/${fixedUrl}").let { + return@let if (it.isSuccessful && !it.url.startsWith("https://cutt.ly/branded-domains")) it.url + else app.get("https://cutt.ly/${fixedUrl}").let let2@{ it2 -> + return@let2 if (it2.isSuccessful) it2.url else null } } } @@ -113,14 +95,14 @@ object RepositoryManager { suspend fun parseRepository(url: String): Repository? { return suspendSafeApiCall { // Take manifestVersion and such into account later - app.get(convertRawGitUrl(url)).parsedSafe() + app.get(url).parsedSafe() } } private suspend fun parsePlugins(pluginUrls: String): List { // Take manifestVersion and such into account later return try { - val response = app.get(convertRawGitUrl(pluginUrls)) + val response = app.get(pluginUrls) // Normal parsed function not working? // return response.parsedSafe() tryParseJson>(response.text)?.toList() ?: emptyList() @@ -150,17 +132,43 @@ object RepositoryManager { file.mkdirs() // Overwrite if exists - if (file.exists()) { - file.delete() - } + if (file.exists()) { file.delete() } file.createNewFile() - val body = app.get(convertRawGitUrl(pluginUrl)).okhttpResponse.body + val body = app.get(pluginUrl).okhttpResponse.body write(body.byteStream(), file.outputStream()) file } } + suspend fun downloadPluginToFile( + context: Context, + pluginUrl: String, + /** Filename without .cs3 */ + fileName: String, + folder: String + ): File? { + return suspendSafeApiCall { + val extensionsDir = File(context.filesDir, ONLINE_PLUGINS_FOLDER) + if (!extensionsDir.exists()) + extensionsDir.mkdirs() + + val newDir = File(extensionsDir, folder) + newDir.mkdirs() + + val newFile = File(newDir, "${fileName}.cs3") + // Overwrite if exists + if (newFile.exists()) { + newFile.delete() + } + newFile.createNewFile() + + val body = app.get(pluginUrl).okhttpResponse.body + write(body.byteStream(), newFile.outputStream()) + newFile + } + } + fun getRepositories(): Array { return getKey(REPOSITORIES_KEY) ?: emptyArray() } @@ -192,17 +200,9 @@ object RepositoryManager { extensionsDir, getPluginSanitizedFileName(repository.url) ) - - // Unload all plugins, not using deletePlugin since we - // delete all data and files in deleteRepositoryData - normalSafeApiCall { - file.listFiles { plugin: File -> - unloadPlugin(plugin.absolutePath) - false - } - } - PluginManager.deleteRepositoryData(file.absolutePath) + + file.delete() } private fun write(stream: InputStream, output: OutputStream) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt b/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt deleted file mode 100644 index adf5abfa..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt +++ /dev/null @@ -1,224 +0,0 @@ -package com.lagradost.cloudstream3.services - -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.os.Build -import androidx.core.app.NotificationCompat -import androidx.core.net.toUri -import androidx.work.* -import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings -import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.mvvm.safeApiCall -import com.lagradost.cloudstream3.plugins.PluginManager -import com.lagradost.cloudstream3.ui.result.txt -import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel -import com.lagradost.cloudstream3.utils.Coroutines.ioWork -import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions -import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub -import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute -import com.lagradost.cloudstream3.utils.VideoDownloadManager.getImageBitmapFromUrl -import kotlinx.coroutines.withTimeoutOrNull -import java.util.concurrent.TimeUnit - -const val SUBSCRIPTION_CHANNEL_ID = "cloudstream3.subscriptions" -const val SUBSCRIPTION_WORK_NAME = "work_subscription" -const val SUBSCRIPTION_CHANNEL_NAME = "Subscriptions" -const val SUBSCRIPTION_CHANNEL_DESCRIPTION = "Notifications for new episodes on subscribed shows" -const val SUBSCRIPTION_NOTIFICATION_ID = 938712897 // Random unique - -class SubscriptionWorkManager(val context: Context, workerParams: WorkerParameters) : - CoroutineWorker(context, workerParams) { - companion object { - fun enqueuePeriodicWork(context: Context?) { - if (context == null) return - - val constraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() - - val periodicSyncDataWork = - PeriodicWorkRequest.Builder(SubscriptionWorkManager::class.java, 6, TimeUnit.HOURS) - .addTag(SUBSCRIPTION_WORK_NAME) - .setConstraints(constraints) - .build() - - WorkManager.getInstance(context).enqueueUniquePeriodicWork( - SUBSCRIPTION_WORK_NAME, - ExistingPeriodicWorkPolicy.KEEP, - periodicSyncDataWork - ) - - // Uncomment below for testing - -// val oneTimeSyncDataWork = -// OneTimeWorkRequest.Builder(SubscriptionWorkManager::class.java) -// .addTag(SUBSCRIPTION_WORK_NAME) -// .setConstraints(constraints) -// .build() -// -// WorkManager.getInstance(context).enqueue(oneTimeSyncDataWork) - } - } - - private val progressNotificationBuilder = - NotificationCompat.Builder(context, SUBSCRIPTION_CHANNEL_ID) - .setAutoCancel(false) - .setColorized(true) - .setOnlyAlertOnce(true) - .setSilent(true) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setColor(context.colorFromAttribute(R.attr.colorPrimary)) - .setContentTitle(context.getString(R.string.subscription_in_progress_notification)) - .setSmallIcon(R.drawable.quantum_ic_refresh_white_24) - .setProgress(0, 0, true) - - private val updateNotificationBuilder = - NotificationCompat.Builder(context, SUBSCRIPTION_CHANNEL_ID) - .setColorized(true) - .setOnlyAlertOnce(true) - .setAutoCancel(true) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setColor(context.colorFromAttribute(R.attr.colorPrimary)) - .setSmallIcon(R.drawable.ic_cloudstream_monochrome_big) - - private val notificationManager: NotificationManager = - context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - private fun updateProgress(max: Int, progress: Int, indeterminate: Boolean) { - notificationManager.notify( - SUBSCRIPTION_NOTIFICATION_ID, progressNotificationBuilder - .setProgress(max, progress, indeterminate) - .build() - ) - } - - override suspend fun doWork(): Result { -// println("Update subscriptions!") - context.createNotificationChannel( - SUBSCRIPTION_CHANNEL_ID, - SUBSCRIPTION_CHANNEL_NAME, - SUBSCRIPTION_CHANNEL_DESCRIPTION - ) - - setForeground( - ForegroundInfo( - SUBSCRIPTION_NOTIFICATION_ID, - progressNotificationBuilder.build() - ) - ) - - val subscriptions = getAllSubscriptions() - - if (subscriptions.isEmpty()) { - WorkManager.getInstance(context).cancelWorkById(this.id) - return Result.success() - } - - val max = subscriptions.size - var progress = 0 - - updateProgress(max, progress, true) - - // We need all plugins loaded. - PluginManager.loadAllOnlinePlugins(context) - PluginManager.loadAllLocalPlugins(context, false) - - subscriptions.apmap { savedData -> - try { - val id = savedData.id ?: return@apmap null - val api = getApiFromNameNull(savedData.apiName) ?: return@apmap null - - // Reasonable timeout to prevent having this worker run forever. - val response = withTimeoutOrNull(60_000) { - api.load(savedData.url) as? EpisodeResponse - } ?: return@apmap null - - val dubPreference = - getDub(id) ?: if ( - context.getApiDubstatusSettings().contains(DubStatus.Dubbed) - ) { - DubStatus.Dubbed - } else { - DubStatus.Subbed - } - - val latestEpisodes = response.getLatestEpisodes() - val latestPreferredEpisode = latestEpisodes[dubPreference] - - val (shouldUpdate, latestEpisode) = if (latestPreferredEpisode != null) { - val latestSeenEpisode = - savedData.lastSeenEpisodeCount[dubPreference] ?: Int.MIN_VALUE - val shouldUpdate = latestPreferredEpisode > latestSeenEpisode - shouldUpdate to latestPreferredEpisode - } else { - val latestEpisode = latestEpisodes[DubStatus.None] ?: Int.MIN_VALUE - val latestSeenEpisode = - savedData.lastSeenEpisodeCount[DubStatus.None] ?: Int.MIN_VALUE - val shouldUpdate = latestEpisode > latestSeenEpisode - shouldUpdate to latestEpisode - } - - DataStoreHelper.updateSubscribedData( - id, - savedData, - response - ) - - if (shouldUpdate) { - val updateHeader = savedData.name - val updateDescription = txt( - R.string.subscription_episode_released, - latestEpisode, - savedData.name - ).asString(context) - - val intent = Intent(context, MainActivity::class.java).apply { - data = savedData.url.toUri() - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - } - - val pendingIntent = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - PendingIntent.getActivity( - context, - 0, - intent, - PendingIntent.FLAG_IMMUTABLE - ) - } else { - PendingIntent.getActivity(context, 0, intent, 0) - } - - val poster = ioWork { - savedData.posterUrl?.let { url -> - context.getImageBitmapFromUrl( - url, - savedData.posterHeaders - ) - } - } - - val updateNotification = - updateNotificationBuilder.setContentTitle(updateHeader) - .setContentText(updateDescription) - .setContentIntent(pendingIntent) - .setLargeIcon(poster) - .build() - - notificationManager.notify(id, updateNotification) - } - - // You can probably get some issues here since this is async but it does not matter much. - updateProgress(max, ++progress, false) - } catch (_: Throwable) { - } - } - - return Result.success() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/services/VideoDownloadService.kt b/app/src/main/java/com/lagradost/cloudstream3/services/VideoDownloadService.kt index 6151a0ed..be2fe75b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/services/VideoDownloadService.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/services/VideoDownloadService.kt @@ -1,22 +1,11 @@ package com.lagradost.cloudstream3.services -import android.app.Service + +import android.app.IntentService import android.content.Intent -import android.os.IBinder import com.lagradost.cloudstream3.utils.VideoDownloadManager -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch -class VideoDownloadService : Service() { - - private val downloadScope = CoroutineScope(Dispatchers.Default) - - override fun onBind(intent: Intent?): IBinder? { - return null - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { +class VideoDownloadService : IntentService("VideoDownloadService") { + override fun onHandleIntent(intent: Intent?) { if (intent != null) { val id = intent.getIntExtra("id", -1) val type = intent.getStringExtra("type") @@ -25,36 +14,10 @@ class VideoDownloadService : Service() { "resume" -> VideoDownloadManager.DownloadActionType.Resume "pause" -> VideoDownloadManager.DownloadActionType.Pause "stop" -> VideoDownloadManager.DownloadActionType.Stop - else -> return START_NOT_STICKY - } - - downloadScope.launch { - VideoDownloadManager.downloadEvent.invoke(Pair(id, state)) + else -> return } + VideoDownloadManager.downloadEvent.invoke(Pair(id, state)) } } - - return START_NOT_STICKY } - - override fun onDestroy() { - downloadScope.coroutineContext.cancel() - super.onDestroy() - } -} -// override fun onHandleIntent(intent: Intent?) { -// if (intent != null) { -// val id = intent.getIntExtra("id", -1) -// val type = intent.getStringExtra("type") -// if (id != -1 && type != null) { -// val state = when (type) { -// "resume" -> VideoDownloadManager.DownloadActionType.Resume -// "pause" -> VideoDownloadManager.DownloadActionType.Pause -// "stop" -> VideoDownloadManager.DownloadActionType.Stop -// else -> return -// } -// VideoDownloadManager.downloadEvent.invoke(Pair(id, state)) -// } -// } -// } -//} +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt index 8ce6bae2..825ff673 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt @@ -12,8 +12,6 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI { val aniListApi = AniListApi(0) val openSubtitlesApi = OpenSubtitlesApi(0) val indexSubtitlesApi = IndexSubtitleApi() - val addic7ed = Addic7ed() - val localListApi = LocalList() // used to login via app intent val OAuth2Apis @@ -30,7 +28,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI { // used for active syncing val SyncApis get() = listOf( - SyncRepo(malApi), SyncRepo(aniListApi), SyncRepo(localListApi) + SyncRepo(malApi), SyncRepo(aniListApi) ) val inAppAuths @@ -39,19 +37,11 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI { val subtitleProviders get() = listOf( openSubtitlesApi, - indexSubtitlesApi, // they got anti scraping measures in place :( - addic7ed + indexSubtitlesApi // they got anti scraping measures in place :( ) const val appString = "cloudstreamapp" const val appStringRepo = "cloudstreamrepo" - const val appStringPlayer = "cloudstreamplayer" - - // Instantly start the search given a query - const val appStringSearch = "cloudstreamsearch" - - // Instantly resume watching a show - const val appStringResumeWatching = "cloudstreamcontinuewatching" val unixTime: Long get() = System.currentTimeMillis() / 1000L diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt index 8c76c5bf..5aa56a02 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt @@ -1,31 +1,10 @@ package com.lagradost.cloudstream3.syncproviders import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.ui.library.ListSorting -import com.lagradost.cloudstream3.ui.result.UiText -import me.xdrop.fuzzywuzzy.FuzzySearch - -enum class SyncIdName { - Anilist, - MyAnimeList, - Trakt, - Imdb, - LocalList -} interface SyncAPI : OAuth2API { - /** - * Set this to true if the user updates something on the list like watch status or score - **/ - var requireLibraryRefresh: Boolean val mainUrl: String - /** - * Allows certain providers to open pages from - * library links. - **/ - val syncIdName: SyncIdName - /** -1 -> None 0 -> Watching @@ -43,9 +22,7 @@ interface SyncAPI : OAuth2API { suspend fun search(name: String): List? - suspend fun getPersonalLibrary(): LibraryMetadata? - - fun getIdFromUrl(url: String): String + fun getIdFromUrl(url : String) : String data class SyncSearchResult( override val name: String, @@ -65,7 +42,7 @@ interface SyncAPI : OAuth2API { val score: Int?, val watchedEpisodes: Int?, var isFavorite: Boolean? = null, - var maxEpisodes: Int? = null, + var maxEpisodes : Int? = null, ) data class SyncResult( @@ -86,9 +63,9 @@ interface SyncAPI : OAuth2API { var genres: List? = null, var synonyms: List? = null, var trailers: List? = null, - var isAdult: Boolean? = null, + var isAdult : Boolean? = null, var posterUrl: String? = null, - var backgroundPosterUrl: String? = null, + var backgroundPosterUrl : String? = null, /** In unixtime */ var startDate: Long? = null, @@ -99,61 +76,4 @@ interface SyncAPI : OAuth2API { var prevSeason: SyncSearchResult? = null, var actors: List? = null, ) - - - data class Page( - val title: UiText, var items: List - ) { - fun sort(method: ListSorting?, query: String? = null) { - items = when (method) { - ListSorting.Query -> - if (query != null) { - items.sortedBy { - -FuzzySearch.partialRatio( - query.lowercase(), it.name.lowercase() - ) - } - } else items - ListSorting.RatingHigh -> items.sortedBy { -(it.personalRating ?: 0) } - ListSorting.RatingLow -> items.sortedBy { (it.personalRating ?: 0) } - ListSorting.AlphabeticalA -> items.sortedBy { it.name } - ListSorting.AlphabeticalZ -> items.sortedBy { it.name }.reversed() - ListSorting.UpdatedNew -> items.sortedBy { it.lastUpdatedUnixTime?.times(-1) } - ListSorting.UpdatedOld -> items.sortedBy { it.lastUpdatedUnixTime } - else -> items - } - } - } - - data class LibraryMetadata( - val allLibraryLists: List, - val supportedListSorting: Set - ) - - data class LibraryList( - val name: UiText, - val items: List - ) - - data class LibraryItem( - override val name: String, - override val url: String, - /** - * Unique unchanging string used for data storage. - * This should be the actual id when you change scores and status - * since score changes from library might get added in the future. - **/ - val syncId: String, - val episodesCompleted: Int?, - val episodesTotal: Int?, - /** Out of 100 */ - val personalRating: Int?, - val lastUpdatedUnixTime: Long?, - override val apiName: String, - override var type: TvType?, - override var posterUrl: String?, - override var posterHeaders: Map?, - override var quality: SearchQuality?, - override var id: Int? = null, - ) : SearchResponse } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt index 85b877e0..b621e81a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt @@ -11,38 +11,26 @@ class SyncRepo(private val repo: SyncAPI) { val icon = repo.icon val mainUrl = repo.mainUrl val requiresLogin = repo.requiresLogin - val syncIdName = repo.syncIdName - var requireLibraryRefresh: Boolean - get() = repo.requireLibraryRefresh - set(value) { - repo.requireLibraryRefresh = value - } suspend fun score(id: String, status: SyncAPI.SyncStatus): Resource { return safeApiCall { repo.score(id, status) } } - suspend fun getStatus(id: String): Resource { + suspend fun getStatus(id : String) : Resource { return safeApiCall { repo.getStatus(id) ?: throw ErrorLoadingException("No data") } } - suspend fun getResult(id: String): Resource { + suspend fun getResult(id : String) : Resource { return safeApiCall { repo.getResult(id) ?: throw ErrorLoadingException("No data") } } - suspend fun search(query: String): Resource> { + suspend fun search(query : String) : Resource> { return safeApiCall { repo.search(query) ?: throw ErrorLoadingException() } } - suspend fun getPersonalLibrary(): Resource { - return safeApiCall { repo.getPersonalLibrary() ?: throw ErrorLoadingException() } - } - - fun hasAccount(): Boolean { + fun hasAccount() : Boolean { return normalSafeApiCall { repo.loginInfo() != null } ?: false } - fun getIdFromUrl(url: String): String? = normalSafeApiCall { - repo.getIdFromUrl(url) - } + fun getIdFromUrl(url : String) : String = repo.getIdFromUrl(url) } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/Addic7ed.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/Addic7ed.kt deleted file mode 100644 index 507c5e2a..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/Addic7ed.kt +++ /dev/null @@ -1,108 +0,0 @@ -package com.lagradost.cloudstream3.syncproviders.providers - -import com.lagradost.cloudstream3.TvType -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.subtitles.AbstractSubApi -import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities -import com.lagradost.cloudstream3.utils.SubtitleHelper - -class Addic7ed : AbstractSubApi { - override val name = "Addic7ed" - override val idPrefix = "addic7ed" - override val requiresLogin = false - override val icon: Nothing? = null - override val createAccountUrl: Nothing? = null - - override fun loginInfo(): Nothing? = null - - override fun logOut() {} - - companion object { - const val host = "https://www.addic7ed.com" - const val TAG = "ADDIC7ED" - } - - private fun fixUrl(url: String): String { - return if (url.startsWith("/")) host + url - else if (!url.startsWith("http")) "$host/$url" - else url - - } - - override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List { - val lang = query.lang - val queryLang = SubtitleHelper.fromTwoLettersToLanguage(lang.toString()) - val queryText = query.query.trim() - val epNum = query.epNumber ?: 0 - val seasonNum = query.seasonNumber ?: 0 - val yearNum = query.year ?: 0 - - fun cleanResources( - results: MutableList, - name: String, - link: String, - headers: Map, - isHearingImpaired: Boolean - ) { - results.add( - AbstractSubtitleEntities.SubtitleEntity( - idPrefix = idPrefix, - name = name, - lang = queryLang.toString(), - data = link, - source = this.name, - type = if (seasonNum > 0) TvType.TvSeries else TvType.Movie, - epNumber = epNum, - seasonNumber = seasonNum, - year = yearNum, - headers = headers, - isHearingImpaired = isHearingImpaired - ) - ) - } - - val title = queryText.substringBefore("(").trim() - val url = "$host/search.php?search=${title}&Submit=Search" - val hostDocument = app.get(url).document - var searchResult = "" - if (!hostDocument.select("span:contains($title)").isNullOrEmpty()) searchResult = url - else if (!hostDocument.select("table.tabel") - .isNullOrEmpty() - ) searchResult = hostDocument.select("a:contains($title)").attr("href").toString() - else { - val show = - hostDocument.selectFirst("#sl button")?.attr("onmouseup")?.substringAfter("(") - ?.substringBefore(",") - val doc = app.get( - "$host/ajax_loadShow.php?show=$show&season=$seasonNum&langs=&hd=undefined&hi=undefined", - referer = "$host/" - ).document - doc.select("#season tr:contains($queryLang)").mapNotNull { node -> - if (node.selectFirst("td")?.text() - ?.toIntOrNull() == seasonNum && node.select("td:eq(1)") - .text() - .toIntOrNull() == epNum - ) searchResult = fixUrl(node.select("a").attr("href")) - } - } - val results = mutableListOf() - val document = app.get( - url = fixUrl(searchResult), - ).document - - document.select(".tabel95 .tabel95 tr:contains($queryLang)").mapNotNull { node -> - val name = if (seasonNum > 0) "${document.select(".titulo").text().replace("Subtitle","").trim()}${ - node.parent()!!.select(".NewsTitle").text().substringAfter("Version").substringBefore(", Duration") - }" else "${document.select(".titulo").text().replace("Subtitle","").trim()}${node.parent()!!.select(".NewsTitle").text().substringAfter("Version").substringBefore(", Duration")}" - val link = fixUrl(node.select("a.buttonDownload").attr("href")) - val isHearingImpaired = - !node.parent()!!.select("tr:last-child [title=\"Hearing Impaired\"]").isNullOrEmpty() - cleanResources(results, name, link, mapOf("referer" to "$host/"), isHearingImpaired) - } - return results - } - - override suspend fun load(data: AbstractSubtitleEntities.SubtitleEntity): String { - return data.data - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index 0010ce25..3140abbc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -1,20 +1,19 @@ package com.lagradost.cloudstream3.syncproviders.providers -import androidx.annotation.StringRes import androidx.fragment.app.FragmentActivity import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.DeserializationFeature +import com.fasterxml.jackson.databind.json.JsonMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI -import com.lagradost.cloudstream3.syncproviders.SyncIdName -import com.lagradost.cloudstream3.ui.library.ListSorting -import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.splitQuery import com.lagradost.cloudstream3.utils.AppUtils.toJson @@ -22,7 +21,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject import java.net.URL -import java.net.URLEncoder import java.util.* class AniListApi(index: Int) : AccountManager(index), SyncAPI { @@ -30,12 +28,10 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { override val key = "6871" override val redirectUrl = "anilistlogin" override val idPrefix = "anilist" - override var requireLibraryRefresh = true override var mainUrl = "https://anilist.co" override val icon = R.drawable.ic_anilist_icon override val requiresLogin = false override val createAccountUrl = "$mainUrl/signup" - override val syncIdName = SyncIdName.Anilist override fun loginInfo(): AuthAPI.LoginInfo? { // context.getUser(true)?. @@ -50,7 +46,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } override fun logOut() { - requireLibraryRefresh = true removeAccountKeys() } @@ -70,8 +65,8 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { switchToNewAccount() setKey(accountId, ANILIST_UNIXTIME_KEY, endTime) setKey(accountId, ANILIST_TOKEN_KEY, token) + setKey(ANILIST_SHOULD_UPDATE_LIST, true) val user = getUser() - requireLibraryRefresh = true return user != null } @@ -146,8 +141,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { this.name, recMedia.id?.toString() ?: return@mapNotNull null, getUrlFromId(recMedia.id), - recMedia.coverImage?.extraLarge ?: recMedia.coverImage?.large - ?: recMedia.coverImage?.medium + recMedia.coverImage?.large ?: recMedia.coverImage?.medium ) }, trailers = when (season.trailer?.site?.lowercase()?.trim()) { @@ -177,9 +171,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { fromIntToAnimeStatus(status.status), status.score, status.watchedEpisodes - ).also { - requireLibraryRefresh = requireLibraryRefresh || it - } + ) } companion object { @@ -190,6 +182,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { const val ANILIST_TOKEN_KEY: String = "anilist_token" // anilist token for api const val ANILIST_USER_KEY: String = "anilist_user" // user data like profile const val ANILIST_CACHED_LIST: String = "anilist_cached_list" + const val ANILIST_SHOULD_UPDATE_LIST: String = "anilist_should_update_list" private fun fixName(name: String): String { return name.lowercase(Locale.ROOT).replace(" ", "") @@ -227,7 +220,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { romaji } idMal - coverImage { medium large extraLarge } + coverImage { medium large } averageScore } } @@ -240,7 +233,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { format id idMal - coverImage { medium large extraLarge } + coverImage { medium large } averageScore title { english @@ -300,13 +293,15 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { val shows = searchShows(name.replace(blackListRegex, "")) shows?.data?.Page?.media?.find { - (malId ?: "NONE") == it.idMal.toString() + malId ?: "NONE" == it.idMal.toString() }?.let { return it } val filtered = shows?.data?.Page?.media?.filter { - (((it.startDate.year ?: year.toString()) == year.toString() - || year == null)) + ( + it.startDate.year ?: year.toString() == year.toString() + || year == null + ) } filtered?.forEach { it.title.romaji?.let { romaji -> @@ -318,14 +313,14 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } // Changing names of these will show up in UI - enum class AniListStatusType(var value: Int, @StringRes val stringRes: Int) { - Watching(0, R.string.type_watching), - Completed(1, R.string.type_completed), - Paused(2, R.string.type_on_hold), - Dropped(3, R.string.type_dropped), - Planning(4, R.string.type_plan_to_watch), - ReWatching(5, R.string.type_re_watching), - None(-1, R.string.none) + enum class AniListStatusType(var value: Int) { + Watching(0), + Completed(1), + Paused(2), + Dropped(3), + Planning(4), + ReWatching(5), + None(-1) } fun fromIntToAnimeStatus(inp: Int): AniListStatusType {//= AniListStatusType.values().first { it.value == inp } @@ -341,7 +336,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } } - fun convertAniListStringToStatus(string: String): AniListStatusType { + fun convertAnilistStringToStatus(string: String): AniListStatusType { return fromIntToAnimeStatus(aniListStatusString.indexOf(string)) } @@ -527,27 +522,19 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } private suspend fun postApi(q: String, cache: Boolean = false): String? { - return suspendSafeApiCall { - if (!checkToken()) { - app.post( - "https://graphql.anilist.co/", - headers = mapOf( - "Authorization" to "Bearer " + (getAuth() - ?: return@suspendSafeApiCall null), - if (cache) "Cache-Control" to "max-stale=$maxStale" else "Cache-Control" to "no-cache" - ), - cacheTime = 0, - data = mapOf( - "query" to URLEncoder.encode( - q, - "UTF-8" - ) - ), //(if (vars == null) mapOf("query" to q) else mapOf("query" to q, "variables" to vars)) - timeout = 5 // REASONABLE TIMEOUT - ).text.replace("\\/", "/") - } else { - null - } + return if (!checkToken()) { + app.post( + "https://graphql.anilist.co/", + headers = mapOf( + "Authorization" to "Bearer " + (getAuth() ?: return null), + if (cache) "Cache-Control" to "max-stale=$maxStale" else "Cache-Control" to "no-cache" + ), + cacheTime = 0, + data = mapOf("query" to q),//(if (vars == null) mapOf("query" to q) else mapOf("query" to q, "variables" to vars)) + timeout = 5 // REASONABLE TIMEOUT + ).text.replace("\\/", "/") + } else { + null } } @@ -582,8 +569,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { data class CoverImage( @JsonProperty("medium") val medium: String?, - @JsonProperty("large") val large: String?, - @JsonProperty("extraLarge") val extraLarge: String? + @JsonProperty("large") val large: String? ) data class Media( @@ -610,29 +596,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { @JsonProperty("score") val score: Int, @JsonProperty("private") val private: Boolean, @JsonProperty("media") val media: Media - ) { - fun toLibraryItem(): SyncAPI.LibraryItem { - return SyncAPI.LibraryItem( - // English title first - this.media.title.english ?: this.media.title.romaji - ?: this.media.synonyms.firstOrNull() - ?: "", - "https://anilist.co/anime/${this.media.id}/", - this.media.id.toString(), - this.progress, - this.media.episodes, - this.score, - this.updatedAt.toLong(), - "AniList", - TvType.Anime, - this.media.coverImage.extraLarge ?: this.media.coverImage.large - ?: this.media.coverImage.medium, - null, - null, - null - ) - } - } + ) data class Lists( @JsonProperty("status") val status: String?, @@ -647,59 +611,40 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { @JsonProperty("MediaListCollection") val MediaListCollection: MediaListCollection ) - private fun getAniListListCached(): Array? { + fun getAnilistListCached(): Array? { return getKey(ANILIST_CACHED_LIST) as? Array } - private suspend fun getAniListAnimeListSmart(): Array? { + suspend fun getAnilistAnimeListSmart(): Array? { if (getAuth() == null) return null if (checkToken()) return null - return if (requireLibraryRefresh) { - val list = getFullAniListList()?.data?.MediaListCollection?.lists?.toTypedArray() + return if (getKey(ANILIST_SHOULD_UPDATE_LIST, true) == true) { + val list = getFullAnilistList()?.data?.MediaListCollection?.lists?.toTypedArray() if (list != null) { setKey(ANILIST_CACHED_LIST, list) + setKey(ANILIST_SHOULD_UPDATE_LIST, false) } list } else { - getAniListListCached() + getAnilistListCached() } } - override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata { - val list = getAniListAnimeListSmart()?.groupBy { - convertAniListStringToStatus(it.status ?: "").stringRes - }?.mapValues { group -> - group.value.map { it.entries.map { entry -> entry.toLibraryItem() } }.flatten() - } ?: emptyMap() - - // To fill empty lists when AniList does not return them - val baseMap = - AniListStatusType.values().filter { it.value >= 0 }.associate { - it.stringRes to emptyList() - } - - return SyncAPI.LibraryMetadata( - (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }, - setOf( - ListSorting.AlphabeticalA, - ListSorting.AlphabeticalZ, - ListSorting.UpdatedNew, - ListSorting.UpdatedOld, - ListSorting.RatingHigh, - ListSorting.RatingLow, - ) - ) - } - - private suspend fun getFullAniListList(): FullAnilistList? { + private suspend fun getFullAnilistList(): FullAnilistList? { + var userID: Int? = null /** WARNING ASSUMES ONE USER! **/ + getKeys(ANILIST_USER_KEY)?.forEach { key -> + getKey(key, null)?.let { + userID = it.id + } + } - val userID = getKey(accountId, ANILIST_USER_KEY)?.id ?: return null + val fixedUserID = userID ?: return null val mediaType = "ANIME" val query = """ - query (${'$'}userID: Int = $userID, ${'$'}MEDIA: MediaType = $mediaType) { + query (${'$'}userID: Int = $fixedUserID, ${'$'}MEDIA: MediaType = $mediaType) { MediaListCollection (userId: ${'$'}userID, type: ${'$'}MEDIA) { lists { status @@ -710,7 +655,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { startedAt { year month day } updatedAt progress - score (format: POINT_100) + score private media { @@ -726,7 +671,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { english romaji } - coverImage { extraLarge large medium } + coverImage { medium } synonyms nextAiringEpisode { timeUntilAiring @@ -759,11 +704,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { return data != "" } - /** Used to query a saved MediaItem on the list to get the id for removal */ - data class MediaListItemRoot(@JsonProperty("data") val data: MediaListItem? = null) - data class MediaListItem(@JsonProperty("MediaList") val MediaList: MediaListId? = null) - data class MediaListId(@JsonProperty("id") val id: Long? = null) - private suspend fun postDataAboutId( id: Int, type: AniListStatusType, @@ -771,43 +711,19 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { progress: Int? ): Boolean { val q = - // Delete item if status type is None - if (type == AniListStatusType.None) { - val userID = getKey(accountId, ANILIST_USER_KEY)?.id ?: return false - // Get list ID for deletion - val idQuery = """ - query MediaList(${'$'}userId: Int = $userID, ${'$'}mediaId: Int = $id) { - MediaList(userId: ${'$'}userId, mediaId: ${'$'}mediaId) { - id - } - } - """ - val response = postApi(idQuery) - val listId = - tryParseJson(response)?.data?.MediaList?.id ?: return false - """ - mutation(${'$'}id: Int = $listId) { - DeleteMediaListEntry(id: ${'$'}id) { - deleted - } - } - """ - } else { - """mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${ - aniListStatusString[maxOf( - 0, - type.value - )] - }, ${if (score != null) "${'$'}scoreRaw: Int = ${score * 10}" else ""} , ${if (progress != null) "${'$'}progress: Int = $progress" else ""}) { - SaveMediaListEntry (mediaId: ${'$'}id, status: ${'$'}status, scoreRaw: ${'$'}scoreRaw, progress: ${'$'}progress) { - id - status - progress - score - } + """mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${ + aniListStatusString[maxOf( + 0, + type.value + )] + }, ${if (score != null) "${'$'}scoreRaw: Int = ${score * 10}" else ""} , ${if (progress != null) "${'$'}progress: Int = $progress" else ""}) { + SaveMediaListEntry (mediaId: ${'$'}id, status: ${'$'}status, scoreRaw: ${'$'}scoreRaw, progress: ${'$'}progress) { + id + status + progress + score + } }""" - } - val data = postApi(q) return data != "" } diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt index 668d10bd..2fc97477 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt @@ -4,6 +4,7 @@ import android.util.Log import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.imdbUrlToIdNullable +import com.lagradost.cloudstream3.network.CloudflareKiller import com.lagradost.cloudstream3.subtitles.AbstractSubApi import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities import com.lagradost.cloudstream3.utils.SubtitleHelper @@ -21,7 +22,7 @@ class IndexSubtitleApi : AbstractSubApi { companion object { - const val host = "https://indexsubtitle.com" + const val host = "https://subscene.cyou" const val TAG = "INDEXSUBS" } @@ -241,7 +242,7 @@ class IndexSubtitleApi : AbstractSubApi { document.selectFirst("div.my-3.p-3 div.media a")!!.attr("href") ) } else { - document.select("div.my-3.p-3 div.media").firstNotNullOf { block -> + document.select("div.my-3.p-3 div.media").mapNotNull { block -> val name = block.selectFirst("strong.d-block")?.text()?.trim().toString() if (seasonNum!! > 0) { @@ -253,7 +254,7 @@ class IndexSubtitleApi : AbstractSubApi { } else { fixUrl(block.selectFirst("a")!!.attr("href")) } - } + }.first() } return link } diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt deleted file mode 100644 index 7dd43fe7..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.lagradost.cloudstream3.syncproviders.providers - -import androidx.fragment.app.FragmentActivity -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.syncproviders.AuthAPI -import com.lagradost.cloudstream3.syncproviders.SyncAPI -import com.lagradost.cloudstream3.syncproviders.SyncIdName -import com.lagradost.cloudstream3.ui.WatchType -import com.lagradost.cloudstream3.ui.library.ListSorting -import com.lagradost.cloudstream3.ui.result.txt -import com.lagradost.cloudstream3.utils.Coroutines.ioWork -import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions -import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds -import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData -import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState - -class LocalList : SyncAPI { - override val name = "Local" - override val icon: Int = R.drawable.ic_baseline_storage_24 - override val requiresLogin = false - override val createAccountUrl: Nothing? = null - override val idPrefix = "local" - override var requireLibraryRefresh = true - - override fun loginInfo(): AuthAPI.LoginInfo { - return AuthAPI.LoginInfo( - null, - null, - 0 - ) - } - - override fun logOut() { - - } - - override val key: String = "" - override val redirectUrl = "" - override suspend fun handleRedirect(url: String): Boolean { - return true - } - - override fun authenticate(activity: FragmentActivity?) { - } - - override val mainUrl = "" - override val syncIdName = SyncIdName.LocalList - override suspend fun score(id: String, status: SyncAPI.SyncStatus): Boolean { - return true - } - - override suspend fun getStatus(id: String): SyncAPI.SyncStatus? { - return null - } - - override suspend fun getResult(id: String): SyncAPI.SyncResult? { - return null - } - - override suspend fun search(name: String): List? { - return null - } - - override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata? { - val watchStatusIds = ioWork { - getAllWatchStateIds()?.map { id -> - Pair(id, getResultWatchState(id)) - } - }?.distinctBy { it.first } ?: return null - - val list = ioWork { - watchStatusIds.groupBy { - it.second.stringRes - }.mapValues { group -> - group.value.mapNotNull { - getBookmarkedData(it.first)?.toLibraryItem(it.first.toString()) - } - } + mapOf(R.string.subscription_list_name to getAllSubscriptions().mapNotNull { - it.toLibraryItem() - }) - } - - val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate { - // None is not something to display - it.stringRes to emptyList() - } + mapOf(R.string.subscription_list_name to emptyList()) - - return SyncAPI.LibraryMetadata( - (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }, - setOf( - ListSorting.AlphabeticalA, - ListSorting.AlphabeticalZ, -// ListSorting.UpdatedNew, -// ListSorting.UpdatedOld, -// ListSorting.RatingHigh, -// ListSorting.RatingLow, - ) - ) - } - - override fun getIdFromUrl(url: String): String { - return url - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt index 5164b606..c08958ce 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt @@ -1,7 +1,6 @@ package com.lagradost.cloudstream3.syncproviders.providers import android.util.Base64 -import androidx.annotation.StringRes import androidx.fragment.app.FragmentActivity import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.AcraApplication.Companion.getKey @@ -9,15 +8,11 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.ShowStatus -import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI -import com.lagradost.cloudstream3.syncproviders.SyncIdName -import com.lagradost.cloudstream3.ui.library.ListSorting -import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.splitQuery import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject @@ -36,15 +31,13 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { override val redirectUrl = "mallogin" override val idPrefix = "mal" override var mainUrl = "https://myanimelist.net" - private val apiUrl = "https://api.myanimelist.net" + val apiUrl = "https://api.myanimelist.net" override val icon = R.drawable.mal_logo override val requiresLogin = false - override val syncIdName = SyncIdName.MyAnimeList - override var requireLibraryRefresh = true + override val createAccountUrl = "$mainUrl/register.php" override fun logOut() { - requireLibraryRefresh = true removeAccountKeys() } @@ -97,9 +90,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { fromIntToAnimeStatus(status.status), status.score, status.watchedEpisodes - ).also { - requireLibraryRefresh = requireLibraryRefresh || it - } + ) } data class MalAnime( @@ -257,45 +248,10 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { const val MAL_USER_KEY: String = "mal_user" // user data like profile const val MAL_CACHED_LIST: String = "mal_cached_list" + const val MAL_SHOULD_UPDATE_LIST: String = "mal_should_update_list" const val MAL_UNIXTIME_KEY: String = "mal_unixtime" // When token expires const val MAL_REFRESH_TOKEN_KEY: String = "mal_refresh_token" // refresh token const val MAL_TOKEN_KEY: String = "mal_token" // anilist token for api - - fun convertToStatus(string: String): MalStatusType { - return fromIntToAnimeStatus(malStatusAsString.indexOf(string)) - } - - enum class MalStatusType(var value: Int, @StringRes val stringRes: Int) { - Watching(0, R.string.type_watching), - Completed(1, R.string.type_completed), - OnHold(2, R.string.type_on_hold), - Dropped(3, R.string.type_dropped), - PlanToWatch(4, R.string.type_plan_to_watch), - None(-1, R.string.type_none) - } - - private fun fromIntToAnimeStatus(inp: Int): MalStatusType {//= AniListStatusType.values().first { it.value == inp } - return when (inp) { - -1 -> MalStatusType.None - 0 -> MalStatusType.Watching - 1 -> MalStatusType.Completed - 2 -> MalStatusType.OnHold - 3 -> MalStatusType.Dropped - 4 -> MalStatusType.PlanToWatch - 5 -> MalStatusType.Watching - else -> MalStatusType.None - } - } - - private fun parseDateLong(string: String?): Long? { - return try { - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse( - string ?: return null - )?.time?.div(1000) - } catch (e: Exception) { - null - } - } } override suspend fun handleRedirect(url: String): Boolean { @@ -319,7 +275,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { switchToNewAccount() storeToken(res) val user = getMalUser() - requireLibraryRefresh = true + setKey(MAL_SHOULD_UPDATE_LIST, true) return user != null } } @@ -352,10 +308,9 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { setKey(accountId, MAL_UNIXTIME_KEY, (token.expires_in + unixTime)) setKey(accountId, MAL_REFRESH_TOKEN_KEY, token.refresh_token) setKey(accountId, MAL_TOKEN_KEY, token.access_token) - requireLibraryRefresh = true } } catch (e: Exception) { - logError(e) + e.printStackTrace() } } @@ -374,7 +329,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { ).text storeToken(res) } catch (e: Exception) { - logError(e) + e.printStackTrace() } } @@ -427,24 +382,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { data class Data( @JsonProperty("node") val node: Node, @JsonProperty("list_status") val list_status: ListStatus?, - ) { - fun toLibraryItem(): SyncAPI.LibraryItem { - return SyncAPI.LibraryItem( - this.node.title, - "https://myanimelist.net/anime/${this.node.id}/", - this.node.id.toString(), - this.list_status?.num_episodes_watched, - this.node.num_episodes, - this.list_status?.score?.times(10), - parseDateLong(this.list_status?.updated_at), - "MAL", - TvType.Anime, - this.node.main_picture?.large ?: this.node.main_picture?.medium, - null, - null, - ) - } - } + ) data class Paging( @JsonProperty("next") val next: String? @@ -475,43 +413,18 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { return getKey(MAL_CACHED_LIST) as? Array } - private suspend fun getMalAnimeListSmart(): Array? { + suspend fun getMalAnimeListSmart(): Array? { if (getAuth() == null) return null - return if (requireLibraryRefresh) { + return if (getKey(MAL_SHOULD_UPDATE_LIST, true) == true) { val list = getMalAnimeList() setKey(MAL_CACHED_LIST, list) + setKey(MAL_SHOULD_UPDATE_LIST, false) list } else { getMalAnimeListCached() } } - override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata { - val list = getMalAnimeListSmart()?.groupBy { - convertToStatus(it.list_status?.status ?: "").stringRes - }?.mapValues { group -> - group.value.map { it.toLibraryItem() } - } ?: emptyMap() - - // To fill empty lists when MAL does not return them - val baseMap = - MalStatusType.values().filter { it.value >= 0 }.associate { - it.stringRes to emptyList() - } - - return SyncAPI.LibraryMetadata( - (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) }, - setOf( - ListSorting.AlphabeticalA, - ListSorting.AlphabeticalZ, - ListSorting.UpdatedNew, - ListSorting.UpdatedOld, - ListSorting.RatingHigh, - ListSorting.RatingLow, - ) - ) - } - private suspend fun getMalAnimeList(): Array { checkMalToken() var offset = 0 @@ -527,6 +440,10 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { return fullList.toTypedArray() } + fun convertToStatus(string: String): MalStatusType { + return fromIntToAnimeStatus(malStatusAsString.indexOf(string)) + } + private suspend fun getMalAnimeListSlice(offset: Int = 0): MalList? { val user = "@me" val auth = getAuth() ?: return null @@ -640,6 +557,28 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI { return user } + enum class MalStatusType(var value: Int) { + Watching(0), + Completed(1), + OnHold(2), + Dropped(3), + PlanToWatch(4), + None(-1) + } + + private fun fromIntToAnimeStatus(inp: Int): MalStatusType {//= AniListStatusType.values().first { it.value == inp } + return when (inp) { + -1 -> MalStatusType.None + 0 -> MalStatusType.Watching + 1 -> MalStatusType.Completed + 2 -> MalStatusType.OnHold + 3 -> MalStatusType.Dropped + 4 -> MalStatusType.PlanToWatch + 5 -> MalStatusType.Watching + else -> MalStatusType.None + } + } + private suspend fun setScoreRequest( id: Int, status: MalStatusType? = null, diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt index 3e372c2d..bfa65f62 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt @@ -15,8 +15,6 @@ import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI import com.lagradost.cloudstream3.syncproviders.InAppAuthAPIManager import com.lagradost.cloudstream3.utils.AppUtils -import java.net.URLEncoder -import java.nio.charset.StandardCharsets class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi { override val idPrefix = "opensubtitles" @@ -166,7 +164,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi val fixedLang = fixLanguage(query.lang) val imdbId = query.imdb ?: 0 - val queryText = query.query + val queryText = query.query.replace(" ", "+") val epNum = query.epNumber ?: 0 val seasonNum = query.seasonNumber ?: 0 val yearNum = query.year ?: 0 @@ -177,7 +175,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi val searchQueryUrl = when (imdbId > 0) { //Use imdb_id to search if its valid true -> "$host/subtitles?imdb_id=$imdbId&languages=${fixedLang}$yearQuery$epQuery$seasonQuery" - false -> "$host/subtitles?query=${queryText}&languages=${fixedLang}$yearQuery$epQuery$seasonQuery" + false -> "$host/subtitles?query=$queryText&languages=${fixedLang}$yearQuery$epQuery$seasonQuery" } val req = app.get( @@ -200,13 +198,9 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi it.data?.forEach { item -> val attr = item.attributes ?: return@forEach val featureDetails = attr.featDetails - //Use filename as name, if its valid - val filename = attr.files?.firstNotNullOfOrNull { subfile -> - subfile.fileName - } //Use any valid name/title in hierarchy - val name = filename ?: featureDetails?.movieName ?: featureDetails?.title - ?: featureDetails?.parentTitle ?: attr.release ?: query.query + val name = featureDetails?.movieName ?: featureDetails?.title + ?: featureDetails?.parentTitle ?: attr.release ?: "" val lang = fixLanguageReverse(attr.language)?: "" val resEpNum = featureDetails?.episodeNumber ?: query.epNumber val resSeasonNum = featureDetails?.seasonNumber ?: query.seasonNumber @@ -334,4 +328,4 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi @JsonProperty("parent_tmdb_id") var parentTmdbId: Int? = null, @JsonProperty("parent_feature_id") var parentFeatureId: Int? = null ) -} +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt index 4ab2e8e2..ef50019c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt @@ -1,13 +1,10 @@ package com.lagradost.cloudstream3.ui import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.APIHolder.unixTimeMS -import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.safeApiCall -import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf import com.lagradost.cloudstream3.utils.ExtractorLink import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi @@ -34,65 +31,26 @@ class APIRepository(val api: MainAPI) { return data.isEmpty() || data == "[]" || data == "about:blank" } - data class SavedLoadResponse( - val unixTime: Long, - val response: LoadResponse, - val hash: Pair - ) - - private val cache = threadSafeListOf() - private var cacheIndex: Int = 0 - const val cacheSize = 20 - } - - private fun afterPluginsLoaded(forceReload: Boolean) { - if (forceReload) { - synchronized(cache) { - cache.clear() - } - } - } - - init { - afterPluginsLoadedEvent += ::afterPluginsLoaded + private val cacheHash: HashMap, LoadResponse> = hashMapOf() } val hasMainPage = api.hasMainPage - val providerType = api.providerType val name = api.name val mainUrl = api.mainUrl val mainPage = api.mainPage val hasQuickSearch = api.hasQuickSearch val vpnStatus = api.vpnStatus + val providerType = api.providerType suspend fun load(url: String): Resource { return safeApiCall { if (isInvalidData(url)) throw ErrorLoadingException() val fixedUrl = api.fixUrl(url) - val lookingForHash = Pair(api.name, fixedUrl) - - synchronized(cache) { - for (item in cache) { - // 10 min save - if (item.hash == lookingForHash && (unixTime - item.unixTime) < 60 * 10) { - return@safeApiCall item.response - } - } - } - - api.load(fixedUrl)?.also { response -> - // Remove all blank tags as early as possible - response.tags = response.tags?.filter { it.isNotBlank() } - val add = SavedLoadResponse(unixTime, response, lookingForHash) - - synchronized(cache) { - if (cache.size > cacheSize) { - cache[cacheIndex] = add // rolling cache - cacheIndex = (cacheIndex + 1) % cacheSize - } else { - cache.add(add) - } - } + val key = Pair(api.name, url) + cacheHash[key] ?: api.load(fixedUrl)?.also { + // we cache 20 responses because ppl often go back to the same shit + 20 because I dont want to cause too much memory leak + if (cacheHash.size > 20) cacheHash.remove(cacheHash.keys.random()) + cacheHash[key] = it } ?: throw ErrorLoadingException() } } @@ -124,6 +82,7 @@ class APIRepository(val api: MainAPI) { delay(delta) } + @OptIn(DelicateCoroutinesApi::class) suspend fun getMainPage(page: Int, nameIndex: Int? = null): Resource> { return safeApiCall { api.lastHomepageRequest = unixTimeMS diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/AutofitRecyclerView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/AutofitRecyclerView.kt index b4c07792..138084fc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/AutofitRecyclerView.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/AutofitRecyclerView.kt @@ -7,8 +7,7 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlin.math.abs -class GrdLayoutManager(val context: Context, _spanCount: Int) : - GridLayoutManager(context, _spanCount) { +class GrdLayoutManager(val context: Context, _spanCount: Int) : GridLayoutManager(context, _spanCount) { override fun onFocusSearchFailed( focused: View, focusDirection: Int, @@ -35,7 +34,7 @@ class GrdLayoutManager(val context: Context, _spanCount: Int) : val pos = maxOf(0, getPosition(focused!!) - 2) parent.scrollToPosition(pos) super.onRequestChildFocus(parent, state, child, focused) - } catch (e: Exception) { + } catch (e: Exception){ false } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/EasterEggMonke.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/EasterEggMonke.kt index 556ebd34..3d28ca94 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/EasterEggMonke.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/EasterEggMonke.kt @@ -69,7 +69,7 @@ class EasterEggMonke : AppCompatActivity() { set.duration = (Math.random() * 1500 + 2500).toLong() set.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { + override fun onAnimationEnd(animation: Animator?) { frame.removeView(newStar) } }) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt deleted file mode 100644 index 40c03012..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.lagradost.cloudstream3.ui - -import android.graphics.Canvas -import android.graphics.Rect -import android.view.View -import androidx.recyclerview.widget.RecyclerView - -class HeaderViewDecoration(private val customView: View) : RecyclerView.ItemDecoration() { - override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { - super.onDraw(c, parent, state) - customView.layout(parent.left, 0, parent.right, customView.measuredHeight) - for (i in 0 until parent.childCount) { - val view = parent.getChildAt(i) - if (parent.getChildAdapterPosition(view) == 0) { - c.save() - val height = customView.measuredHeight - val top = view.top - height - c.translate(0f, top.toFloat()) - customView.draw(c) - c.restore() - break - } - } - } - - override fun getItemOffsets( - outRect: Rect, - view: View, - parent: RecyclerView, - state: RecyclerView.State - ) { - if (parent.getChildAdapterPosition(view) == 0) { - customView.measure( - View.MeasureSpec.makeMeasureSpec(parent.measuredWidth, View.MeasureSpec.AT_MOST), - View.MeasureSpec.makeMeasureSpec(parent.measuredHeight, View.MeasureSpec.AT_MOST) - ) - outRect.set(0, customView.measuredHeight, 0, 0) - } else { - outRect.setEmpty() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt index add36f1a..0069be3a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt @@ -11,7 +11,6 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.player.DownloadFileGenerator import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.utils.AppUtils.getNameFull -import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE import com.lagradost.cloudstream3.utils.ExtractorUri import com.lagradost.cloudstream3.utils.UIHelper.navigate @@ -50,7 +49,7 @@ object DownloadButtonSetup { ) .setPositiveButton(R.string.delete, dialogClickListener) .setNegativeButton(R.string.cancel, dialogClickListener) - .show().setDefaultFocus() + .show() } catch (e: Exception) { logError(e) // ye you somehow fucked up formatting did you? diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt index e80a8fa5..b2286c99 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt @@ -24,6 +24,7 @@ import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.LinkGenerator +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE @@ -39,8 +40,6 @@ import kotlinx.android.synthetic.main.stream_input.* import android.text.format.Formatter.formatShortFileSize import androidx.core.widget.doOnTextChanged import com.lagradost.cloudstream3.mvvm.normalSafeApiCall -import com.lagradost.cloudstream3.ui.player.BasicLink -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import java.net.URI @@ -179,9 +178,7 @@ class DownloadFragment : Fragment() { download_list?.adapter = adapter download_list?.layoutManager = GridLayoutManager(context, 1) - - // Should be visible in emulator layout - download_stream_button?.isGone = isTrueTvSettings() + download_stream_button?.isGone = isTvSettings() download_stream_button?.setOnClickListener { val dialog = Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom) @@ -225,7 +222,7 @@ class DownloadFragment : Fragment() { R.id.global_to_navigation_player, GeneratorPlayer.newInstance( LinkGenerator( - listOf(BasicLink(url)), + listOf(url), extract = true, referer = referer, isM3u8 = dialog.hls_switch?.isChecked 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 5cf6fc8e..ac6daa53 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 @@ -7,20 +7,25 @@ import android.content.DialogInterface import android.content.Intent import android.content.res.Configuration import android.net.Uri -import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.* import androidx.appcompat.app.AlertDialog +import androidx.appcompat.widget.SearchView +import androidx.core.content.ContextCompat.getDrawable import androidx.core.view.isGone import androidx.core.view.isVisible +import androidx.core.widget.NestedScrollView import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.* import androidx.preference.PreferenceManager +import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.transition.ChangeBounds +import androidx.transition.TransitionManager +import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.button.MaterialButton @@ -29,55 +34,80 @@ import com.google.android.material.chip.ChipGroup import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia +import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings +import com.lagradost.cloudstream3.APIHolder.getId import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent -import com.lagradost.cloudstream3.MainActivity.Companion.bookmarksUpdatedEvent import com.lagradost.cloudstream3.MainActivity.Companion.mainPluginsLoadedEvent import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi import com.lagradost.cloudstream3.ui.AutofitRecyclerView import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment +import com.lagradost.cloudstream3.ui.result.ResultViewModel2.Companion.updateWatchStatus +import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST +import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.search.* import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings -import com.lagradost.cloudstream3.utils.AppUtils.addProgramsToContinueWatching import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable import com.lagradost.cloudstream3.utils.AppUtils.loadResult import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult -import com.lagradost.cloudstream3.utils.AppUtils.ownHide -import com.lagradost.cloudstream3.utils.AppUtils.ownShow -import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus +import com.lagradost.cloudstream3.utils.AppUtils.setMaxViewPoolSize import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.DataStoreHelper.deleteAllBookmarkedData +import com.lagradost.cloudstream3.utils.DataStoreHelper.deleteAllResumeStateIds +import com.lagradost.cloudstream3.utils.DataStoreHelper.removeLastWatched +import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState import com.lagradost.cloudstream3.utils.Event +import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog +import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar +import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView +import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount +import com.lagradost.cloudstream3.utils.UIHelper.getStatusBarHeight import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes +import com.lagradost.cloudstream3.utils.UIHelper.setImage +import com.lagradost.cloudstream3.utils.UIHelper.setImageBlur import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API -import kotlinx.android.synthetic.main.activity_main_tv.* +import com.lagradost.cloudstream3.widget.CenterZoomLayoutManager import kotlinx.android.synthetic.main.fragment_home.* import kotlinx.android.synthetic.main.fragment_home.home_api_fab +import kotlinx.android.synthetic.main.fragment_home.home_bookmarked_child_recyclerview +import kotlinx.android.synthetic.main.fragment_home.home_bookmarked_holder import kotlinx.android.synthetic.main.fragment_home.home_change_api_loading +import kotlinx.android.synthetic.main.fragment_home.home_loaded import kotlinx.android.synthetic.main.fragment_home.home_loading import kotlinx.android.synthetic.main.fragment_home.home_loading_error import kotlinx.android.synthetic.main.fragment_home.home_loading_shimmer import kotlinx.android.synthetic.main.fragment_home.home_loading_statusbar import kotlinx.android.synthetic.main.fragment_home.home_master_recycler +import kotlinx.android.synthetic.main.fragment_home.home_plan_to_watch_btt +import kotlinx.android.synthetic.main.fragment_home.home_provider_meta_info +import kotlinx.android.synthetic.main.fragment_home.home_provider_name import kotlinx.android.synthetic.main.fragment_home.home_reload_connection_open_in_browser import kotlinx.android.synthetic.main.fragment_home.home_reload_connectionerror +import kotlinx.android.synthetic.main.fragment_home.home_type_completed_btt +import kotlinx.android.synthetic.main.fragment_home.home_type_dropped_btt +import kotlinx.android.synthetic.main.fragment_home.home_type_on_hold_btt +import kotlinx.android.synthetic.main.fragment_home.home_type_watching_btt +import kotlinx.android.synthetic.main.fragment_home.home_watch_child_recyclerview +import kotlinx.android.synthetic.main.fragment_home.home_watch_holder +import kotlinx.android.synthetic.main.fragment_home.home_watch_parent_item_title import kotlinx.android.synthetic.main.fragment_home.result_error_text import kotlinx.android.synthetic.main.fragment_home_tv.* -import kotlinx.android.synthetic.main.fragment_result.* import kotlinx.android.synthetic.main.fragment_search.* import kotlinx.android.synthetic.main.home_episodes_expanded.* import kotlinx.android.synthetic.main.tvtypes_chips.* @@ -109,32 +139,26 @@ class HomeFragment : Fragment() { val errorProfilePic = errorProfilePics.random() - //fun Activity.loadHomepageList( - // item: HomePageList, - // deleteCallback: (() -> Unit)? = null, - //) { - // loadHomepageList( - // expand = HomeViewModel.ExpandableHomepageList(item, 1, false), - // deleteCallback = deleteCallback, - // expandCallback = null - // ) - //} + fun Activity.loadHomepageList( + item: HomePageList, + deleteCallback: (() -> Unit)? = null, + ) { + loadHomepageList( + expand = HomeViewModel.ExpandableHomepageList(item, 1, false), + deleteCallback = deleteCallback, + expandCallback = null + ) + } - // returns a BottomSheetDialog that will be hidden with OwnHidden upon hide, and must be saved to be able call ownShow in onCreateView fun Activity.loadHomepageList( expand: HomeViewModel.ExpandableHomepageList, deleteCallback: (() -> Unit)? = null, - expandCallback: (suspend (String) -> HomeViewModel.ExpandableHomepageList?)? = null, - dismissCallback : (() -> Unit), - ): BottomSheetDialog { + expandCallback: (suspend (String) -> HomeViewModel.ExpandableHomepageList?)? = null + ) { val context = this val bottomSheetDialogBuilder = BottomSheetDialog(context) - bottomSheetDialogBuilder.setContentView(R.layout.home_episodes_expanded) val title = bottomSheetDialogBuilder.findViewById(R.id.home_expanded_text)!! - - //title.findViewTreeLifecycleOwner().lifecycle.addObserver() - val item = expand.list title.text = item.name val recycle = @@ -142,23 +166,6 @@ class HomeFragment : Fragment() { val titleHolder = bottomSheetDialogBuilder.findViewById(R.id.home_expanded_drag_down)!! - // main { - //(bottomSheetDialogBuilder.ownerActivity as androidx.fragment.app.FragmentActivity?)?.supportFragmentManager?.fragments?.lastOrNull()?.viewLifecycleOwner?.apply { - // println("GOT LIFE: lifecycle $this") - // this.lifecycle.addObserver(object : DefaultLifecycleObserver { - // override fun onResume(owner: LifecycleOwner) { - // super.onResume(owner) - // println("onResume!!!!") - // bottomSheetDialogBuilder?.ownShow() - // } - - // override fun onStop(owner: LifecycleOwner) { - // super.onStop(owner) - // bottomSheetDialogBuilder?.ownHide() - // } - // }) - //} - // } val delete = bottomSheetDialogBuilder.home_expanded_delete delete.isGone = deleteCallback == null if (deleteCallback != null) { @@ -184,7 +191,7 @@ class HomeFragment : Fragment() { ) .setPositiveButton(R.string.delete, dialogClickListener) .setNegativeButton(R.string.cancel, dialogClickListener) - .show().setDefaultFocus() + .show() } catch (e: Exception) { logError(e) // ye you somehow fucked up formatting did you? @@ -203,8 +210,7 @@ class HomeFragment : Fragment() { recycle.adapter = SearchAdapter(item.list.toMutableList(), recycle) { callback -> handleSearchClickCallback(this, callback) if (callback.action == SEARCH_ACTION_LOAD || callback.action == SEARCH_ACTION_PLAY_FILE) { - bottomSheetDialogBuilder.ownHide() // we hide here because we want to resume it later - //bottomSheetDialogBuilder.dismissSafe(this) + bottomSheetDialogBuilder.dismissSafe(this) } }.apply { hasNext = expand.hasNext @@ -245,14 +251,12 @@ class HomeFragment : Fragment() { configEvent += spanListener bottomSheetDialogBuilder.setOnDismissListener { - dismissCallback.invoke() configEvent -= spanListener } //(recycle.adapter as SearchAdapter).notifyDataSetChanged() bottomSheetDialogBuilder.show() - return bottomSheetDialogBuilder } fun getPairList( @@ -430,15 +434,13 @@ class HomeFragment : Fragment() { ): View? { //homeViewModel = // ViewModelProvider(this).get(HomeViewModel::class.java) - bottomSheetDialog?.ownShow() val layout = if (isTvSettings()) R.layout.fragment_home_tv else R.layout.fragment_home return inflater.inflate(layout, container, false) } - override fun onDestroyView() { - bottomSheetDialog?.ownHide() - super.onDestroyView() + private fun toggleMainVisibility(visible: Boolean) { + home_main_poster_recyclerview?.isVisible = visible } private fun fixGrid() { @@ -463,26 +465,19 @@ class HomeFragment : Fragment() { override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - //(home_preview_viewpager?.adapter as? HomeScrollAdapter)?.notifyDataSetChanged() fixGrid() } - fun bookmarksUpdated(_data : Boolean) { - reloadStored() - } - override fun onResume() { super.onResume() reloadStored() - bookmarksUpdatedEvent += ::bookmarksUpdated - afterPluginsLoadedEvent += ::afterPluginsLoaded - mainPluginsLoadedEvent += ::afterMainPluginsLoaded + afterPluginsLoadedEvent += ::firstLoadHomePage + mainPluginsLoadedEvent += ::firstLoadHomePage } override fun onStop() { - bookmarksUpdatedEvent -= ::bookmarksUpdated - afterPluginsLoadedEvent -= ::afterPluginsLoaded - mainPluginsLoadedEvent -= ::afterMainPluginsLoaded + afterPluginsLoadedEvent -= ::firstLoadHomePage + mainPluginsLoadedEvent -= ::firstLoadHomePage super.onStop() } @@ -495,26 +490,34 @@ class HomeFragment : Fragment() { homeViewModel.loadStoredData(list) } - private fun afterMainPluginsLoaded(unused: Boolean = false) { + private fun firstLoadHomePage(successful: Boolean = false) { + // dirty hack to make it only load once loadHomePage(false) } - private fun afterPluginsLoaded(forceReload: Boolean) { - loadHomePage(forceReload) - } - - private fun loadHomePage(forceReload: Boolean) { + private fun loadHomePage(forceReload: Boolean = true) { val apiName = context?.getKey(USER_SELECTED_HOMEPAGE_API) - if (homeViewModel.apiName.value != apiName || apiName == null || forceReload) { + if (homeViewModel.apiName.value != apiName || apiName == null) { //println("Caught home: " + homeViewModel.apiName.value + " at " + apiName) homeViewModel.loadAndCancel(apiName, forceReload) } } + /*private fun handleBack(poppedFragment: Boolean) { + if (poppedFragment) { + reloadStored() + } + }*/ + + private fun focusCallback(card: SearchResponse) { + home_focus_text?.text = card.name + home_blur_poster?.setImageBlur(card.posterUrl, 50) + } + private fun homeHandleSearch(callback: SearchClickCallback) { if (callback.action == SEARCH_ACTION_FOCUSED) { - //focusCallback(callback.card) + focusCallback(callback.card) } else { handleSearchClickCallback(activity, callback) } @@ -523,13 +526,12 @@ class HomeFragment : Fragment() { private var currentApiName: String? = null private var toggleRandomButton = false - private var bottomSheetDialog: BottomSheetDialog? = null - @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) fixGrid() + home_change_api?.setOnClickListener(apiChangeClickListener) home_change_api_loading?.setOnClickListener(apiChangeClickListener) home_api_fab?.setOnClickListener(apiChangeClickListener) home_random?.setOnClickListener { @@ -547,19 +549,211 @@ class HomeFragment : Fragment() { } observe(homeViewModel.preview) { preview -> - (home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setPreviewData( - preview - ) + // Always reset the padding, otherwise the will move lower and lower + // home_fix_padding?.setPadding(0, 0, 0, 0) + home_fix_padding?.let { v -> + val params = v.layoutParams + params.height = 0 + v.layoutParams = params + } + + when (preview) { + is Resource.Success -> { + home_preview?.isVisible = true + (home_preview_viewpager?.adapter as? HomeScrollAdapter)?.apply { + if (!setItems(preview.value.second, preview.value.first)) { + home_preview_viewpager?.setCurrentItem(0, false) + } + // home_preview_viewpager?.setCurrentItem(1000, false) + } + + //.also { + //home_preview_viewpager?.adapter = + //} + } + else -> { + (home_preview_viewpager?.adapter as? HomeScrollAdapter)?.setItems( + listOf(), + false + ) + home_preview?.isVisible = false + context?.fixPaddingStatusbarView(home_fix_padding) + } + } + } + + val searchText = + home_search?.findViewById(androidx.appcompat.R.id.search_src_text) + searchText?.context?.getResourceColor(R.attr.white)?.let { color -> + searchText.setTextColor(color) + searchText.setHintTextColor(color) + } + + home_preview_viewpager?.apply { + setPageTransformer(HomeScrollTransformer()) + val callback: OnPageChangeCallback = object : OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + + // home_search?.isIconified = true + //home_search?.isVisible = true + //home_search?.clearFocus() + + (home_preview_viewpager?.adapter as? HomeScrollAdapter)?.apply { + if (position >= itemCount - 1 && hasMoreItems) { + hasMoreItems = false // dont make two requests + homeViewModel.loadMoreHomeScrollResponses() + } + + getItem(position) + ?.apply { + home_preview_title_holder?.let { parent -> + TransitionManager.beginDelayedTransition(parent, ChangeBounds()) + } + + // home_preview_tags?.text = tags?.joinToString(" • ") ?: "" + // home_preview_tags?.isGone = tags.isNullOrEmpty() + // home_preview_image?.setImage(posterUrl, posterHeaders) + // home_preview_title?.text = name + + home_preview_play?.setOnClickListener { + activity?.loadResult(url, apiName, START_ACTION_RESUME_LATEST) + //activity.loadSearchResult(url, START_ACTION_RESUME_LATEST) + } + home_preview_info?.setOnClickListener { + activity?.loadResult(url, apiName) + //activity.loadSearchResult(random) + } + // very ugly code, but I dont care + val watchType = DataStoreHelper.getResultWatchState(this.getId()) + home_preview_bookmark?.setText(watchType.stringRes) + home_preview_bookmark?.setCompoundDrawablesWithIntrinsicBounds( + null, + getDrawable(home_preview_bookmark.context, watchType.iconRes), + null, + null + ) + home_preview_bookmark?.setOnClickListener { fab -> + activity?.showBottomDialog( + WatchType.values() + .map { fab.context.getString(it.stringRes) } + .toList(), + DataStoreHelper.getResultWatchState(this.getId()).ordinal, + fab.context.getString(R.string.action_add_to_bookmarks), + showApply = false, + {}) { + val newValue = WatchType.values()[it] + home_preview_bookmark?.setCompoundDrawablesWithIntrinsicBounds( + null, + getDrawable( + home_preview_bookmark.context, + newValue.iconRes + ), + null, + null + ) + home_preview_bookmark?.setText(newValue.stringRes) + + updateWatchStatus(this, newValue) + reloadStored() + } + } + + } + } + } + } + registerOnPageChangeCallback(callback) + adapter = HomeScrollAdapter() } observe(homeViewModel.apiName) { apiName -> currentApiName = apiName + // setKey(USER_SELECTED_HOMEPAGE_API, apiName) home_api_fab?.text = apiName - (home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setApiName( - apiName - ) + home_provider_name?.text = apiName + try { + home_search?.queryHint = getString(R.string.search_hint_site).format(apiName) + } catch (e: Exception) { + logError(e) + } + home_provider_meta_info?.isVisible = false + + getApiFromNameNull(apiName)?.let { currentApi -> + val typeChoices = listOf( + Pair(R.string.movies, listOf(TvType.Movie)), + Pair(R.string.tv_series, listOf(TvType.TvSeries)), + Pair(R.string.documentaries, listOf(TvType.Documentary)), + Pair(R.string.cartoons, listOf(TvType.Cartoon)), + Pair(R.string.anime, listOf(TvType.Anime, TvType.OVA, TvType.AnimeMovie)), + Pair(R.string.torrent, listOf(TvType.Torrent)), + Pair(R.string.asian_drama, listOf(TvType.AsianDrama)), + ).filter { item -> currentApi.supportedTypes.any { type -> item.second.contains(type) } } + home_provider_meta_info?.text = + typeChoices.joinToString(separator = ", ") { getString(it.first) } + home_provider_meta_info?.isVisible = true + } } + home_main_poster_recyclerview?.adapter = + HomeChildItemAdapter( + mutableListOf(), + R.layout.home_result_big_grid, + nextFocusUp = home_main_poster_recyclerview?.nextFocusUpId, + nextFocusDown = home_main_poster_recyclerview?.nextFocusDownId + ) { callback -> + homeHandleSearch(callback) + } + home_main_poster_recyclerview?.setLinearListLayout() + observe(homeViewModel.randomItems) { items -> + if (items.isNullOrEmpty()) { + toggleMainVisibility(false) + } else { + val tempAdapter = home_main_poster_recyclerview?.adapter as? HomeChildItemAdapter? + // no need to reload if it has the same data + if (tempAdapter != null && tempAdapter.cardList == items) { + toggleMainVisibility(true) + return@observe + } + + val randomSize = items.size + tempAdapter?.updateList(items) + if (!isTvSettings()) { + home_main_poster_recyclerview?.post { + (home_main_poster_recyclerview?.layoutManager as CenterZoomLayoutManager?)?.let { manager -> + manager.updateSize(forceUpdate = true) + if (randomSize > 2) { + manager.scrollToPosition(randomSize / 2) + manager.snap { dx -> + home_main_poster_recyclerview?.post { + // this is the best I can do, fuck android for not including instant scroll + home_main_poster_recyclerview?.smoothScrollBy(dx, 0) + } + } + } + } + } + } else { + items.firstOrNull()?.let { + focusCallback(it) + } + } + toggleMainVisibility(true) + } + } + + home_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + QuickSearchFragment.pushSearch(activity, query, currentApiName?.let { arrayOf(it) }) + + return true + } + + override fun onQueryTextChange(newText: String): Boolean { + //searchViewModel.quickSearch(newText) + return true + } + }) + observe(homeViewModel.page) { data -> when (data) { is Resource.Success -> { @@ -569,15 +763,15 @@ class HomeFragment : Fragment() { val mutableListOfResponse = mutableListOf() listHomepageItems.clear() - (home_master_recycler?.adapter as? ParentItemAdapter)?.updateList( + // println("ITEMCOUNT: ${d.values.size} ${home_master_recycler?.adapter?.itemCount}") + (home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList( d.values.toMutableList(), home_master_recycler ) home_loading?.isVisible = false home_loading_error?.isVisible = false - home_master_recycler?.isVisible = true - //home_loaded?.isVisible = true + home_loaded?.isVisible = true if (toggleRandomButton) { //Flatten list d.values.forEach { dlist -> @@ -617,122 +811,346 @@ class HomeFragment : Fragment() { home_loading?.isVisible = false home_loading_error?.isVisible = true - home_master_recycler?.isVisible = false - //home_loaded?.isVisible = false + home_loaded?.isVisible = false } is Resource.Loading -> { - (home_master_recycler?.adapter as? ParentItemAdapter)?.updateList(listOf()) + (home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList(listOf()) home_loading_shimmer?.startShimmer() home_loading?.isVisible = true home_loading_error?.isVisible = false - home_master_recycler?.isVisible = false - //home_loaded?.isVisible = false + home_loaded?.isVisible = false } } } + val toggleList = listOf( + Pair(home_type_watching_btt, WatchType.WATCHING), + Pair(home_type_completed_btt, WatchType.COMPLETED), + Pair(home_type_dropped_btt, WatchType.DROPPED), + Pair(home_type_on_hold_btt, WatchType.ONHOLD), + Pair(home_plan_to_watch_btt, WatchType.PLANTOWATCH), + ) + val currentSet = getKey(HOME_BOOKMARK_VALUE_LIST) + ?.map { WatchType.fromInternalId(it) }?.toSet() ?: emptySet() + for ((chip, watch) in toggleList) { + chip.isChecked = currentSet.contains(watch) + chip?.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + homeViewModel.loadStoredData( + setOf(watch) + // If we filter all buttons then two can be checked at the same time + // Revert this if you want to go back to multi selection +// toggleList.filter { it.first?.isChecked == true }.map { it.second }.toSet() + ) + } + // Else if all are unchecked -> Do not load data + else if (toggleList.all { it.first?.isChecked != true }) { + homeViewModel.loadStoredData(emptySet()) + } + } + /*chip?.setOnClickListener { + + + homeViewModel.loadStoredData(EnumSet.of(watch)) + } + + chip?.setOnLongClickListener { itemView -> + val list = EnumSet.noneOf(WatchType::class.java) + itemView.context.getKey(HOME_BOOKMARK_VALUE_LIST) + ?.map { WatchType.fromInternalId(it) }?.let { + list.addAll(it) + } + + if (list.contains(watch)) { + list.remove(watch) + } else { + list.add(watch) + } + homeViewModel.loadStoredData(list) + return@setOnLongClickListener true + }*/ + } observe(homeViewModel.availableWatchStatusTypes) { availableWatchStatusTypes -> context?.setKey( HOME_BOOKMARK_VALUE_LIST, availableWatchStatusTypes.first.map { it.internalId }.toIntArray() ) - (home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setAvailableWatchStatusTypes( - availableWatchStatusTypes - ) + + for (item in toggleList) { + val watch = item.second + item.first?.apply { + isVisible = availableWatchStatusTypes.second.contains(watch) + isSelected = availableWatchStatusTypes.first.contains(watch) + } + } + + /*home_bookmark_select?.setOnClickListener { + it.popupMenuNoIcons(availableWatchStatusTypes.second.map { type -> + Pair( + type.internalId, + type.stringRes + ) + }) { + homeViewModel.loadStoredData(it.context, WatchType.fromInternalId(this.itemId)) + } + } + home_bookmarked_parent_item_title?.text = getString(availableWatchStatusTypes.first.stringRes)*/ } - observe(homeViewModel.bookmarks) { data -> - (home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setBookmarkData( - data - ) - } + observe(homeViewModel.bookmarks) { (isVis, bookmarks) -> + home_bookmarked_holder.isVisible = isVis - observe(homeViewModel.resumeWatching) { resumeWatching -> - (home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setResumeWatchingData( - resumeWatching + (home_bookmarked_child_recyclerview?.adapter as? HomeChildItemAdapter?)?.updateList( + bookmarks ) - if (isTrueTvSettings()) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - ioSafe { - activity?.addProgramsToContinueWatching(resumeWatching.mapNotNull { it as? DataStoreHelper.ResumeWatchingResult }) - } + + home_bookmarked_child_more_info?.setOnClickListener { + activity?.loadHomepageList( + HomePageList( + getString(R.string.error_bookmarks_text), //home_bookmarked_parent_item_title?.text?.toString() ?: getString(R.string.error_bookmarks_text), + bookmarks + ) + ) { + deleteAllBookmarkedData() + homeViewModel.loadStoredData(null) } } } + observe(homeViewModel.resumeWatching) { resumeWatching -> + home_watch_holder?.isVisible = resumeWatching.isNotEmpty() + (home_watch_child_recyclerview?.adapter as? HomeChildItemAdapter?)?.updateList( + resumeWatching + ) + + //if (context?.isTvSettings() == true) { + // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // context?.addProgramsToContinueWatching(resumeWatching.mapNotNull { it as? DataStoreHelper.ResumeWatchingResult }) + // } + //} + + home_watch_child_more_info?.setOnClickListener { + activity?.loadHomepageList( + HomePageList( + home_watch_parent_item_title?.text?.toString() + ?: getString(R.string.continue_watching), + resumeWatching + ) + ) { + deleteAllResumeStateIds() + homeViewModel.loadResumeWatching() + } + } + } + + home_bookmarked_child_recyclerview.adapter = HomeChildItemAdapter( + ArrayList(), + nextFocusUp = home_bookmarked_child_recyclerview?.nextFocusUpId, + nextFocusDown = home_bookmarked_child_recyclerview?.nextFocusDownId + ) { callback -> + if (callback.action == SEARCH_ACTION_SHOW_METADATA) { + activity?.showOptionSelectStringRes( + callback.view, + callback.card.posterUrl, + listOf( + R.string.action_open_watching, + R.string.action_remove_from_bookmarks, + ), + listOf( + R.string.action_open_play, + R.string.action_open_watching, + R.string.action_remove_from_bookmarks + ) + ) { (isTv, actionId) -> + fun play() { + activity.loadSearchResult(callback.card, START_ACTION_RESUME_LATEST) + reloadStored() + } + + fun remove() { + setResultWatchState(callback.card.id, WatchType.NONE.internalId) + reloadStored() + } + + fun info() { + handleSearchClickCallback( + activity, + SearchClickCallback( + SEARCH_ACTION_LOAD, + callback.view, + -1, + callback.card + ) + ) + reloadStored() + } + + if (isTv) { + when (actionId) { + 0 -> { + play() + } + 1 -> { + info() + } + 2 -> { + remove() + } + } + } else { + when (actionId) { + 0 -> { + info() + } + 1 -> { + remove() + } + } + } + } + } else { + homeHandleSearch(callback) + } + } + home_watch_child_recyclerview.setLinearListLayout() + home_bookmarked_child_recyclerview.setLinearListLayout() + + home_watch_child_recyclerview?.adapter = HomeChildItemAdapter( + ArrayList(), + nextFocusUp = home_watch_child_recyclerview?.nextFocusUpId, + nextFocusDown = home_watch_child_recyclerview?.nextFocusDownId + ) { callback -> + if (callback.action == SEARCH_ACTION_SHOW_METADATA) { + activity?.showOptionSelectStringRes( + callback.view, + callback.card.posterUrl, + listOf( + R.string.action_open_watching, + R.string.action_remove_watching + ), + listOf( + R.string.action_open_play, + R.string.action_open_watching, + R.string.action_remove_watching + ) + ) { (isTv, actionId) -> + fun play() { + activity.loadSearchResult(callback.card, START_ACTION_RESUME_LATEST) + reloadStored() + } + + fun remove() { + val card = callback.card + if (card is DataStoreHelper.ResumeWatchingResult) { + removeLastWatched(card.parentId) + reloadStored() + } + } + + fun info() { + handleSearchClickCallback( + activity, + SearchClickCallback( + SEARCH_ACTION_LOAD, + callback.view, + -1, + callback.card + ) + ) + reloadStored() + } + + if (isTv) { + when (actionId) { + 0 -> { + play() + } + 1 -> { + info() + } + 2 -> { + remove() + } + } + } else { + when (actionId) { + 0 -> { + info() + } + 1 -> { + remove() + } + } + } + + } + } else { + homeHandleSearch(callback) + } + } //context?.fixPaddingStatusbarView(home_statusbar) - //context?.fixPaddingStatusbar(home_padding) + context?.fixPaddingStatusbar(home_padding) context?.fixPaddingStatusbar(home_loading_statusbar) - home_master_recycler?.adapter = - HomeParentItemAdapterPreview(mutableListOf(), { callback -> + home_master_recycler.adapter = + ParentItemAdapter(mutableListOf(), { callback -> homeHandleSearch(callback) }, { item -> - bottomSheetDialog = activity?.loadHomepageList(item, expandCallback = { + activity?.loadHomepageList(item, expandCallback = { homeViewModel.expandAndReturn(it) - }, dismissCallback = { - bottomSheetDialog = null }) }, { name -> homeViewModel.expand(name) - }, { load -> - activity?.loadResult(load.response.url, load.response.apiName, load.action) - }, { - homeViewModel.loadMoreHomeScrollResponses() - }, { - apiChangeClickListener.onClick(it) - }, reloadStored = { - reloadStored() - }, loadStoredData = { - homeViewModel.loadStoredData(it) - }, { (isQuickSearch, text) -> - if (!isQuickSearch) { - QuickSearchFragment.pushSearch( - activity, - text, - currentApiName?.let { arrayOf(it) }) - } }) + home_master_recycler.setLinearListLayout() + home_master_recycler?.setMaxViewPoolSize(0, Int.MAX_VALUE) + home_master_recycler.layoutManager = object : LinearLayoutManager(context) { + override fun supportsPredictiveItemAnimations(): Boolean { + return false + } + } // GridLayoutManager(context, 1).also { it.supportsPredictiveItemAnimations() } reloadStored() - loadHomePage(false) - home_master_recycler?.addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - if (dy > 0) { //check for scroll down - home_api_fab?.shrink() // hide - home_random?.shrink() - } else if (dy < -5) { - if (!isTvSettings()) { - home_api_fab?.extend() // show - home_random?.extend() - } - } + loadHomePage() - super.onScrolled(recyclerView, dx, dy) + home_loaded.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { v, _, scrollY, _, oldScrollY -> + val dy = scrollY - oldScrollY + if (dy > 0) { //check for scroll down + home_api_fab?.shrink() // hide + home_random?.shrink() + } else if (dy < -5) { + if (!isTvSettings()) { + home_api_fab?.extend() // show + home_random?.extend() + } } }) // nice profile pic on homepage - //home_profile_picture_holder?.isVisible = false + home_profile_picture_holder?.isVisible = false // just in case if (isTvSettings()) { home_api_fab?.isVisible = false + home_change_api?.isVisible = true if (isTrueTvSettings()) { home_change_api_loading?.isVisible = true home_change_api_loading?.isFocusable = true home_change_api_loading?.isFocusableInTouchMode = true + home_change_api?.isFocusable = true + home_change_api?.isFocusableInTouchMode = true } // home_bookmark_select?.isFocusable = true // home_bookmark_select?.isFocusableInTouchMode = true } else { home_api_fab?.isVisible = true + home_change_api?.isVisible = false home_change_api_loading?.isVisible = false } - //TODO READD THIS - /*for (syncApi in OAuth2Apis) { + + for (syncApi in OAuth2Apis) { val login = syncApi.loginInfo() val pic = login?.profilePicture if (home_profile_picture?.setImage( @@ -743,6 +1161,6 @@ class HomeFragment : Fragment() { home_profile_picture_holder?.isVisible = true break } - }*/ + } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index 58c6dbe0..23ab81ce 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -4,70 +4,34 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout -import android.widget.LinearLayout import android.widget.TextView -import androidx.core.content.ContextCompat -import androidx.core.view.isGone -import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListUpdateCallback import androidx.recyclerview.widget.RecyclerView -import androidx.transition.ChangeBounds -import androidx.transition.TransitionManager -import androidx.viewpager2.widget.ViewPager2 -import com.google.android.material.chip.Chip -import com.google.android.material.chip.ChipDrawable -import com.lagradost.cloudstream3.APIHolder.getId -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity import com.lagradost.cloudstream3.HomePageList -import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.SearchResponse -import com.lagradost.cloudstream3.mvvm.Resource -import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.result.LinearListLayout -import com.lagradost.cloudstream3.ui.result.ResultViewModel2 -import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST import com.lagradost.cloudstream3.ui.result.setLinearListLayout -import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable -import com.lagradost.cloudstream3.utils.AppUtils.loadResult -import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog -import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView -import kotlinx.android.synthetic.main.activity_main_tv.* -import kotlinx.android.synthetic.main.activity_main_tv.view.* -import kotlinx.android.synthetic.main.fragment_home.* -import kotlinx.android.synthetic.main.fragment_home.view.* -import kotlinx.android.synthetic.main.fragment_home_head_tv.* -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.* -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_viewpager import kotlinx.android.synthetic.main.homepage_parent.view.* -class LoadClickCallback( - val action: Int = 0, - val view: View, - val position: Int, - val response: LoadResponse -) -open class ParentItemAdapter( +class ParentItemAdapter( private var items: MutableList, private val clickCallback: (SearchClickCallback) -> Unit, private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit, private val expandCallback: ((String) -> Unit)? = null, ) : RecyclerView.Adapter() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + + override fun onCreateViewHolder(parent: ViewGroup, i: Int): ParentViewHolder { + //println("onCreateViewHolder $i") + val layout = + if (isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent return ParentViewHolder( - LayoutInflater.from(parent.context).inflate( - if (isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent, - parent, - false - ), + LayoutInflater.from(parent.context).inflate(layout, parent, false), clickCallback, moreInfoClickCallback, expandCallback @@ -75,6 +39,8 @@ open class ParentItemAdapter( } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + //println("onBindViewHolder $position") + when (holder) { is ParentViewHolder -> { holder.bind(items[position]) @@ -115,60 +81,47 @@ open class ParentItemAdapter( items.clear() items.addAll(new) - //val mAdapter = this - val delta = if (this@ParentItemAdapter is HomeParentItemAdapterPreview) { - headItems - } else { - 0 - } - + val mAdapter = this diffResult.dispatchUpdatesTo(object : ListUpdateCallback { override fun onInserted(position: Int, count: Int) { - //notifyItemRangeChanged(position + delta, count) - notifyItemRangeInserted(position + delta, count) + mAdapter.notifyItemRangeInserted(position, count) } override fun onRemoved(position: Int, count: Int) { - notifyItemRangeRemoved(position + delta, count) + mAdapter.notifyItemRangeRemoved(position, count) } override fun onMoved(fromPosition: Int, toPosition: Int) { - notifyItemMoved(fromPosition + delta, toPosition + delta) + mAdapter.notifyItemMoved(fromPosition, toPosition) } - override fun onChanged(_position: Int, count: Int, payload: Any?) { - - val position = _position + delta - + override fun onChanged(position: Int, count: Int, payload: Any?) { // I know kinda messy, what this does is using the update or bind instead of onCreateViewHolder -> bind recyclerView?.apply { // this loops every viewHolder in the recycle view and checks the position to see if it is within the update range val missingUpdates = (position until (position + count)).toMutableSet() for (i in 0 until itemCount) { - val child = getChildAt(i) ?: continue - val viewHolder = getChildViewHolder(child) ?: continue - if (viewHolder !is ParentViewHolder) continue - - val absolutePosition = viewHolder.bindingAdapterPosition + val viewHolder = getChildViewHolder(getChildAt(i)) + val absolutePosition = viewHolder.absoluteAdapterPosition if (absolutePosition >= position && absolutePosition < position + count) { - val expand = items.getOrNull(absolutePosition - delta) ?: continue - missingUpdates -= absolutePosition - //println("Updating ${viewHolder.title.text} ($absolutePosition $position) -> ${expand.list.name}") - if (viewHolder.title.text == expand.list.name) { - viewHolder.update(expand) - } else { - viewHolder.bind(expand) + val expand = items.getOrNull(absolutePosition) ?: continue + if (viewHolder is ParentViewHolder) { + missingUpdates -= absolutePosition + if (viewHolder.title.text == expand.list.name) { + viewHolder.update(expand) + } else { + viewHolder.bind(expand) + } } } } // just in case some item did not get updated for (i in missingUpdates) { - notifyItemChanged(i, payload) + mAdapter.notifyItemChanged(i, payload) } - } ?: run { - // in case we don't have a nice - notifyItemRangeChanged(position, count, payload) + } ?: run { // in case we don't have a nice + mAdapter.notifyItemRangeChanged(position, count, payload) } } }) @@ -184,8 +137,9 @@ open class ParentItemAdapter( private val expandCallback: ((String) -> Unit)? = null, ) : RecyclerView.ViewHolder(itemView) { - val title: TextView = itemView.home_child_more_info - private val recyclerView: RecyclerView = itemView.home_child_recyclerview + val title: TextView = itemView.home_parent_item_title + val recyclerView: RecyclerView = itemView.home_child_recyclerview + private val moreInfo: FrameLayout? = itemView.home_child_more_info fun update(expand: HomeViewModel.ExpandableHomepageList) { val info = expand.list @@ -247,10 +201,9 @@ open class ParentItemAdapter( }) //(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged() - if (!isTvSettings()) { - title.setOnClickListener { - moreInfoClickCallback.invoke(expand) - } + + moreInfo?.setOnClickListener { + moreInfoClickCallback.invoke(expand) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt deleted file mode 100644 index 94a1a526..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ /dev/null @@ -1,658 +0,0 @@ -package com.lagradost.cloudstream3.ui.home - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import androidx.appcompat.widget.SearchView -import androidx.core.content.ContextCompat -import androidx.core.view.isGone -import androidx.core.view.isVisible -import androidx.recyclerview.widget.RecyclerView -import androidx.viewpager2.widget.ViewPager2 -import com.google.android.material.chip.Chip -import com.google.android.material.chip.ChipDrawable -import com.lagradost.cloudstream3.APIHolder.getId -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity -import com.lagradost.cloudstream3.HomePageList -import com.lagradost.cloudstream3.LoadResponse -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.SearchResponse -import com.lagradost.cloudstream3.mvvm.Resource -import com.lagradost.cloudstream3.ui.WatchType -import com.lagradost.cloudstream3.ui.result.ResultViewModel2 -import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST -import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD -import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA -import com.lagradost.cloudstream3.ui.search.SearchClickCallback -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings -import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog -import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes -import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar -import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView -import com.lagradost.cloudstream3.utils.UIHelper.setImage -import kotlinx.android.synthetic.main.activity_main.view.* -import kotlinx.android.synthetic.main.fragment_home_head.view.* -import kotlinx.android.synthetic.main.fragment_home_head.view.home_bookmarked_child_recyclerview -import kotlinx.android.synthetic.main.fragment_home_head.view.home_watch_parent_item_title -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.* -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_bookmarked_holder -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_none_padding -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_plan_to_watch_btt -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_viewpager -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_type_completed_btt -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_type_dropped_btt -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_type_on_hold_btt -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_type_watching_btt -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_watch_child_recyclerview -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_watch_holder -import kotlinx.android.synthetic.main.toast.view.* - -class HomeParentItemAdapterPreview( - items: MutableList, - val clickCallback: (SearchClickCallback) -> Unit, - private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit, - expandCallback: ((String) -> Unit)? = null, - private val loadCallback: (LoadClickCallback) -> Unit, - private val loadMoreCallback: (() -> Unit), - private val changeHomePageCallback: ((View) -> Unit), - private val reloadStored: (() -> Unit), - private val loadStoredData: ((Set) -> Unit), - private val searchQueryCallback: ((Pair) -> Unit) -) : ParentItemAdapter(items, clickCallback, moreInfoClickCallback, expandCallback) { - private var previewData: Resource>> = Resource.Loading() - private var resumeWatchingData: List = listOf() - private var bookmarkData: Pair> = - false to listOf() - private var apiName: String = "NONE" - - val headItems = 1 - - private var availableWatchStatusTypes: Pair, Set> = - setOf() to setOf() - - fun setAvailableWatchStatusTypes(data: Pair, Set>) { - availableWatchStatusTypes = data - holder?.setAvailableWatchStatusTypes(data) - } - - companion object { - private const val VIEW_TYPE_HEADER = 2 - private const val VIEW_TYPE_ITEM = 1 - } - - fun setResumeWatchingData(resumeWatching: List) { - resumeWatchingData = resumeWatching - holder?.updateResume(resumeWatchingData) - } - - fun setPreviewData(preview: Resource>>) { - previewData = preview - holder?.updatePreview(preview) - } - - fun setApiName(name: String) { - apiName = name - holder?.updateApiName(name) - } - - fun setBookmarkData(data: Pair>) { - bookmarkData = data - holder?.updateBookmarks(data) - } - - override fun getItemViewType(position: Int) = when (position) { - 0 -> VIEW_TYPE_HEADER - else -> VIEW_TYPE_ITEM - } - - var holder: HeaderViewHolder? = null - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (holder) { - is HeaderViewHolder -> { - holder.updatePreview(previewData) - holder.updateResume(resumeWatchingData) - holder.updateBookmarks(bookmarkData) - holder.setAvailableWatchStatusTypes(availableWatchStatusTypes) - holder.updateApiName(apiName) - } - else -> super.onBindViewHolder(holder, position - 1) - } - } - - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - println("onCreateViewHolder $viewType") - return when (viewType) { - VIEW_TYPE_HEADER -> HeaderViewHolder( - LayoutInflater.from(parent.context).inflate( - if (isTvSettings()) R.layout.fragment_home_head_tv else R.layout.fragment_home_head, - parent, - false - ), - loadCallback, - loadMoreCallback, - changeHomePageCallback, - clickCallback, - reloadStored, - loadStoredData, - searchQueryCallback, - moreInfoClickCallback - ).also { - this.holder = it - } - VIEW_TYPE_ITEM -> super.onCreateViewHolder(parent, viewType) - else -> error("Unhandled viewType=$viewType") - } - } - - override fun getItemCount(): Int { - return super.getItemCount() + headItems - } - - override fun getItemId(position: Int): Long { - if (position == 0) return previewData.hashCode().toLong() - return super.getItemId(position - headItems) - } - - override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) { - when (holder) { - is HeaderViewHolder -> { - holder.onViewDetachedFromWindow() - } - else -> super.onViewDetachedFromWindow(holder) - } - } - - override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) { - when (holder) { - is HeaderViewHolder -> { - holder.onViewAttachedToWindow() - } - else -> super.onViewAttachedToWindow(holder) - } - } - - - class HeaderViewHolder - constructor( - itemView: View, - private val clickCallback: ((LoadClickCallback) -> Unit)?, - private val loadMoreCallback: (() -> Unit), - private val changeHomePageCallback: ((View) -> Unit), - private val searchClickCallback: (SearchClickCallback) -> Unit, - private val reloadStored: () -> Unit, - private val loadStoredData: ((Set) -> Unit), - private val searchQueryCallback: ((Pair) -> Unit), - private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit - ) : RecyclerView.ViewHolder(itemView) { - private var previewAdapter: HomeScrollAdapter? = null - private val previewViewpager: ViewPager2? = itemView.home_preview_viewpager - private val previewHeader: FrameLayout? = itemView.home_preview - private val previewCallback: ViewPager2.OnPageChangeCallback = - object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - // home_search?.isIconified = true - //home_search?.isVisible = true - //home_search?.clearFocus() - - previewAdapter?.apply { - if (position >= itemCount - 1 && hasMoreItems) { - hasMoreItems = false // dont make two requests - loadMoreCallback() - //homeViewModel.loadMoreHomeScrollResponses() - } - } - previewAdapter?.getItem(position) - ?.apply { - //itemView.home_preview_title_holder?.let { parent -> - // TransitionManager.beginDelayedTransition( - // parent, - // ChangeBounds() - // ) - //} - itemView.home_preview_description?.isGone = - this.plot.isNullOrBlank() - itemView.home_preview_description?.text = - this.plot ?: "" - itemView.home_preview_text?.text = this.name - itemView.home_preview_tags?.apply { - removeAllViews() - tags?.forEach { tag -> - val chip = Chip(context) - val chipDrawable = - ChipDrawable.createFromAttributes( - context, - null, - 0, - R.style.ChipFilledSemiTransparent - ) - chip.setChipDrawable(chipDrawable) - chip.text = tag - chip.isChecked = false - chip.isCheckable = false - chip.isFocusable = false - chip.isClickable = false - addView(chip) - } - } - itemView.home_preview_tags?.isGone = - tags.isNullOrEmpty() - itemView.home_preview_image?.setImage( - posterUrl, - posterHeaders - ) - // itemView.home_preview_title?.text = name - - itemView.home_preview_play?.setOnClickListener { view -> - clickCallback?.invoke( - LoadClickCallback( - START_ACTION_RESUME_LATEST, - view, - position, - this - ) - ) - } - itemView.home_preview_info?.setOnClickListener { view -> - clickCallback?.invoke( - LoadClickCallback(0, view, position, this) - ) - } - - itemView.home_preview_play_btt?.setOnClickListener { view -> - clickCallback?.invoke( - LoadClickCallback( - START_ACTION_RESUME_LATEST, - view, - position, - this - ) - ) - } - - // This makes the hidden next buttons only available when on the info button - // Otherwise you might be able to go to the next item without being at the info button - itemView.home_preview_info_btt?.setOnFocusChangeListener { _, hasFocus -> - itemView.home_preview_hidden_next_focus?.isFocusable = hasFocus - } - itemView.home_preview_play_btt?.setOnFocusChangeListener { _, hasFocus -> - itemView.home_preview_hidden_prev_focus?.isFocusable = hasFocus - } - - - itemView.home_preview_info_btt?.setOnClickListener { view -> - clickCallback?.invoke( - LoadClickCallback(0, view, position, this) - ) - } - - itemView.home_preview_hidden_next_focus?.setOnFocusChangeListener { _, hasFocus -> - if (hasFocus) { - previewViewpager?.apply { - setCurrentItem(currentItem + 1, true) - } - itemView.home_preview_info_btt?.requestFocus() - } - } - - itemView.home_preview_hidden_prev_focus?.setOnFocusChangeListener { _, hasFocus -> - if (hasFocus) { - previewViewpager?.apply { - if (currentItem <= 0) { - nav_rail_view?.menu?.getItem(0)?.actionView?.requestFocus() - } else { - setCurrentItem(currentItem - 1, true) - itemView.home_preview_play_btt?.requestFocus() - } - } - } - } - // very ugly code, but I dont care - val watchType = - DataStoreHelper.getResultWatchState(this.getId()) - itemView.home_preview_bookmark?.setText(watchType.stringRes) - itemView.home_preview_bookmark?.setCompoundDrawablesWithIntrinsicBounds( - null, - ContextCompat.getDrawable( - itemView.home_preview_bookmark.context, - watchType.iconRes - ), - null, - null - ) - itemView.home_preview_bookmark?.setOnClickListener { fab -> - fab.context.getActivity()?.showBottomDialog( - WatchType.values() - .map { fab.context.getString(it.stringRes) } - .toList(), - DataStoreHelper.getResultWatchState(this.getId()).ordinal, - fab.context.getString(R.string.action_add_to_bookmarks), - showApply = false, - {}) { - val newValue = WatchType.values()[it] - itemView.home_preview_bookmark?.setCompoundDrawablesWithIntrinsicBounds( - null, - ContextCompat.getDrawable( - itemView.home_preview_bookmark.context, - newValue.iconRes - ), - null, - null - ) - itemView.home_preview_bookmark?.setText(newValue.stringRes) - - ResultViewModel2.updateWatchStatus( - this, - newValue - ) - reloadStored() - } - } - } - } - } - - private var resumeAdapter: HomeChildItemAdapter? = null - private var resumeHolder: View? = itemView.home_watch_holder - private var resumeRecyclerView: RecyclerView? = itemView.home_watch_child_recyclerview - - private var bookmarkHolder: View? = itemView.home_bookmarked_holder - private var bookmarkAdapter: HomeChildItemAdapter? = null - private var bookmarkRecyclerView: RecyclerView? = - itemView.home_bookmarked_child_recyclerview - - fun onViewDetachedFromWindow() { - previewViewpager?.unregisterOnPageChangeCallback(previewCallback) - } - - fun onViewAttachedToWindow() { - previewViewpager?.registerOnPageChangeCallback(previewCallback) - } - - private val toggleList = listOf( - Pair(itemView.home_type_watching_btt, WatchType.WATCHING), - Pair(itemView.home_type_completed_btt, WatchType.COMPLETED), - Pair(itemView.home_type_dropped_btt, WatchType.DROPPED), - Pair(itemView.home_type_on_hold_btt, WatchType.ONHOLD), - Pair(itemView.home_plan_to_watch_btt, WatchType.PLANTOWATCH), - ) - - init { - itemView.home_preview_change_api?.setOnClickListener { view -> - changeHomePageCallback(view) - } - itemView.home_preview_change_api2?.setOnClickListener { view -> - changeHomePageCallback(view) - } - - previewViewpager?.apply { - //if (!isTvSettings()) - setPageTransformer(HomeScrollTransformer()) - //else - // setPageTransformer(null) - - if (adapter == null) - adapter = HomeScrollAdapter( - if (isTvSettings()) R.layout.home_scroll_view_tv else R.layout.home_scroll_view, - if (isTvSettings()) true else null - ) - } - previewAdapter = previewViewpager?.adapter as? HomeScrollAdapter? - // previewViewpager?.registerOnPageChangeCallback(previewCallback) - - if (resumeAdapter == null) { - resumeRecyclerView?.adapter = HomeChildItemAdapter( - ArrayList(), - nextFocusUp = itemView.nextFocusUpId, - nextFocusDown = itemView.nextFocusDownId - ) { callback -> - if (callback.action != SEARCH_ACTION_SHOW_METADATA) { - searchClickCallback(callback) - return@HomeChildItemAdapter - } - callback.view.context?.getActivity()?.showOptionSelectStringRes( - callback.view, - callback.card.posterUrl, - listOf( - R.string.action_open_watching, - R.string.action_remove_watching - ), - listOf( - R.string.action_open_play, - R.string.action_open_watching, - R.string.action_remove_watching - ) - ) { (isTv, actionId) -> - when (actionId + if (isTv) 0 else 1) { - // play - 0 -> { - searchClickCallback.invoke( - SearchClickCallback( - START_ACTION_RESUME_LATEST, - callback.view, - -1, - callback.card - ) - ) - reloadStored() - } - //info - 1 -> { - searchClickCallback( - SearchClickCallback( - SEARCH_ACTION_LOAD, - callback.view, - -1, - callback.card - ) - ) - - reloadStored() - } - // remove - 2 -> { - val card = callback.card - if (card is DataStoreHelper.ResumeWatchingResult) { - DataStoreHelper.removeLastWatched(card.parentId) - reloadStored() - } - } - } - } - } - } - resumeAdapter = resumeRecyclerView?.adapter as? HomeChildItemAdapter - if (bookmarkAdapter == null) { - bookmarkRecyclerView?.adapter = HomeChildItemAdapter( - ArrayList(), - nextFocusUp = itemView.nextFocusUpId, - nextFocusDown = itemView.nextFocusDownId - ) { callback -> - if (callback.action != SEARCH_ACTION_SHOW_METADATA) { - searchClickCallback(callback) - return@HomeChildItemAdapter - } - callback.view.context?.getActivity()?.showOptionSelectStringRes( - callback.view, - callback.card.posterUrl, - listOf( - R.string.action_open_watching, - R.string.action_remove_from_bookmarks, - ), - listOf( - R.string.action_open_play, - R.string.action_open_watching, - R.string.action_remove_from_bookmarks - ) - ) { (isTv, actionId) -> - when (actionId + if (isTv) 0 else 1) { // play - 0 -> { - searchClickCallback.invoke( - SearchClickCallback( - START_ACTION_RESUME_LATEST, - callback.view, - -1, - callback.card - ) - ) - reloadStored() - } - 1 -> { // info - searchClickCallback( - SearchClickCallback( - SEARCH_ACTION_LOAD, - callback.view, - -1, - callback.card - ) - ) - - reloadStored() - } - 2 -> { // remove - DataStoreHelper.setResultWatchState( - callback.card.id, - WatchType.NONE.internalId - ) - reloadStored() - } - } - } - } - } - bookmarkAdapter = bookmarkRecyclerView?.adapter as? HomeChildItemAdapter - - for ((chip, watch) in toggleList) { - chip?.isChecked = false - chip?.setOnCheckedChangeListener { _, isChecked -> - if (isChecked) { - loadStoredData( - setOf(watch) - // If we filter all buttons then two can be checked at the same time - // Revert this if you want to go back to multi selection -// toggleList.filter { it.first?.isChecked == true }.map { it.second }.toSet() - ) - } - // Else if all are unchecked -> Do not load data - else if (toggleList.all { it.first?.isChecked != true }) { - loadStoredData(emptySet()) - } - } - } - - itemView.home_search?.context?.fixPaddingStatusbar(itemView.home_search) - - itemView.home_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { - override fun onQueryTextSubmit(query: String): Boolean { - searchQueryCallback.invoke(false to query) - //QuickSearchFragment.pushSearch(activity, query, currentApiName?.let { arrayOf(it) } - return true - } - - override fun onQueryTextChange(newText: String): Boolean { - searchQueryCallback.invoke(true to newText) - //searchViewModel.quickSearch(newText) - return true - } - }) - } - - fun updateApiName(name: String) { - itemView.home_preview_change_api2?.text = name - itemView.home_preview_change_api?.text = name - } - - fun updatePreview(preview: Resource>>) { - itemView.home_preview_change_api2?.isGone = preview is Resource.Success - if (preview is Resource.Success) { - itemView.home_none_padding?.apply { - val params = layoutParams - params.height = 0 - layoutParams = params - } - } else { - itemView.home_none_padding?.context?.fixPaddingStatusbarView(itemView.home_none_padding) - } - when (preview) { - is Resource.Success -> { - if (true != previewAdapter?.setItems( - preview.value.second, - preview.value.first - ) - ) { - // this might seam weird and useless, however this prevents a very weird andrid bug were the viewpager is not rendered properly - // I have no idea why that happens, but this is my ducktape solution - previewViewpager?.setCurrentItem(0, false) - previewViewpager?.beginFakeDrag() - previewViewpager?.fakeDragBy(1f) - previewViewpager?.endFakeDrag() - previewCallback.onPageSelected(0) - previewHeader?.isVisible = true - } - } - else -> { - previewAdapter?.setItems(listOf(), false) - previewViewpager?.setCurrentItem(0, false) - previewHeader?.isVisible = false - } - } - // previewViewpager?.postDelayed({ previewViewpager?.scr(100, 0) }, 1000) - //previewViewpager?.postInvalidate() - } - - fun updateResume(resumeWatching: List) { - resumeHolder?.isVisible = resumeWatching.isNotEmpty() - resumeAdapter?.updateList(resumeWatching) - - if (!isTvSettings()) { - itemView.home_watch_parent_item_title?.setOnClickListener { - moreInfoClickCallback.invoke( - HomeViewModel.ExpandableHomepageList( - HomePageList( - itemView.home_watch_parent_item_title?.text.toString(), - resumeWatching, - false - ), 1, false - ) - ) - } - } - } - - fun updateBookmarks(data: Pair>) { - bookmarkHolder?.isVisible = data.first - bookmarkAdapter?.updateList(data.second) - if (!isTvSettings()) { - itemView.home_bookmark_parent_item_title?.setOnClickListener { - val items = toggleList.mapNotNull { it.first }.filter { it.isChecked } - if (items.isEmpty()) return@setOnClickListener // we don't want to show an empty dialog - val textSum = items - .mapNotNull { it.text }.joinToString() - - moreInfoClickCallback.invoke( - HomeViewModel.ExpandableHomepageList( - HomePageList( - textSum, - data.second, - false - ), 1, false - ) - ) - } - } - } - - fun setAvailableWatchStatusTypes(availableWatchStatusTypes: Pair, Set>) { - for ((chip, watch) in toggleList) { - chip?.apply { - isVisible = availableWatchStatusTypes.second.contains(watch) - isChecked = availableWatchStatusTypes.first.contains(watch) - } - } - } - } -} \ No newline at end of file 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 f296e53d..a3eaf7c7 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 @@ -1,29 +1,22 @@ package com.lagradost.cloudstream3.ui.home -import android.content.res.Configuration import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.annotation.LayoutRes import androidx.core.view.isGone import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.utils.UIHelper.setImage -import kotlinx.android.synthetic.main.fragment_home_head_tv.* -import kotlinx.android.synthetic.main.fragment_home_head_tv.view.* import kotlinx.android.synthetic.main.home_scroll_view.view.* -class HomeScrollAdapter( - @LayoutRes val layout: Int = R.layout.home_scroll_view, - private val forceHorizontalPosters: Boolean? = null -) : RecyclerView.Adapter() { +class HomeScrollAdapter : RecyclerView.Adapter() { private var items: MutableList = mutableListOf() var hasMoreItems: Boolean = false - fun getItem(position: Int): LoadResponse? { + fun getItem(position: Int) : LoadResponse? { return items.getOrNull(position) } @@ -46,8 +39,7 @@ class HomeScrollAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return CardViewHolder( - LayoutInflater.from(parent.context).inflate(layout, parent, false), - forceHorizontalPosters + LayoutInflater.from(parent.context).inflate(R.layout.home_scroll_view, parent, false), ) } @@ -62,17 +54,11 @@ class HomeScrollAdapter( class CardViewHolder constructor( itemView: View, - private val forceHorizontalPosters: Boolean? = null ) : RecyclerView.ViewHolder(itemView) { fun bind(card: LoadResponse) { card.apply { - val isHorizontal = - (forceHorizontalPosters == true) || ((forceHorizontalPosters != false) && itemView.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) - - val posterUrl = if (isHorizontal) backgroundPosterUrl ?: posterUrl else posterUrl - ?: backgroundPosterUrl itemView.home_scroll_preview_tags?.text = tags?.joinToString(" • ") ?: "" itemView.home_scroll_preview_tags?.isGone = tags.isNullOrEmpty() itemView.home_scroll_preview?.setImage(posterUrl, posterHeaders) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index edf58008..9d75b0f0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -36,42 +36,6 @@ import java.util.* import kotlin.collections.set class HomeViewModel : ViewModel() { - companion object { - suspend fun getResumeWatching(): List? { - val resumeWatching = withContext(Dispatchers.IO) { - getAllResumeStateIds()?.mapNotNull { id -> - getLastWatched(id) - }?.sortedBy { -it.updateTime } - } - val resumeWatchingResult = withContext(Dispatchers.IO) { - resumeWatching?.mapNotNull { resume -> - - val data = getKey( - DOWNLOAD_HEADER_CACHE, - resume.parentId.toString() - ) ?: return@mapNotNull null - - val watchPos = getViewPos(resume.episodeId) - - DataStoreHelper.ResumeWatchingResult( - data.name, - data.url, - data.apiName, - data.type, - data.poster, - watchPos, - resume.episodeId, - resume.parentId, - resume.episode, - resume.season, - resume.isFromDownload - ) - } - } - return resumeWatchingResult - } - } - private var repo: APIRepository? = null private val _apiName = MutableLiveData() @@ -102,7 +66,36 @@ class HomeViewModel : ViewModel() { val preview: LiveData>>> = _preview fun loadResumeWatching() = viewModelScope.launchSafe { - val resumeWatchingResult = getResumeWatching() + val resumeWatching = withContext(Dispatchers.IO) { + getAllResumeStateIds()?.mapNotNull { id -> + getLastWatched(id) + }?.sortedBy { -it.updateTime } + } + + // val resumeWatchingResult = ArrayList() + + val resumeWatchingResult = withContext(Dispatchers.IO) { + resumeWatching?.map { resume -> + val data = getKey( + DOWNLOAD_HEADER_CACHE, + resume.parentId.toString() + ) ?: return@map null + val watchPos = getViewPos(resume.episodeId) + DataStoreHelper.ResumeWatchingResult( + data.name, + data.url, + data.apiName, + data.type, + data.poster, + watchPos, + resume.episodeId, + resume.parentId, + resume.episode, + resume.season, + resume.isFromDownload + ) + }?.filterNotNull() + } resumeWatchingResult?.let { _resumeWatching.postValue(it) } @@ -128,7 +121,6 @@ class HomeViewModel : ViewModel() { currentWatchTypes.remove(WatchType.NONE) if (currentWatchTypes.size <= 0) { - _availableWatchStatusTypes.postValue(setOf() to setOf()) _bookmarks.postValue(Pair(false, ArrayList())) return@launchSafe } @@ -265,83 +257,80 @@ class HomeViewModel : ViewModel() { _apiName.postValue(repo?.name) _randomItems.postValue(listOf()) - if (repo?.hasMainPage != true) { + if (repo?.hasMainPage == true) { + _page.postValue(Resource.Loading()) + _preview.postValue(Resource.Loading()) + addJob?.cancel() + + when (val data = repo?.getMainPage(1, null)) { + is Resource.Success -> { + try { + expandable.clear() + data.value.forEach { home -> + home?.items?.forEach { list -> + val filteredList = + context?.filterHomePageListByFilmQuality(list) ?: list + expandable[list.name] = + ExpandableHomepageList(filteredList, 1, home.hasNext) + } + } + + val items = data.value.mapNotNull { it?.items }.flatten() + + + previewResponses.clear() + previewResponsesAdded.clear() + + //val home = data.value + if (items.isNotEmpty()) { + val currentList = + items.shuffled().filter { it.list.isNotEmpty() } + .flatMap { it.list } + .distinctBy { it.url } + .toList() + + if (currentList.isNotEmpty()) { + val randomItems = + context?.filterSearchResultByFilmQuality(currentList.shuffled()) + ?: currentList.shuffled() + + updatePreviewResponses( + previewResponses, + previewResponsesAdded, + randomItems, + 3 + ) + + _randomItems.postValue(randomItems) + currentShuffledList = randomItems + } + } + if (previewResponses.isEmpty()) { + _preview.postValue( + Resource.Failure( + false, + null, + null, + "No homepage responses" + ) + ) + } else { + _preview.postValue(Resource.Success((previewResponsesAdded.size < currentShuffledList.size) to previewResponses)) + } + _page.postValue(Resource.Success(expandable)) + } catch (e: Exception) { + _randomItems.postValue(emptyList()) + logError(e) + } + } + is Resource.Failure -> { + _page.postValue(data!!) + } + else -> Unit + } + } else { _page.postValue(Resource.Success(emptyMap())) _preview.postValue(Resource.Failure(false, null, null, "No homepage")) - return@ioSafe - } - - - _page.postValue(Resource.Loading()) - _preview.postValue(Resource.Loading()) - addJob?.cancel() - - when (val data = repo?.getMainPage(1, null)) { - is Resource.Success -> { - try { - expandable.clear() - data.value.forEach { home -> - home?.items?.forEach { list -> - val filteredList = - context?.filterHomePageListByFilmQuality(list) ?: list - expandable[list.name] = - ExpandableHomepageList(filteredList, 1, home.hasNext) - } - } - - val items = data.value.mapNotNull { it?.items }.flatten() - - - previewResponses.clear() - previewResponsesAdded.clear() - - //val home = data.value - if (items.isNotEmpty()) { - val currentList = - items.shuffled().filter { it.list.isNotEmpty() } - .flatMap { it.list } - .distinctBy { it.url } - .toList() - - if (currentList.isNotEmpty()) { - val randomItems = - context?.filterSearchResultByFilmQuality(currentList.shuffled()) - ?: currentList.shuffled() - - updatePreviewResponses( - previewResponses, - previewResponsesAdded, - randomItems, - 3 - ) - - _randomItems.postValue(randomItems) - currentShuffledList = randomItems - } - } - if (previewResponses.isEmpty()) { - _preview.postValue( - Resource.Failure( - false, - null, - null, - "No homepage responses" - ) - ) - } else { - _preview.postValue(Resource.Success((previewResponsesAdded.size < currentShuffledList.size) to previewResponses)) - } - _page.postValue(Resource.Success(expandable)) - } catch (e: Exception) { - _randomItems.postValue(emptyList()) - logError(e) - } - } - is Resource.Failure -> { - _page.postValue(data!!) - _preview.postValue(data!!) - } - else -> Unit } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt deleted file mode 100644 index d7c06c4e..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ /dev/null @@ -1,395 +0,0 @@ -package com.lagradost.cloudstream3.ui.library - -import android.app.Activity -import android.content.Context -import android.content.res.Configuration -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import androidx.fragment.app.Fragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.animation.AlphaAnimation -import androidx.annotation.StringRes -import androidx.appcompat.widget.SearchView -import androidx.core.view.isVisible -import androidx.fragment.app.activityViewModels -import com.google.android.material.tabs.TabLayoutMediator -import com.lagradost.cloudstream3.APIHolder -import com.lagradost.cloudstream3.APIHolder.allProviders -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.mvvm.Resource -import com.lagradost.cloudstream3.mvvm.debugAssert -import com.lagradost.cloudstream3.mvvm.observe -import com.lagradost.cloudstream3.syncproviders.SyncAPI -import com.lagradost.cloudstream3.syncproviders.SyncIdName -import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment -import com.lagradost.cloudstream3.ui.result.txt -import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD -import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA -import com.lagradost.cloudstream3.utils.AppUtils.loadResult -import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult -import com.lagradost.cloudstream3.utils.AppUtils.reduceDragSensitivity -import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog -import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar -import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount -import kotlinx.android.synthetic.main.fragment_library.* -import kotlin.math.abs - -const val LIBRARY_FOLDER = "library_folder" - - -enum class LibraryOpenerType(@StringRes val stringRes: Int) { - Default(R.string.default_subtitles), // TODO FIX AFTER MERGE - Provider(R.string.none), - Browser(R.string.browser), - Search(R.string.search), - None(R.string.none), -} - -/** Used to store how the user wants to open said poster */ -data class LibraryOpener( - val openType: LibraryOpenerType, - val providerData: ProviderLibraryData?, -) - -data class ProviderLibraryData( - val apiName: String -) - -class LibraryFragment : Fragment() { - companion object { - fun newInstance() = LibraryFragment() - - /** - * Store which page was last seen when exiting the fragment and returning - **/ - const val VIEWPAGER_ITEM_KEY = "viewpager_item" - } - - private val libraryViewModel: LibraryViewModel by activityViewModels() - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_library, container, false) - } - - override fun onSaveInstanceState(outState: Bundle) { - viewpager?.currentItem?.let { currentItem -> - outState.putInt(VIEWPAGER_ITEM_KEY, currentItem) - } - super.onSaveInstanceState(outState) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - context?.fixPaddingStatusbar(search_status_bar_padding) - - sort_fab?.setOnClickListener { - val methods = libraryViewModel.sortingMethods.map { - txt(it.stringRes).asString(view.context) - } - - activity?.showBottomDialog(methods, - libraryViewModel.sortingMethods.indexOf(libraryViewModel.currentSortingMethod), - txt(R.string.sort_by).asString(view.context), - false, - {}, - { - val method = libraryViewModel.sortingMethods[it] - libraryViewModel.sort(method) - }) - } - - main_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { - override fun onQueryTextSubmit(query: String?): Boolean { - libraryViewModel.sort(ListSorting.Query, query) - return true - } - - // This is required to prevent the first text change - // When this is attached it'll immediately send a onQueryTextChange("") - // Which we do not want - var hasInitialized = false - override fun onQueryTextChange(newText: String?): Boolean { - if (!hasInitialized) { - hasInitialized = true - return true - } - - libraryViewModel.sort(ListSorting.Query, newText) - return true - } - }) - - libraryViewModel.reloadPages(false) - - list_selector?.setOnClickListener { - val items = libraryViewModel.availableApiNames - val currentItem = libraryViewModel.currentApiName.value - - activity?.showBottomDialog(items, - items.indexOf(currentItem), - txt(R.string.select_library).asString(it.context), - false, - {}) { index -> - val selectedItem = items.getOrNull(index) ?: return@showBottomDialog - libraryViewModel.switchList(selectedItem) - } - } - - - /** - * Shows a plugin selection dialogue and saves the response - **/ - fun Activity.showPluginSelectionDialog( - key: String, - syncId: SyncIdName, - apiName: String? = null, - ) { - val availableProviders = allProviders.filter { - it.supportedSyncNames.contains(syncId) - }.map { it.name } + - // Add the api if it exists - (APIHolder.getApiFromNameNull(apiName)?.let { listOf(it.name) } ?: emptyList()) - - val baseOptions = listOf( - LibraryOpenerType.Default, - LibraryOpenerType.None, - LibraryOpenerType.Browser, - LibraryOpenerType.Search - ) - - val items = baseOptions.map { txt(it.stringRes).asString(this) } + availableProviders - - val savedSelection = getKey(LIBRARY_FOLDER, key) - val selectedIndex = - when { - savedSelection == null -> 0 - // If provider - savedSelection.openType == LibraryOpenerType.Provider - && savedSelection.providerData?.apiName != null -> { - availableProviders.indexOf(savedSelection.providerData.apiName) - .takeIf { it != -1 } - ?.plus(baseOptions.size) ?: 0 - } - // Else base option - else -> baseOptions.indexOf(savedSelection.openType) - } - - this.showBottomDialog( - items, - selectedIndex, - txt(R.string.open_with).asString(this), - false, - {}, - ) { - val savedData = if (it < baseOptions.size) { - LibraryOpener( - baseOptions[it], - null - ) - } else { - LibraryOpener( - LibraryOpenerType.Provider, - ProviderLibraryData(items[it]) - ) - } - - setKey( - LIBRARY_FOLDER, - key, - savedData, - ) - } - } - - provider_selector?.setOnClickListener { - val syncName = libraryViewModel.currentSyncApi?.syncIdName ?: return@setOnClickListener - activity?.showPluginSelectionDialog(syncName.name, syncName) - } - - viewpager?.setPageTransformer(LibraryScrollTransformer()) - viewpager?.adapter = - viewpager.adapter ?: ViewpagerAdapter(mutableListOf(), { isScrollingDown: Boolean -> - if (isScrollingDown) { - sort_fab?.shrink() - } else { - sort_fab?.extend() - } - }) callback@{ searchClickCallback -> - // To prevent future accidents - debugAssert({ - searchClickCallback.card !is SyncAPI.LibraryItem - }, { - "searchClickCallback ${searchClickCallback.card} is not a LibraryItem" - }) - - val syncId = (searchClickCallback.card as SyncAPI.LibraryItem).syncId - val syncName = - libraryViewModel.currentSyncApi?.syncIdName ?: return@callback - - when (searchClickCallback.action) { - SEARCH_ACTION_SHOW_METADATA -> { - activity?.showPluginSelectionDialog( - syncId, - syncName, - searchClickCallback.card.apiName - ) - } - - SEARCH_ACTION_LOAD -> { - // This basically first selects the individual opener and if that is default then - // selects the whole list opener - val savedListSelection = - getKey(LIBRARY_FOLDER, syncName.name) - val savedSelection = getKey(LIBRARY_FOLDER, syncId).takeIf { - it?.openType != LibraryOpenerType.Default - } ?: savedListSelection - - when (savedSelection?.openType) { - null, LibraryOpenerType.Default -> { - // Prevents opening MAL/AniList as a provider - if (APIHolder.getApiFromNameNull(searchClickCallback.card.apiName) != null) { - activity?.loadSearchResult( - searchClickCallback.card - ) - } else { - // Search when no provider can open - QuickSearchFragment.pushSearch( - activity, - searchClickCallback.card.name - ) - } - } - LibraryOpenerType.None -> {} - LibraryOpenerType.Provider -> - savedSelection.providerData?.apiName?.let { apiName -> - activity?.loadResult( - searchClickCallback.card.url, - apiName, - ) - } - LibraryOpenerType.Browser -> - openBrowser(searchClickCallback.card.url) - LibraryOpenerType.Search -> { - QuickSearchFragment.pushSearch( - activity, - searchClickCallback.card.name - ) - } - } - } - } - } - - viewpager?.offscreenPageLimit = 2 - viewpager?.reduceDragSensitivity() - - val startLoading = Runnable { - gridview?.numColumns = context?.getSpanCount() ?: 3 - gridview?.adapter = - context?.let { LoadingPosterAdapter(it, 6 * 3) } - library_loading_overlay?.isVisible = true - library_loading_shimmer?.startShimmer() - empty_list_textview?.isVisible = false - } - - val stopLoading = Runnable { - gridview?.adapter = null - library_loading_overlay?.isVisible = false - library_loading_shimmer?.stopShimmer() - } - - val handler = Handler(Looper.getMainLooper()) - - observe(libraryViewModel.pages) { resource -> - when (resource) { - is Resource.Success -> { - handler.removeCallbacks(startLoading) - val pages = resource.value - val showNotice = pages.all { it.items.isEmpty() } - empty_list_textview?.isVisible = showNotice - if (showNotice) { - if (libraryViewModel.availableApiNames.size > 1) { - empty_list_textview?.setText(R.string.empty_library_logged_in_message) - } else { - empty_list_textview?.setText(R.string.empty_library_no_accounts_message) - } - } - - (viewpager.adapter as? ViewpagerAdapter)?.pages = pages - // Using notifyItemRangeChanged keeps the animations when sorting - viewpager.adapter?.notifyItemRangeChanged(0, viewpager.adapter?.itemCount ?: 0) - - // Only stop loading after 300ms to hide the fade effect the viewpager produces when updating - // Without this there would be a flashing effect: - // loading -> show old viewpager -> black screen -> show new viewpager - handler.postDelayed(stopLoading, 300) - - savedInstanceState?.getInt(VIEWPAGER_ITEM_KEY)?.let { currentPos -> - if (currentPos < 0) return@let - viewpager?.setCurrentItem(currentPos, false) - // Using remove() sets the key to 0 instead of removing it - savedInstanceState.putInt(VIEWPAGER_ITEM_KEY, -1) - } - - // Since the animation to scroll multiple items is so much its better to just hide - // the viewpager a bit while the fastest animation is running - fun hideViewpager(distance: Int) { - if (distance < 3) return - - val hideAnimation = AlphaAnimation(1f, 0f).apply { - duration = distance * 50L - fillAfter = true - } - val showAnimation = AlphaAnimation(0f, 1f).apply { - duration = distance * 50L - startOffset = distance * 100L - fillAfter = true - } - viewpager?.startAnimation(hideAnimation) - viewpager?.startAnimation(showAnimation) - } - - TabLayoutMediator( - library_tab_layout, - viewpager, - ) { tab, position -> - tab.text = pages.getOrNull(position)?.title?.asStringNull(context) - tab.view.setOnClickListener { - val currentItem = viewpager?.currentItem ?: return@setOnClickListener - val distance = abs(position - currentItem) - hideViewpager(distance) - } - }.attach() - } - is Resource.Loading -> { - // Only start loading after 200ms to prevent loading cached lists - handler.postDelayed(startLoading, 200) - } - is Resource.Failure -> { - stopLoading.run() - // No user indication it failed :( - // TODO - } - } - } - } - - override fun onConfigurationChanged(newConfig: Configuration) { - (viewpager.adapter as? ViewpagerAdapter)?.rebind() - super.onConfigurationChanged(newConfig) - } -} - -class MenuSearchView(context: Context) : SearchView(context) { - override fun onActionViewCollapsed() { - super.onActionViewCollapsed() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryScrollTransformer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryScrollTransformer.kt deleted file mode 100644 index 8aafbdd6..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryScrollTransformer.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.lagradost.cloudstream3.ui.library - -import android.view.View -import androidx.viewpager2.widget.ViewPager2 -import kotlinx.android.synthetic.main.library_viewpager_page.view.* -import kotlin.math.roundToInt - -class LibraryScrollTransformer : ViewPager2.PageTransformer { - override fun transformPage(page: View, position: Float) { - val padding = (-position * page.width).roundToInt() - page.page_recyclerview.setPadding( - padding, 0, - -padding, 0 - ) - } -} - diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt deleted file mode 100644 index 5f64880c..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.lagradost.cloudstream3.ui.library - -import androidx.annotation.StringRes -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.mvvm.Resource -import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis -import com.lagradost.cloudstream3.syncproviders.SyncAPI -import com.lagradost.cloudstream3.utils.Coroutines.ioSafe -import kotlinx.coroutines.delay - -enum class ListSorting(@StringRes val stringRes: Int) { - Query(R.string.none), - RatingHigh(R.string.sort_rating_desc), - RatingLow(R.string.sort_rating_asc), - UpdatedNew(R.string.sort_updated_new), - UpdatedOld(R.string.sort_updated_old), - AlphabeticalA(R.string.sort_alphabetical_a), - AlphabeticalZ(R.string.sort_alphabetical_z), -} - -const val LAST_SYNC_API_KEY = "last_sync_api" - -class LibraryViewModel : ViewModel() { - private val _pages: MutableLiveData>> = MutableLiveData(null) - val pages: LiveData>> = _pages - - private val _currentApiName: MutableLiveData = MutableLiveData("") - val currentApiName: LiveData = _currentApiName - - private val availableSyncApis - get() = SyncApis.filter { it.hasAccount() } - - var currentSyncApi = availableSyncApis.let { allApis -> - val lastSelection = getKey(LAST_SYNC_API_KEY) - availableSyncApis.firstOrNull { it.name == lastSelection } ?: allApis.firstOrNull() - } - private set(value) { - field = value - setKey(LAST_SYNC_API_KEY, field?.name) - } - - val availableApiNames: List - get() = availableSyncApis.map { it.name } - - var sortingMethods = emptyList() - private set - - var currentSortingMethod: ListSorting? = sortingMethods.firstOrNull() - private set - - fun switchList(name: String) { - currentSyncApi = availableSyncApis[availableApiNames.indexOf(name)] - _currentApiName.postValue(currentSyncApi?.name) - reloadPages(true) - } - - fun sort(method: ListSorting, query: String? = null) { - val currentList = pages.value ?: return - currentSortingMethod = method - (currentList as? Resource.Success)?.value?.forEachIndexed { _, page -> - page.sort(method, query) - } - _pages.postValue(currentList) - } - - fun reloadPages(forceReload: Boolean) { - // Only skip loading if its not forced and pages is not empty - if (!forceReload && (pages.value as? Resource.Success)?.value?.isNotEmpty() == true && - currentSyncApi?.requireLibraryRefresh != true - ) return - - ioSafe { - currentSyncApi?.let { repo -> - _currentApiName.postValue(repo.name) - _pages.postValue(Resource.Loading()) - val libraryResource = repo.getPersonalLibrary() - if (libraryResource is Resource.Failure) { - _pages.postValue(libraryResource) - return@let - } - val library = (libraryResource as? Resource.Success)?.value ?: return@let - - sortingMethods = library.supportedListSorting.toList() - currentSortingMethod = null - - repo.requireLibraryRefresh = false - - val pages = library.allLibraryLists.map { - SyncAPI.Page( - it.name, - it.items - ) - } - - _pages.postValue(Resource.Success(pages)) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LoadingPosterAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LoadingPosterAdapter.kt deleted file mode 100644 index a637133b..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LoadingPosterAdapter.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.lagradost.cloudstream3.ui.library - -import android.content.Context -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.BaseAdapter -import android.widget.FrameLayout -import android.widget.LinearLayout -import android.widget.ListPopupWindow.MATCH_PARENT -import android.widget.RelativeLayout -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.utils.UIHelper.toPx -import kotlinx.android.synthetic.main.loading_poster_dynamic.view.* -import kotlin.math.roundToInt -import kotlin.math.sqrt - -class LoadingPosterAdapter(context: Context, private val itemCount: Int) : - BaseAdapter() { - private val inflater: LayoutInflater = LayoutInflater.from(context) - - override fun getCount(): Int { - return itemCount - } - - override fun getItem(position: Int): Any? { - return null - } - - override fun getItemId(position: Int): Long { - return position.toLong() - } - - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - return convertView ?: inflater.inflate(R.layout.loading_poster_dynamic, parent, false) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt deleted file mode 100644 index 2435f8be..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/PageAdapter.kt +++ /dev/null @@ -1,130 +0,0 @@ -package com.lagradost.cloudstream3.ui.library - -import android.content.res.ColorStateList -import android.graphics.Color -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.ImageView -import androidx.core.content.ContextCompat -import androidx.core.graphics.ColorUtils -import androidx.core.view.isVisible -import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.AcraApplication -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.syncproviders.SyncAPI -import com.lagradost.cloudstream3.ui.AutofitRecyclerView -import com.lagradost.cloudstream3.ui.search.SearchClickCallback -import com.lagradost.cloudstream3.ui.search.SearchResultBuilder -import com.lagradost.cloudstream3.utils.AppUtils -import com.lagradost.cloudstream3.utils.UIHelper.toPx -import kotlinx.android.synthetic.main.search_result_grid_expanded.view.* -import kotlin.math.roundToInt - - -class PageAdapter( - override val items: MutableList, - private val resView: AutofitRecyclerView, - val clickCallback: (SearchClickCallback) -> Unit -) : - AppUtils.DiffAdapter(items) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return LibraryItemViewHolder( - LayoutInflater.from(parent.context) - .inflate(R.layout.search_result_grid_expanded, parent, false) - ) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (holder) { - is LibraryItemViewHolder -> { - holder.bind(items[position], position) - } - } - } - - private fun isDark(color: Int): Boolean { - return ColorUtils.calculateLuminance(color) < 0.5 - } - - fun getDifferentColor(color: Int, ratio: Float = 0.7f): Int { - return if (isDark(color)) { - ColorUtils.blendARGB(color, Color.WHITE, ratio) - } else { - ColorUtils.blendARGB(color, Color.BLACK, ratio) - } - } - - inner class LibraryItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - val cardView: ImageView = itemView.imageView - - private val compactView = false//itemView.context.getGridIsCompact() - private val coverHeight: Int = - if (compactView) 80.toPx else (resView.itemWidth / 0.68).roundToInt() - - fun bind(item: SyncAPI.LibraryItem, position: Int) { - /** https://stackoverflow.com/questions/8817522/how-to-get-color-code-of-image-view */ - - SearchResultBuilder.bind( - this@PageAdapter.clickCallback, - item, - position, - itemView, - colorCallback = { palette -> - AcraApplication.context?.let { ctx -> - val defColor = ContextCompat.getColor(ctx, R.color.ratingColorBg) - var bg = palette.getDarkVibrantColor(defColor) - if (bg == defColor) { - bg = palette.getDarkMutedColor(defColor) - } - if (bg == defColor) { - bg = palette.getVibrantColor(defColor) - } - - val fg = - getDifferentColor(bg)//palette.getVibrantColor(ContextCompat.getColor(ctx,R.color.ratingColor)) - itemView.text_rating.apply { - setTextColor(ColorStateList.valueOf(fg)) - } - itemView.text_rating_holder?.backgroundTintList = ColorStateList.valueOf(bg) - itemView.watchProgress?.apply { - progressTintList = ColorStateList.valueOf(fg) - progressBackgroundTintList = ColorStateList.valueOf(bg) - } - } - } - ) - - // See searchAdaptor for this, it basically fixes the height - if (!compactView) { - cardView.apply { - layoutParams = FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - coverHeight - ) - } - } - - val showProgress = item.episodesCompleted != null && item.episodesTotal != null - itemView.watchProgress.isVisible = showProgress - if (showProgress) { - itemView.watchProgress.max = item.episodesTotal!! - itemView.watchProgress.progress = item.episodesCompleted!! - } - - itemView.imageText.text = item.name - - val showRating = (item.personalRating ?: 0) != 0 - itemView.text_rating_holder.isVisible = showRating - if (showRating) { - // We want to show 8.5 but not 8.0 hence the replace - val rating = ((item.personalRating ?: 0).toDouble() / 10).toString() - .replace(".0", "") - - itemView.text_rating.text = "★ $rating" - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt deleted file mode 100644 index 33a40386..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.lagradost.cloudstream3.ui.library - -import android.os.Build -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.doOnAttach -import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.RecyclerView.OnFlingListener -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.syncproviders.SyncAPI -import com.lagradost.cloudstream3.ui.search.SearchClickCallback -import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount -import kotlinx.android.synthetic.main.library_viewpager_page.view.* - -class ViewpagerAdapter( - var pages: List, - val scrollCallback: (isScrollingDown: Boolean) -> Unit, - val clickCallback: (SearchClickCallback) -> Unit -) : RecyclerView.Adapter() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return PageViewHolder( - LayoutInflater.from(parent.context) - .inflate(R.layout.library_viewpager_page, parent, false) - ) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (holder) { - is PageViewHolder -> { - holder.bind(pages[position], unbound.remove(position)) - } - } - } - - private val unbound = mutableSetOf() - /** - * Used to mark all pages for re-binding and forces all items to be refreshed - * Without this the pages will still use the same adapters - **/ - fun rebind() { - unbound.addAll(0..pages.size) - this.notifyItemRangeChanged(0, pages.size) - } - - inner class PageViewHolder(private val itemViewTest: View) : - RecyclerView.ViewHolder(itemViewTest) { - fun bind(page: SyncAPI.Page, rebind: Boolean) { - itemView.page_recyclerview?.spanCount = - this@PageViewHolder.itemView.context.getSpanCount() ?: 3 - - if (itemViewTest.page_recyclerview?.adapter == null || rebind) { - // Only add the items after it has been attached since the items rely on ItemWidth - // Which is only determined after the recyclerview is attached. - // If this fails then item height becomes 0 when there is only one item - itemViewTest.page_recyclerview?.doOnAttach { - itemViewTest.page_recyclerview?.adapter = PageAdapter( - page.items.toMutableList(), - itemViewTest.page_recyclerview, - clickCallback - ) - } - } else { - (itemViewTest.page_recyclerview?.adapter as? PageAdapter)?.updateList(page.items) - itemViewTest.page_recyclerview?.scrollToPosition(0) - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - itemViewTest.page_recyclerview.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> - val diff = scrollY - oldScrollY - if (diff == 0) return@setOnScrollChangeListener - - scrollCallback.invoke(diff > 0) - } - } else { - itemViewTest.page_recyclerview.onFlingListener = object : OnFlingListener() { - override fun onFling(velocityX: Int, velocityY: Int): Boolean { - scrollCallback.invoke(velocityY > 0) - return false - } - } - } - - } - } - - override fun getItemCount(): Int { - return pages.size - } -} \ 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 e0885671..8eda6e30 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 @@ -8,12 +8,10 @@ import android.util.Log import android.widget.FrameLayout import androidx.preference.PreferenceManager import com.google.android.exoplayer2.* -import com.google.android.exoplayer2.C.* -import com.google.android.exoplayer2.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON -import com.google.android.exoplayer2.DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER +import com.google.android.exoplayer2.C.TRACK_TYPE_AUDIO +import com.google.android.exoplayer2.C.TRACK_TYPE_VIDEO import com.google.android.exoplayer2.database.StandaloneDatabaseProvider import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSource -import com.google.android.exoplayer2.mediacodec.MediaCodecSelector import com.google.android.exoplayer2.source.* import com.google.android.exoplayer2.text.TextRenderer import com.google.android.exoplayer2.trackselection.DefaultTrackSelector @@ -38,13 +36,11 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.utils.EpisodeSkip -import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList import com.lagradost.cloudstream3.utils.ExtractorUri import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage import java.io.File -import java.time.Duration import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext import javax.net.ssl.SSLSession @@ -93,10 +89,10 @@ class CS3IPlayer : IPlayer { /** * Tracks reported to be used by exoplayer, since sometimes it has a mind of it's own when selecting subs. - * String = id + * String = lowercase language as set by .setLanguage("_$langId") * Boolean = if it's active * */ - private var playerSelectedSubtitleTracks = listOf>() + private var exoPlayerSelectedTracks = listOf>() /** isPlaying */ private var updateIsPlaying: ((Pair) -> Unit)? = null @@ -315,18 +311,14 @@ class CS3IPlayer : IPlayer { * */ private fun List.getFormats(): List> { return this.map { - it.getFormats() + (0 until it.mediaTrackGroup.length).mapNotNull { i -> + if (it.isSupported) + it.mediaTrackGroup.getFormat(i) to i + else null + } }.flatten() } - private fun Tracks.Group.getFormats(): List> { - return (0 until this.mediaTrackGroup.length).mapNotNull { i -> - if (this.isSupported) - this.mediaTrackGroup.getFormat(i) to i - else null - } - } - private fun Format.toAudioTrack(): AudioTrack { return AudioTrack( this.id, @@ -369,17 +361,12 @@ class CS3IPlayer : IPlayer { override fun setPreferredSubtitles(subtitle: SubtitleData?): Boolean { Log.i(TAG, "setPreferredSubtitles init $subtitle") currentSubtitles = subtitle - - fun getTextTrack(id: String) = - exoPlayer?.currentTracks?.groups?.filter { it.type == TRACK_TYPE_TEXT } - ?.getTrack(id) - return (exoPlayer?.trackSelector as? DefaultTrackSelector?)?.let { trackSelector -> - if (subtitle == null) { + val name = subtitle?.name + if (name.isNullOrBlank()) { trackSelector.setParameters( trackSelector.buildUponParameters() .setPreferredTextLanguage(null) - .clearOverridesOfType(TRACK_TYPE_TEXT) ) } else { when (subtitleHelper.subtitleStatus(subtitle)) { @@ -393,15 +380,12 @@ class CS3IPlayer : IPlayer { trackSelector.setParameters( trackSelector.buildUponParameters() .apply { - val track = getTextTrack(subtitle.getId()) - if (track != null) { - setOverrideForType( - TrackSelectionOverride( - track.first, - track.second - ) - ) - } + if (subtitle.origin == SubtitleOrigin.EMBEDDED_IN_VIDEO) + // The real Language (two letter) is in the url + // No underscore as the .url is the actual exoplayer designated language + setPreferredTextLanguage(subtitle.url) + else + setPreferredTextLanguage("_$name") } ) @@ -435,8 +419,17 @@ class CS3IPlayer : IPlayer { override fun getCurrentPreferredSubtitle(): SubtitleData? { return subtitleHelper.getAllSubtitles().firstOrNull { sub -> - playerSelectedSubtitleTracks.any { (id, isSelected) -> - isSelected && sub.getId() == id + exoPlayerSelectedTracks.any { + // When embedded the real language is in .url as the real name is a two letter code + val realName = + if (sub.origin == SubtitleOrigin.EMBEDDED_IN_VIDEO) sub.url else sub.name + + // The replace is needed as exoplayer translates _ to - + // Also we prefix the languages with _ + it.second && it.first.replace("-", "").equals( + realName.replace("-", ""), + ignoreCase = true + ) } } } @@ -540,17 +533,15 @@ class CS3IPlayer : IPlayer { OkHttpDataSource.Factory(client).setUserAgent(USER_AGENT) } - // Do no include empty referer, if the provider wants those they can use the header map. - val refererMap = - if (link.referer.isBlank()) emptyMap() else mapOf("referer" to link.referer) val headers = mapOf( + "referer" to link.referer, "accept" to "*/*", "sec-ch-ua" to "\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\"", "sec-ch-ua-mobile" to "?0", "sec-fetch-user" to "?1", "sec-fetch-mode" to "navigate", "sec-fetch-dest" to "video" - ) + refererMap + link.headers // Adds the headers from the provider, e.g Authorization + ) + link.headers // Adds the headers from the provider, e.g Authorization return source.apply { setDefaultRequestProperties(headers) @@ -673,27 +664,23 @@ class CS3IPlayer : IPlayer { val exoPlayerBuilder = ExoPlayer.Builder(context) .setRenderersFactory { eventHandler, videoRendererEventListener, audioRendererEventListener, textRendererOutput, metadataRendererOutput -> - DefaultRenderersFactory(context).apply { -// setEnableDecoderFallback(true) - // Enable Ffmpeg extension -// setExtensionRendererMode(EXTENSION_RENDERER_MODE_ON) - }.createRenderers( - eventHandler, - videoRendererEventListener, - audioRendererEventListener, - textRendererOutput, - metadataRendererOutput - ).map { - if (it is TextRenderer) { - currentTextRenderer = CustomTextRenderer( - subtitleOffset, - textRendererOutput, - eventHandler.looper, - CustomSubtitleDecoderFactory() - ) - currentTextRenderer!! - } else it - }.toTypedArray() + DefaultRenderersFactory(context).createRenderers( + eventHandler, + videoRendererEventListener, + audioRendererEventListener, + textRendererOutput, + metadataRendererOutput + ).map { + if (it is TextRenderer) { + currentTextRenderer = CustomTextRenderer( + subtitleOffset, + textRendererOutput, + eventHandler.looper, + CustomSubtitleDecoderFactory() + ) + currentTextRenderer!! + } else it + }.toTypedArray() } .setTrackSelector( trackSelector ?: getTrackSelector( @@ -701,8 +688,6 @@ class CS3IPlayer : IPlayer { maxVideoHeight ) ) - // Allows any seeking to be +- 0.3s to allow for faster seeking - .setSeekParameters(SeekParameters(300_000, 300_000)) .setLoadControl( DefaultLoadControl.Builder() .setTargetBufferBytes( @@ -762,7 +747,7 @@ class CS3IPlayer : IPlayer { } } - private fun getCurrentTimestamp(writePosition: Long? = null): EpisodeSkip.SkipStamp? { + private fun getCurrentTimestamp(writePosition : Long? = null): EpisodeSkip.SkipStamp? { val position = writePosition ?: this@CS3IPlayer.getPosition() ?: return null for (lastTimeStamp in lastTimeStamps) { if (lastTimeStamp.startMs <= position && position < lastTimeStamp.endMs) { @@ -772,7 +757,7 @@ class CS3IPlayer : IPlayer { return null } - fun updatedTime(writePosition: Long? = null) { + fun updatedTime(writePosition : Long? = null) { getCurrentTimestamp(writePosition)?.let { timestamp -> onTimestampInvoked?.invoke(timestamp) } @@ -858,7 +843,7 @@ class CS3IPlayer : IPlayer { Log.i(TAG, "loadExo") val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) val maxVideoHeight = settingsManager.getInt( - context.getString(if (context.isUsingMobileData()) com.lagradost.cloudstream3.R.string.quality_pref_mobile_data_key else com.lagradost.cloudstream3.R.string.quality_pref_key), + context.getString(com.lagradost.cloudstream3.R.string.quality_pref_key), Int.MAX_VALUE ) @@ -898,36 +883,43 @@ class CS3IPlayer : IPlayer { } exoPlayer?.addListener(object : Player.Listener { override fun onTracksChanged(tracks: Tracks) { + fun Format.isSubtitle(): Boolean { + return this.sampleMimeType?.contains("video/") == false && + this.sampleMimeType?.contains("audio/") == false + } + normalSafeApiCall { - val textTracks = tracks.groups.filter { it.type == TRACK_TYPE_TEXT } + exoPlayerSelectedTracks = + tracks.groups.mapNotNull { + val format = it.mediaTrackGroup.getFormat(0) + if (format.isSubtitle()) + format.language?.let { lang -> lang to it.isSelected } + else null + } - playerSelectedSubtitleTracks = - textTracks.map { group -> - group.getFormats().mapNotNull { (format, _) -> - (format.id ?: return@mapNotNull null) to group.isSelected - } - }.flatten() - - val exoPlayerReportedTracks = - tracks.groups.filter { it.type == TRACK_TYPE_TEXT }.getFormats() - .mapNotNull { (format, _) -> - // Filter out non subs, already used subs and subs without languages - if (format.id == null || - format.language == null || - format.language?.startsWith("-") == true - ) return@mapNotNull null - - return@mapNotNull SubtitleData( - // Nicer looking displayed names - fromTwoLettersToLanguage(format.language!!) - ?: format.language!!, - // See setPreferredTextLanguage - format.id!!, - SubtitleOrigin.EMBEDDED_IN_VIDEO, - format.sampleMimeType ?: MimeTypes.APPLICATION_SUBRIP, - emptyMap() - ) - } + val exoPlayerReportedTracks = tracks.groups.mapNotNull { + // Filter out unsupported tracks + if (it.isSupported) + it.mediaTrackGroup.getFormat(0) + else + null + }.mapNotNull { + // Filter out non subs, already used subs and subs without languages + if (!it.isSubtitle() || + // Anything starting with - is not embedded + it.language?.startsWith("-") == true || + it.language == null + ) return@mapNotNull null + return@mapNotNull SubtitleData( + // Nicer looking displayed names + fromTwoLettersToLanguage(it.language!!) ?: it.language!!, + // See setPreferredTextLanguage + it.language!!, + SubtitleOrigin.EMBEDDED_IN_VIDEO, + it.sampleMimeType ?: MimeTypes.APPLICATION_SUBRIP, + emptyMap() + ) + } embeddedSubtitlesFetched?.invoke(exoPlayerReportedTracks) onTracksInfoChanged?.invoke() @@ -986,7 +978,7 @@ class CS3IPlayer : IPlayer { // This is to switch mirrors automatically if the stream has not been fetched, but // allow playing the buffer without internet as then the duration is fetched. if (error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED - && exoPlayer?.duration != TIME_UNSET + && exoPlayer?.duration != C.TIME_UNSET ) { exoPlayer?.prepare() } else { @@ -1145,15 +1137,14 @@ class CS3IPlayer : IPlayer { val subConfig = MediaItem.SubtitleConfiguration.Builder(Uri.parse(sub.url)) .setMimeType(sub.mimeType) .setLanguage("_${sub.name}") - .setId(sub.getId()) - .setSelectionFlags(SELECTION_FLAG_DEFAULT) + .setSelectionFlags(C.SELECTION_FLAG_DEFAULT) .build() when (sub.origin) { SubtitleOrigin.DOWNLOADED_FILE -> { if (offlineSourceFactory != null) { activeSubtitles.add(sub) SingleSampleMediaSource.Factory(offlineSourceFactory) - .createMediaSource(subConfig, TIME_UNSET) + .createMediaSource(subConfig, C.TIME_UNSET) } else { null } @@ -1165,7 +1156,7 @@ class CS3IPlayer : IPlayer { if (sub.headers.isNotEmpty()) this.setDefaultRequestProperties(sub.headers) }) - .createMediaSource(subConfig, TIME_UNSET) + .createMediaSource(subConfig, C.TIME_UNSET) } else { null } @@ -1174,7 +1165,7 @@ class CS3IPlayer : IPlayer { if (offlineSourceFactory != null) { activeSubtitles.add(sub) SingleSampleMediaSource.Factory(offlineSourceFactory) - .createMediaSource(subConfig, TIME_UNSET) + .createMediaSource(subConfig, C.TIME_UNSET) } else { null } @@ -1204,10 +1195,10 @@ class CS3IPlayer : IPlayer { HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.socketFactory) } - val mime = when { - link.isM3u8 -> MimeTypes.APPLICATION_M3U8 - link.isDash -> MimeTypes.APPLICATION_MPD - else -> MimeTypes.VIDEO_MP4 + val mime = if (link.isM3u8) { + MimeTypes.APPLICATION_M3U8 + } else { + MimeTypes.VIDEO_MP4 } val mediaItems = if (link is ExtractorLinkPlayList) { @@ -1257,4 +1248,4 @@ class CS3IPlayer : IPlayer { loadOfflinePlayer(context, it) } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt index 974a5d26..690d3706 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt @@ -4,16 +4,13 @@ import android.content.Context import android.util.Log import androidx.preference.PreferenceManager import com.google.android.exoplayer2.Format -import com.google.android.exoplayer2.text.* -import com.google.android.exoplayer2.text.cea.Cea608Decoder -import com.google.android.exoplayer2.text.cea.Cea708Decoder -import com.google.android.exoplayer2.text.dvb.DvbDecoder -import com.google.android.exoplayer2.text.pgs.PgsDecoder +import com.google.android.exoplayer2.text.SubtitleDecoder +import com.google.android.exoplayer2.text.SubtitleDecoderFactory +import com.google.android.exoplayer2.text.SubtitleInputBuffer +import com.google.android.exoplayer2.text.SubtitleOutputBuffer import com.google.android.exoplayer2.text.ssa.SsaDecoder import com.google.android.exoplayer2.text.subrip.SubripDecoder import com.google.android.exoplayer2.text.ttml.TtmlDecoder -import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder -import com.google.android.exoplayer2.text.webvtt.Mp4WebvttDecoder import com.google.android.exoplayer2.text.webvtt.WebvttDecoder import com.google.android.exoplayer2.util.MimeTypes import com.lagradost.cloudstream3.R @@ -22,11 +19,7 @@ import org.mozilla.universalchardet.UniversalDetector import java.nio.ByteBuffer import java.nio.charset.Charset -/** - * @param fallbackFormat used to create a decoder based on mimetype if the subtitle string is not - * enough to identify the subtitle format. - **/ -class CustomDecoder(private val fallbackFormat: Format?) : SubtitleDecoder { +class CustomDecoder : SubtitleDecoder { companion object { fun updateForcedEncoding(context: Context) { val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) @@ -146,7 +139,7 @@ class CustomDecoder(private val fallbackFormat: Format?) : SubtitleDecoder { val inputString = getStr(inputBuffer) if (realDecoder == null && !inputString.isNullOrBlank()) { var str: String = inputString - // this way we read the subtitle file and decide what decoder to use instead of relying fully on mimetype + // this way we read the subtitle file and decide what decoder to use instead of relying on mimetype Log.i(TAG, "Got data from queueInputBuffer") //https://github.com/LagradOst/CloudStream-2/blob/ddd774ee66810137ff7bd65dae70bcf3ba2d2489/CloudStreamForms/CloudStreamForms/Script/MainChrome.cs#L388 realDecoder = when { @@ -155,31 +148,8 @@ class CustomDecoder(private val fallbackFormat: Format?) : SubtitleDecoder { (str.startsWith( "[Script Info]", ignoreCase = true - ) || str.startsWith("Title:", ignoreCase = true)) -> SsaDecoder(fallbackFormat?.initializationData) + ) || str.startsWith("Title:", ignoreCase = true)) -> SsaDecoder() str.startsWith("1", ignoreCase = true) -> SubripDecoder() - fallbackFormat != null -> { - when (val mimeType = fallbackFormat.sampleMimeType) { - MimeTypes.TEXT_VTT -> WebvttDecoder() - MimeTypes.TEXT_SSA -> SsaDecoder(fallbackFormat.initializationData) - MimeTypes.APPLICATION_MP4VTT -> Mp4WebvttDecoder() - MimeTypes.APPLICATION_TTML -> TtmlDecoder() - MimeTypes.APPLICATION_SUBRIP -> SubripDecoder() - MimeTypes.APPLICATION_TX3G -> Tx3gDecoder(fallbackFormat.initializationData) - MimeTypes.APPLICATION_CEA608, MimeTypes.APPLICATION_MP4CEA608 -> Cea608Decoder( - mimeType, - fallbackFormat.accessibilityChannel, - Cea608Decoder.MIN_DATA_CHANNEL_TIMEOUT_MS - ) - MimeTypes.APPLICATION_CEA708 -> Cea708Decoder( - fallbackFormat.accessibilityChannel, - fallbackFormat.initializationData - ) - MimeTypes.APPLICATION_DVBSUBS -> DvbDecoder(fallbackFormat.initializationData) - MimeTypes.APPLICATION_PGS -> PgsDecoder() - MimeTypes.TEXT_EXOPLAYER_CUES -> ExoplayerCuesDecoder() - else -> null - } - } else -> null } Log.i( @@ -276,6 +246,28 @@ class CustomSubtitleDecoderFactory : SubtitleDecoderFactory { } override fun createDecoder(format: Format): SubtitleDecoder { - return CustomDecoder(format) + return CustomDecoder() + //return when (val mimeType = format.sampleMimeType) { + // MimeTypes.TEXT_VTT -> WebvttDecoder() + // MimeTypes.TEXT_SSA -> SsaDecoder(format.initializationData) + // MimeTypes.APPLICATION_MP4VTT -> Mp4WebvttDecoder() + // MimeTypes.APPLICATION_TTML -> TtmlDecoder() + // MimeTypes.APPLICATION_SUBRIP -> SubripDecoder() + // MimeTypes.APPLICATION_TX3G -> Tx3gDecoder(format.initializationData) + // MimeTypes.APPLICATION_CEA608, MimeTypes.APPLICATION_MP4CEA608 -> return Cea608Decoder( + // mimeType, + // format.accessibilityChannel, + // Cea608Decoder.MIN_DATA_CHANNEL_TIMEOUT_MS + // ) + // MimeTypes.APPLICATION_CEA708 -> Cea708Decoder( + // format.accessibilityChannel, + // format.initializationData + // ) + // MimeTypes.APPLICATION_DVBSUBS -> DvbDecoder(format.initializationData) + // MimeTypes.APPLICATION_PGS -> PgsDecoder() + // MimeTypes.TEXT_EXOPLAYER_CUES -> ExoplayerCuesDecoder() + // // Default WebVttDecoder + // else -> WebvttDecoder() + //} } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt index 6f40e145..dc1bbba3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt @@ -42,7 +42,7 @@ class DownloadedPlayerActivity : AppCompatActivity() { R.id.global_to_navigation_player, GeneratorPlayer.newInstance( LinkGenerator( listOf( - BasicLink(url) + url ) ) ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt deleted file mode 100644 index 7c19e97d..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.lagradost.cloudstream3.ui.player - -import com.lagradost.cloudstream3.utils.ExtractorLink -import com.lagradost.cloudstream3.utils.ExtractorUri - -class ExtractorLinkGenerator( - private val links: List, - private val subtitles: List, -) : IGenerator { - override val hasCache = false - - override fun getCurrentId(): Int? { - return null - } - - override fun hasNext(): Boolean { - return false - } - - override fun getAll(): List? { - return null - } - - override fun hasPrev(): Boolean { - return false - } - - override fun getCurrent(offset: Int): Any? { - return null - } - - override fun goto(index: Int) {} - - override fun next() {} - - override fun prev() {} - - override suspend fun generateLinks( - clearCache: Boolean, - isCasting: Boolean, - callback: (Pair) -> Unit, - subtitleCallback: (SubtitleData) -> Unit, - offset: Int - ): Boolean { - subtitles.forEach(subtitleCallback) - links.forEach { - callback.invoke(it to null) - } - - return true - } -} \ No newline at end of file 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 86e21fd6..0f9a6548 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 @@ -40,7 +40,6 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvidersIsActive import com.lagradost.cloudstream3.utils.Qualities -import com.lagradost.cloudstream3.utils.AppUtils.isUsingMobileData import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe @@ -84,7 +83,6 @@ const val HORIZONTAL_MULTIPLIER = 2.0f const val DOUBLE_TAB_MAXIMUM_HOLD_TIME = 200L const val DOUBLE_TAB_MINIMUM_TIME_BETWEEN = 200L // this also affects the UI show response time const val DOUBLE_TAB_PAUSE_PERCENTAGE = 0.15 // in both directions -private const val SUBTITLE_DELAY_BUNDLE_KEY = "subtitle_delay" // All the UI Logic for the player open class FullScreenPlayer : AbstractPlayerFragment() { @@ -111,8 +109,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { protected var currentPrefQuality = Qualities.P2160.value // preferred maximum quality, used for ppl w bad internet or on cell protected var fastForwardTime = 10000L - protected var androidTVInterfaceOffSeekTime = 10000L; - protected var androidTVInterfaceOnSeekTime = 30000L; protected var swipeHorizontalEnabled = false protected var swipeVerticalEnabled = false protected var playBackSpeedEnabled = false @@ -609,14 +605,13 @@ open class FullScreenPlayer : AbstractPlayerFragment() { player_top_holder?.isGone = isGone //player_episodes_button?.isVisible = !isGone && hasEpisodes player_video_title?.isGone = togglePlayerTitleGone -// player_video_title_rez?.isGone = isGone + player_video_title_rez?.isGone = isGone player_episode_filler?.isGone = isGone player_center_menu?.isGone = isGone player_lock?.isGone = !isShowing //player_media_route_button?.isClickable = !isGone player_go_back_holder?.isGone = isGone player_sources_btt?.isGone = isGone - player_skip_episode?.isClickable = !isGone } private fun updateLockUI() { @@ -1055,19 +1050,19 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } KeyEvent.KEYCODE_DPAD_LEFT -> { if (!isShowing && !isLocked) { - player.seekTime(-androidTVInterfaceOffSeekTime) + player.seekTime(-10000L) return true } else if (player_pause_play?.isFocused == true) { - player.seekTime(-androidTVInterfaceOnSeekTime) + player.seekTime(-30000L) return true } } KeyEvent.KEYCODE_DPAD_RIGHT -> { if (!isShowing && !isLocked) { - player.seekTime(androidTVInterfaceOffSeekTime) + player.seekTime(10000L) return true } else if (player_pause_play?.isFocused == true) { - player.seekTime(androidTVInterfaceOnSeekTime) + player.seekTime(30000L) return true } } @@ -1106,6 +1101,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } protected fun uiReset() { + isLocked = false isShowing = false // if nothing has loaded these buttons should not be visible @@ -1121,20 +1117,11 @@ open class FullScreenPlayer : AbstractPlayerFragment() { resetRewindText() } - override fun onSaveInstanceState(outState: Bundle) { - // As this is video specific it is better to not do any setKey/getKey - outState.putLong(SUBTITLE_DELAY_BUNDLE_KEY, subtitleDelay) - super.onSaveInstanceState(outState) - } - @SuppressLint("ClickableViewAccessibility") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // init variables setPlayBackSpeed(getKey(PLAYBACK_SPEED_KEY) ?: 1.0f) - savedInstanceState?.getLong(SUBTITLE_DELAY_BUNDLE_KEY)?.let { - subtitleDelay = it - } // handle tv controls playerEventListener = { eventType -> @@ -1220,13 +1207,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { settingsManager.getInt(ctx.getString(R.string.double_tap_seek_time_key), 10) .toLong() * 1000L - androidTVInterfaceOffSeekTime = - settingsManager.getInt(ctx.getString(R.string.android_tv_interface_off_seek_key), 10) - .toLong() * 1000L - androidTVInterfaceOnSeekTime = - settingsManager.getInt(ctx.getString(R.string.android_tv_interface_on_seek_key), 10) - .toLong() * 1000L - navigationBarHeight = ctx.getNavigationBarHeight() statusBarHeight = ctx.getStatusBarHeight() @@ -1257,8 +1237,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() { ctx.getString(R.string.double_tap_pause_enabled_key), false ) + currentPrefQuality = settingsManager.getInt( - ctx.getString(if (ctx.isUsingMobileData()) R.string.quality_pref_mobile_data_key else R.string.quality_pref_key), + ctx.getString(R.string.quality_pref_key), currentPrefQuality ) // useSystemBrightness = 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 46f2bca9..e1f1d99d 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 @@ -13,6 +13,7 @@ import android.view.View import android.view.ViewGroup import android.widget.* import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AlertDialog import androidx.core.animation.addListener import androidx.core.content.ContextCompat import androidx.core.view.isGone @@ -25,13 +26,19 @@ import com.hippo.unifile.UniFile import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.CommonActivity.showToast -import com.lagradost.cloudstream3.mvvm.* +import com.lagradost.cloudstream3.mvvm.Resource +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subtitleProviders import com.lagradost.cloudstream3.ui.player.CS3IPlayer.Companion.preferredAudioTrackLanguage import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.updateForcedEncoding import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType -import com.lagradost.cloudstream3.ui.result.* +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.ResultFragment +import com.lagradost.cloudstream3.ui.result.SyncViewModel +import com.lagradost.cloudstream3.ui.result.setText import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1 import com.lagradost.cloudstream3.utils.* @@ -54,9 +61,6 @@ import kotlinx.android.synthetic.main.player_select_source_and_subs.* import kotlinx.android.synthetic.main.player_select_source_and_subs.subtitles_click_settings import kotlinx.android.synthetic.main.player_select_tracks.* import kotlinx.coroutines.Job -import java.util.* -import kotlin.collections.ArrayList -import kotlin.collections.HashMap class GeneratorPlayer : FullScreenPlayer() { companion object { @@ -162,8 +166,7 @@ class GeneratorPlayer : FullScreenPlayer() { isActive = true setPlayerDimen(null) setTitle() - if (!sameEpisode) - hasRequestedStamps = false + hasRequestedStamps = false loadExtractorJob(link.first) // load player @@ -183,9 +186,7 @@ class GeneratorPlayer : FullScreenPlayer() { ), ) } - - if (!sameEpisode) - player.addTimeStamps(listOf()) // clear stamps + player.addTimeStamps(listOf()) // clear stamps } private fun sortLinks(useQualitySettings: Boolean = true): List> { @@ -326,54 +327,17 @@ class GeneratorPlayer : FullScreenPlayer() { dialog.search_loading_bar.progressTintList = color dialog.search_loading_bar.indeterminateTintList = color - observeNullable(viewModel.currentSubtitleYear) { - // When year is changed search again - dialog.subtitles_search.setQuery(dialog.subtitles_search.query, true) - dialog.year_btt.text = it?.toString() ?: txt(R.string.none).asString(context) - } - - dialog.year_btt?.setOnClickListener { - val none = txt(R.string.none).asString(context) - val currentYear = Calendar.getInstance().get(Calendar.YEAR) - val earliestYear = 1900 - - val years = (currentYear downTo earliestYear).toList() - val options = listOf(none) + years.map { - it.toString() - } - - val selectedIndex = viewModel.currentSubtitleYear.value - ?.let { - // + 1 since none also takes a space - years.indexOf(it) + 1 - } - ?.takeIf { it >= 0 } ?: 0 - - activity?.showDialog( - options, - selectedIndex, - txt(R.string.year).asString(context), - true, { - }, { index -> - viewModel.setSubtitleYear(years.getOrNull(index - 1)) - } - ) - } - dialog.subtitles_search.setOnQueryTextListener(object : androidx.appcompat.widget.SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { dialog.search_loading_bar?.show() ioSafe { val search = - AbstractSubtitleEntities.SubtitleSearch( - query = query ?: return@ioSafe, + AbstractSubtitleEntities.SubtitleSearch(query = query ?: return@ioSafe, imdb = imdbId, epNumber = currentTempMeta.episode, seasonNumber = currentTempMeta.season, - lang = currentLanguageTwoLetters.ifBlank { null }, - year = viewModel.currentSubtitleYear.value - ) + lang = currentLanguageTwoLetters.ifBlank { null }) val results = providers.amap { try { it.search(search) @@ -381,7 +345,7 @@ class GeneratorPlayer : FullScreenPlayer() { null } }.filterNotNull() - val max = results.maxOfOrNull { it.size } ?: return@ioSafe + val max = results.map { it.size }.maxOrNull() ?: return@ioSafe // very ugly val items = ArrayList() @@ -447,8 +411,6 @@ class GeneratorPlayer : FullScreenPlayer() { dialog.show() dialog.subtitles_search.setQuery(currentTempMeta.name, true) - //TODO: Set year text from currently loaded movie on Player - //dialog.subtitles_search_year?.setText(currentTempMeta.year) } private fun openSubPicker() { @@ -473,17 +435,16 @@ class GeneratorPlayer : FullScreenPlayer() { private fun addAndSelectSubtitles(subtitleData: SubtitleData) { val ctx = context ?: return + setSubtitles(subtitleData) + // this is used instead of observe, because observe is too slow val subs = currentSubs + subtitleData - // this is used instead of observe(viewModel._currentSubs), because observe is too slow - player.setActiveSubtitles(subs) - // Save current time as to not reset player to 00:00 player.saveData() + player.setActiveSubtitles(subs) player.reloadPlayer(ctx) - setSubtitles(subtitleData) viewModel.addSubtitles(setOf(subtitleData)) selectSourceDialog?.dismissSafe() @@ -525,7 +486,7 @@ class GeneratorPlayer : FullScreenPlayer() { } } - var selectSourceDialog: Dialog? = null + var selectSourceDialog: AlertDialog? = null // var selectTracksDialog: AlertDialog? = null override fun showMirrorsDialogue() { @@ -537,8 +498,10 @@ class GeneratorPlayer : FullScreenPlayer() { player.handleEvent(CSPlayerEvent.Pause) val currentSubtitles = sortSubs(currentSubs) - val sourceDialog = Dialog(ctx, R.style.AlertDialogCustomBlack) - sourceDialog.setContentView(R.layout.player_select_source_and_subs) + val sourceBuilder = AlertDialog.Builder(ctx, R.style.AlertDialogCustomBlack) + .setView(R.layout.player_select_source_and_subs) + + val sourceDialog = sourceBuilder.create() selectSourceDialog = sourceDialog @@ -733,17 +696,19 @@ class GeneratorPlayer : FullScreenPlayer() { } val currentAudioTracks = tracks.allAudioTracks - val trackDialog = Dialog(ctx, R.style.AlertDialogCustomBlack) - trackDialog.setContentView(R.layout.player_select_tracks) - trackDialog.show() + val trackBuilder = AlertDialog.Builder(ctx, R.style.AlertDialogCustomBlack) + .setView(R.layout.player_select_tracks) + + val tracksDialog = trackBuilder.create() // selectTracksDialog = tracksDialog - val videosList = trackDialog.video_tracks_list - val audioList = trackDialog.auto_tracks_list + tracksDialog.show() + val videosList = tracksDialog.video_tracks_list + val audioList = tracksDialog.auto_tracks_list - trackDialog.video_tracks_holder.isVisible = currentVideoTracks.size > 1 - trackDialog.audio_tracks_holder.isVisible = currentAudioTracks.size > 1 + tracksDialog.video_tracks_holder.isVisible = currentVideoTracks.size > 1 + tracksDialog.audio_tracks_holder.isVisible = currentAudioTracks.size > 1 fun dismiss() { if (isPlaying) { @@ -778,7 +743,7 @@ class GeneratorPlayer : FullScreenPlayer() { videosList.setItemChecked(which, true) } - trackDialog.setOnDismissListener { + tracksDialog.setOnDismissListener { dismiss() // selectTracksDialog = null } @@ -808,11 +773,11 @@ class GeneratorPlayer : FullScreenPlayer() { audioList.setItemChecked(which, true) } - trackDialog.cancel_btt?.setOnClickListener { - trackDialog.dismissSafe(activity) + tracksDialog.cancel_btt?.setOnClickListener { + tracksDialog.dismissSafe(activity) } - trackDialog.apply_btt?.setOnClickListener { + tracksDialog.apply_btt?.setOnClickListener { val currentTrack = currentAudioTracks.getOrNull(audioIndexStart) player.setPreferredAudioTrack( currentTrack?.language, currentTrack?.id @@ -825,7 +790,7 @@ class GeneratorPlayer : FullScreenPlayer() { player.setMaxVideoSize(width, height, currentVideo?.id) } - trackDialog.dismissSafe(activity) + tracksDialog.dismissSafe(activity) } } } catch (e: Exception) { @@ -907,15 +872,7 @@ class GeneratorPlayer : FullScreenPlayer() { if (duration <= 0L) return // idk how you achieved this, but div by zero crash if (!hasRequestedStamps) { hasRequestedStamps = true - val fetchStamps = context?.let { ctx -> - val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) - settingsManager.getBoolean( - ctx.getString(R.string.enable_skip_op_from_database), - true - ) - } ?: true - if (fetchStamps) - viewModel.loadStamps(duration) + viewModel.loadStamps(duration) } viewModel.getId()?.let { @@ -991,7 +948,7 @@ class GeneratorPlayer : FullScreenPlayer() { subtitles: Set, settings: Boolean, downloads: Boolean ): SubtitleData? { val langCode = preferredAutoSelectSubtitles ?: return null - val lang = fromTwoLettersToLanguage(langCode) ?: return null + val lang = SubtitleHelper.fromTwoLettersToLanguage(langCode) ?: return null if (downloads) { return subtitles.firstOrNull { sub -> (sub.origin == SubtitleOrigin.DOWNLOADED_FILE && sub.name == context?.getString( @@ -1002,11 +959,22 @@ class GeneratorPlayer : FullScreenPlayer() { sortSubs(subtitles).firstOrNull { sub -> val t = sub.name.replace(Regex("[^A-Za-z]"), " ").trim() - (settings) && t == lang || t.startsWith(lang) || t == langCode + (settings || (downloads && sub.origin == SubtitleOrigin.DOWNLOADED_FILE)) && t == lang || t.startsWith( + "$lang " + ) || t == langCode }?.let { sub -> return sub } + // post check in case both did not catch anything + if (downloads) { + return subtitles.firstOrNull { sub -> + (sub.origin == SubtitleOrigin.DOWNLOADED_FILE || sub.name == context?.getString( + R.string.default_subtitles + )) + } + } + return null } @@ -1027,12 +995,14 @@ class GeneratorPlayer : FullScreenPlayer() { getAutoSelectSubtitle( currentSubs, settings = true, downloads = false )?.let { sub -> + if (setSubtitles(sub)) { player.saveData() player.reloadPlayer(ctx) player.handleEvent(CSPlayerEvent.Play) return true } + } } } @@ -1142,15 +1112,13 @@ class GeneratorPlayer : FullScreenPlayer() { val source = currentSelectedLink?.first?.name ?: currentSelectedLink?.second?.name ?: "NULL" - val title = when (titleRez) { + player_video_title_rez?.text = when (titleRez) { 0 -> "" 1 -> extra 2 -> source 3 -> "$source - $extra" else -> "" } - player_video_title_rez?.text = title - player_video_title_rez?.isVisible = title.isNotBlank() } override fun playerDimensionsLoaded(widthHeight: Pair) { @@ -1325,10 +1293,8 @@ class GeneratorPlayer : FullScreenPlayer() { Log.i("subfilter", "Filtering subtitle") langFilterList.forEach { lang -> Log.i("subfilter", "Lang: $lang") - setOfSub += set.filter { - it.name.contains(lang, ignoreCase = true) || - it.origin != SubtitleOrigin.URL - } + setOfSub += set.filter { it.name.contains(lang, ignoreCase = true) } + .toMutableSet() } currentSubs = setOfSub } else { @@ -1336,13 +1302,7 @@ class GeneratorPlayer : FullScreenPlayer() { } player.setActiveSubtitles(set) - // If the file is downloaded then do not select auto select the subtitles - // Downloaded subtitles cannot be selected immediately after loading since - // player.getCurrentPreferredSubtitle() cannot fetch data from non-loaded subtitles - // Resulting in unselecting the downloaded subtitle - if (set.lastOrNull()?.origin != SubtitleOrigin.DOWNLOADED_FILE) { - autoSelectSubtitles() - } + autoSelectSubtitles() } } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt index 0b560857..1f242481 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt @@ -5,15 +5,8 @@ import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.utils.* import java.net.URI -/** - * Used to open the player more easily with the LinkGenerator - **/ -data class BasicLink( - val url: String, - val name: String? = null, -) class LinkGenerator( - private val links: List, + private val links: List, private val extract: Boolean = true, private val referer: String? = null, private val isM3u8: Boolean? = null @@ -54,7 +47,7 @@ class LinkGenerator( offset: Int ): Boolean { links.amap { link -> - if (!extract || !loadExtractor(link.url, referer, { + if (!extract || !loadExtractor(link, referer, { subtitleCallback(PlayerSubtitleHelper.getSubtitleData(it)) }) { callback(it to null) @@ -64,11 +57,11 @@ class LinkGenerator( callback( ExtractorLink( "", - link.name ?: link.url, - unshortenLinkSafe(link.url), // unshorten because it might be a raw link + link, + unshortenLinkSafe(link), // unshorten because it might be a raw link referer ?: "", Qualities.Unknown.value, isM3u8 ?: normalSafeApiCall { - URI(link.url).path?.substringAfterLast(".")?.contains("m3u") + URI(link).path?.substringAfterLast(".")?.contains("m3u") } ?: false ) to null ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/NonFinalTextRenderer.java b/app/src/main/java/com/lagradost/cloudstream3/ui/player/NonFinalTextRenderer.java index 3b47b27a..8602ce25 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/NonFinalTextRenderer.java +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/NonFinalTextRenderer.java @@ -15,8 +15,6 @@ */ package com.lagradost.cloudstream3.ui.player; -import static com.google.android.exoplayer2.text.Cue.DIMEN_UNSET; -import static com.google.android.exoplayer2.text.Cue.LINE_TYPE_NUMBER; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkState; import static java.lang.annotation.ElementType.TYPE_USE; @@ -25,10 +23,8 @@ import android.os.Handler; import android.os.Handler.Callback; import android.os.Looper; import android.os.Message; - import androidx.annotation.IntDef; import androidx.annotation.Nullable; - import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -47,17 +43,13 @@ import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; - import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; // DO NOT CONVERT TO KOTLIN AUTOMATICALLY, IT FUCKS UP AND DOES NOT DISPLAY SUBS FOR SOME REASON -// IF YOU CHANGE THE CODE MAKE SURE YOU GET THE CUES CORRECT! - /** * A renderer for text. * @@ -67,390 +59,365 @@ import java.util.stream.Collectors; */ public class NonFinalTextRenderer extends BaseRenderer implements Callback { - private static final String TAG = "TextRenderer"; + private static final String TAG = "TextRenderer"; - /** - * @param trackType The track type that the renderer handles. One of the {@link C} {@code - * TRACK_TYPE_*} constants. - * @param outputHandler - */ - public NonFinalTextRenderer(int trackType, @Nullable Handler outputHandler) { - super(trackType); - this.outputHandler = outputHandler; + /** + * @param trackType The track type that the renderer handles. One of the {@link C} {@code + * TRACK_TYPE_*} constants. + * @param outputHandler + */ + public NonFinalTextRenderer(int trackType, @Nullable Handler outputHandler) { + super(trackType); + this.outputHandler = outputHandler; + } + + @Documented + @Retention(RetentionPolicy.SOURCE) + @Target(TYPE_USE) + @IntDef({ + REPLACEMENT_STATE_NONE, + REPLACEMENT_STATE_SIGNAL_END_OF_STREAM, + REPLACEMENT_STATE_WAIT_END_OF_STREAM + }) + private @interface ReplacementState {} + /** The decoder does not need to be replaced. */ + private static final int REPLACEMENT_STATE_NONE = 0; + /** + * The decoder needs to be replaced, but we haven't yet signaled an end of stream to the existing + * decoder. We need to do so in order to ensure that it outputs any remaining buffers before we + * release it. + */ + private static final int REPLACEMENT_STATE_SIGNAL_END_OF_STREAM = 1; + /** + * The decoder needs to be replaced, and we've signaled an end of stream to the existing decoder. + * We're waiting for the decoder to output an end of stream signal to indicate that it has output + * any remaining buffers before we release it. + */ + private static final int REPLACEMENT_STATE_WAIT_END_OF_STREAM = 2; + + private static final int MSG_UPDATE_OUTPUT = 0; + + @Nullable private final Handler outputHandler; + private TextOutput output = null; + private SubtitleDecoderFactory decoderFactory = null; + private FormatHolder formatHolder = null; + + private boolean inputStreamEnded; + private boolean outputStreamEnded; + private boolean waitingForKeyFrame; + private @ReplacementState int decoderReplacementState; + @Nullable private Format streamFormat; + @Nullable private SubtitleDecoder decoder; + @Nullable private SubtitleInputBuffer nextInputBuffer; + @Nullable private SubtitleOutputBuffer subtitle; + @Nullable private SubtitleOutputBuffer nextSubtitle; + private int nextSubtitleEventIndex; + private long finalStreamEndPositionUs; + + /** + * @param output The output. + * @param outputLooper The looper associated with the thread on which the output should be called. + * If the output makes use of standard Android UI components, then this should normally be the + * looper associated with the application's main thread, which can be obtained using {@link + * android.app.Activity#getMainLooper()}. Null may be passed if the output should be called + * directly on the player's internal rendering thread. + */ + public NonFinalTextRenderer(TextOutput output, @Nullable Looper outputLooper) { + this(output, outputLooper, SubtitleDecoderFactory.DEFAULT); + } + + /** + * @param output The output. + * @param outputLooper The looper associated with the thread on which the output should be called. + * If the output makes use of standard Android UI components, then this should normally be the + * looper associated with the application's main thread, which can be obtained using {@link + * android.app.Activity#getMainLooper()}. Null may be passed if the output should be called + * directly on the player's internal rendering thread. + * @param decoderFactory A factory from which to obtain {@link SubtitleDecoder} instances. + */ + public NonFinalTextRenderer( + TextOutput output, @Nullable Looper outputLooper, SubtitleDecoderFactory decoderFactory) { + super(C.TRACK_TYPE_TEXT); + this.output = checkNotNull(output); + this.outputHandler = + outputLooper == null ? null : Util.createHandler(outputLooper, /* callback= */ this); + this.decoderFactory = decoderFactory; + formatHolder = new FormatHolder(); + finalStreamEndPositionUs = C.TIME_UNSET; + } + + @Override + public String getName() { + return TAG; + } + + @Override + public @Capabilities int supportsFormat(Format format) { + if (decoderFactory.supportsFormat(format)) { + return RendererCapabilities.create( + format.cryptoType == C.CRYPTO_TYPE_NONE ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_DRM); + } else if (MimeTypes.isText(format.sampleMimeType)) { + return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); + } else { + return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE); + } + } + + /** + * Sets the position at which to stop rendering the current stream. + * + *

Must be called after {@link #setCurrentStreamFinal()}. + * + * @param streamEndPositionUs The position to stop rendering at or {@link C#LENGTH_UNSET} to + * render until the end of the current stream. + */ + // TODO(internal b/181312195): Remove this when it's no longer needed once subtitles are decoded + // on the loading side of SampleQueue. + public void setFinalStreamEndPositionUs(long streamEndPositionUs) { + checkState(isCurrentStreamFinal()); + this.finalStreamEndPositionUs = streamEndPositionUs; + } + + @Override + protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) { + streamFormat = formats[0]; + if (decoder != null) { + decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM; + } else { + initDecoder(); + } + } + + @Override + protected void onPositionReset(long positionUs, boolean joining) { + clearOutput(); + inputStreamEnded = false; + outputStreamEnded = false; + finalStreamEndPositionUs = C.TIME_UNSET; + if (decoderReplacementState != REPLACEMENT_STATE_NONE) { + replaceDecoder(); + } else { + releaseBuffers(); + checkNotNull(decoder).flush(); + } + } + + @Override + public void render(long positionUs, long elapsedRealtimeUs) { + if (isCurrentStreamFinal() + && finalStreamEndPositionUs != C.TIME_UNSET + && positionUs >= finalStreamEndPositionUs) { + releaseBuffers(); + outputStreamEnded = true; } - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - REPLACEMENT_STATE_NONE, - REPLACEMENT_STATE_SIGNAL_END_OF_STREAM, - REPLACEMENT_STATE_WAIT_END_OF_STREAM - }) - private @interface ReplacementState { + if (outputStreamEnded) { + return; } - /** - * The decoder does not need to be replaced. - */ - private static final int REPLACEMENT_STATE_NONE = 0; - /** - * The decoder needs to be replaced, but we haven't yet signaled an end of stream to the existing - * decoder. We need to do so in order to ensure that it outputs any remaining buffers before we - * release it. - */ - private static final int REPLACEMENT_STATE_SIGNAL_END_OF_STREAM = 1; - /** - * The decoder needs to be replaced, and we've signaled an end of stream to the existing decoder. - * We're waiting for the decoder to output an end of stream signal to indicate that it has output - * any remaining buffers before we release it. - */ - private static final int REPLACEMENT_STATE_WAIT_END_OF_STREAM = 2; - - private static final int MSG_UPDATE_OUTPUT = 0; - - @Nullable - private final Handler outputHandler; - private TextOutput output = null; - private SubtitleDecoderFactory decoderFactory = null; - private FormatHolder formatHolder = null; - - private boolean inputStreamEnded; - private boolean outputStreamEnded; - private boolean waitingForKeyFrame; - private @ReplacementState int decoderReplacementState; - @Nullable - private Format streamFormat; - @Nullable - private SubtitleDecoder decoder; - @Nullable - private SubtitleInputBuffer nextInputBuffer; - @Nullable - private SubtitleOutputBuffer subtitle; - @Nullable - private SubtitleOutputBuffer nextSubtitle; - private int nextSubtitleEventIndex; - private long finalStreamEndPositionUs; - - /** - * @param output The output. - * @param outputLooper The looper associated with the thread on which the output should be called. - * If the output makes use of standard Android UI components, then this should normally be the - * looper associated with the application's main thread, which can be obtained using {@link - * android.app.Activity#getMainLooper()}. Null may be passed if the output should be called - * directly on the player's internal rendering thread. - */ - public NonFinalTextRenderer(TextOutput output, @Nullable Looper outputLooper) { - this(output, outputLooper, SubtitleDecoderFactory.DEFAULT); + if (nextSubtitle == null) { + checkNotNull(decoder).setPositionUs(positionUs); + try { + nextSubtitle = checkNotNull(decoder).dequeueOutputBuffer(); + } catch (SubtitleDecoderException e) { + handleDecoderError(e); + return; + } } - /** - * @param output The output. - * @param outputLooper The looper associated with the thread on which the output should be called. - * If the output makes use of standard Android UI components, then this should normally be the - * looper associated with the application's main thread, which can be obtained using {@link - * android.app.Activity#getMainLooper()}. Null may be passed if the output should be called - * directly on the player's internal rendering thread. - * @param decoderFactory A factory from which to obtain {@link SubtitleDecoder} instances. - */ - public NonFinalTextRenderer( - TextOutput output, @Nullable Looper outputLooper, SubtitleDecoderFactory decoderFactory) { - super(C.TRACK_TYPE_TEXT); - this.output = checkNotNull(output); - this.outputHandler = - outputLooper == null ? null : Util.createHandler(outputLooper, /* callback= */ this); - this.decoderFactory = decoderFactory; - formatHolder = new FormatHolder(); - finalStreamEndPositionUs = C.TIME_UNSET; + if (getState() != STATE_STARTED) { + return; } - @Override - public String getName() { - return TAG; + boolean textRendererNeedsUpdate = false; + if (subtitle != null) { + // We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we + // advance to the next event. + long subtitleNextEventTimeUs = getNextEventTime(); + while (subtitleNextEventTimeUs <= positionUs) { + nextSubtitleEventIndex++; + subtitleNextEventTimeUs = getNextEventTime(); + textRendererNeedsUpdate = true; + } } - - @Override - public @Capabilities int supportsFormat(Format format) { - if (decoderFactory.supportsFormat(format)) { - return RendererCapabilities.create( - format.cryptoType == C.CRYPTO_TYPE_NONE ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_DRM); - } else if (MimeTypes.isText(format.sampleMimeType)) { - return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); - } else { - return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE); - } - } - - /** - * Sets the position at which to stop rendering the current stream. - * - *

Must be called after {@link #setCurrentStreamFinal()}. - * - * @param streamEndPositionUs The position to stop rendering at or {@link C#LENGTH_UNSET} to - * render until the end of the current stream. - */ - // TODO(internal b/181312195): Remove this when it's no longer needed once subtitles are decoded - // on the loading side of SampleQueue. - public void setFinalStreamEndPositionUs(long streamEndPositionUs) { - checkState(isCurrentStreamFinal()); - this.finalStreamEndPositionUs = streamEndPositionUs; - } - - @Override - protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) { - streamFormat = formats[0]; - if (decoder != null) { - decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM; - } else { - initDecoder(); - } - } - - @Override - protected void onPositionReset(long positionUs, boolean joining) { - clearOutput(); - inputStreamEnded = false; - outputStreamEnded = false; - finalStreamEndPositionUs = C.TIME_UNSET; - if (decoderReplacementState != REPLACEMENT_STATE_NONE) { + if (nextSubtitle != null) { + SubtitleOutputBuffer nextSubtitle = this.nextSubtitle; + if (nextSubtitle.isEndOfStream()) { + if (!textRendererNeedsUpdate && getNextEventTime() == Long.MAX_VALUE) { + if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) { replaceDecoder(); - } else { - releaseBuffers(); - checkNotNull(decoder).flush(); - } - } - - @Override - public void render(long positionUs, long elapsedRealtimeUs) { - if (isCurrentStreamFinal() - && finalStreamEndPositionUs != C.TIME_UNSET - && positionUs >= finalStreamEndPositionUs) { + } else { releaseBuffers(); outputStreamEnded = true; + } } - - if (outputStreamEnded) { - return; - } - - if (nextSubtitle == null) { - checkNotNull(decoder).setPositionUs(positionUs); - try { - nextSubtitle = checkNotNull(decoder).dequeueOutputBuffer(); - } catch (SubtitleDecoderException e) { - handleDecoderError(e); - return; - } - } - - if (getState() != STATE_STARTED) { - return; - } - - boolean textRendererNeedsUpdate = false; + } else if (nextSubtitle.timeUs <= positionUs) { + // Advance to the next subtitle. Sync the next event index and trigger an update. if (subtitle != null) { - // We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we - // advance to the next event. - long subtitleNextEventTimeUs = getNextEventTime(); - while (subtitleNextEventTimeUs <= positionUs) { - nextSubtitleEventIndex++; - subtitleNextEventTimeUs = getNextEventTime(); - textRendererNeedsUpdate = true; - } - } - if (nextSubtitle != null) { - SubtitleOutputBuffer nextSubtitle = this.nextSubtitle; - if (nextSubtitle.isEndOfStream()) { - if (!textRendererNeedsUpdate && getNextEventTime() == Long.MAX_VALUE) { - if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) { - replaceDecoder(); - } else { - releaseBuffers(); - outputStreamEnded = true; - } - } - } else if (nextSubtitle.timeUs <= positionUs) { - // Advance to the next subtitle. Sync the next event index and trigger an update. - if (subtitle != null) { - subtitle.release(); - } - nextSubtitleEventIndex = nextSubtitle.getNextEventTimeIndex(positionUs); - subtitle = nextSubtitle; - this.nextSubtitle = null; - textRendererNeedsUpdate = true; - } + subtitle.release(); } + nextSubtitleEventIndex = nextSubtitle.getNextEventTimeIndex(positionUs); + subtitle = nextSubtitle; + this.nextSubtitle = null; + textRendererNeedsUpdate = true; + } + } - if (textRendererNeedsUpdate) { - // If textRendererNeedsUpdate then subtitle must be non-null. - checkNotNull(subtitle); - // textRendererNeedsUpdate is set and we're playing. Update the renderer. - updateOutput(subtitle.getCues(positionUs)); - } + if (textRendererNeedsUpdate) { + // If textRendererNeedsUpdate then subtitle must be non-null. + checkNotNull(subtitle); + // textRendererNeedsUpdate is set and we're playing. Update the renderer. + updateOutput(subtitle.getCues(positionUs)); + } - if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) { + if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) { + return; + } + + try { + while (!inputStreamEnded) { + @Nullable SubtitleInputBuffer nextInputBuffer = this.nextInputBuffer; + if (nextInputBuffer == null) { + nextInputBuffer = checkNotNull(decoder).dequeueInputBuffer(); + if (nextInputBuffer == null) { return; + } + this.nextInputBuffer = nextInputBuffer; } - - try { - while (!inputStreamEnded) { - @Nullable SubtitleInputBuffer nextInputBuffer = this.nextInputBuffer; - if (nextInputBuffer == null) { - nextInputBuffer = checkNotNull(decoder).dequeueInputBuffer(); - if (nextInputBuffer == null) { - return; - } - this.nextInputBuffer = nextInputBuffer; - } - if (decoderReplacementState == REPLACEMENT_STATE_SIGNAL_END_OF_STREAM) { - nextInputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); - checkNotNull(decoder).queueInputBuffer(nextInputBuffer); - this.nextInputBuffer = null; - decoderReplacementState = REPLACEMENT_STATE_WAIT_END_OF_STREAM; - return; - } - // Try and read the next subtitle from the source. - @ReadDataResult int result = readSource(formatHolder, nextInputBuffer, /* readFlags= */ 0); - if (result == C.RESULT_BUFFER_READ) { - if (nextInputBuffer.isEndOfStream()) { - inputStreamEnded = true; - waitingForKeyFrame = false; - } else { - @Nullable Format format = formatHolder.format; - if (format == null) { - // We haven't received a format yet. - return; - } - nextInputBuffer.subsampleOffsetUs = format.subsampleOffsetUs; - nextInputBuffer.flip(); - waitingForKeyFrame &= !nextInputBuffer.isKeyFrame(); - } - if (!waitingForKeyFrame) { - checkNotNull(decoder).queueInputBuffer(nextInputBuffer); - this.nextInputBuffer = null; - } - } else if (result == C.RESULT_NOTHING_READ) { - return; - } + if (decoderReplacementState == REPLACEMENT_STATE_SIGNAL_END_OF_STREAM) { + nextInputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); + checkNotNull(decoder).queueInputBuffer(nextInputBuffer); + this.nextInputBuffer = null; + decoderReplacementState = REPLACEMENT_STATE_WAIT_END_OF_STREAM; + return; + } + // Try and read the next subtitle from the source. + @ReadDataResult int result = readSource(formatHolder, nextInputBuffer, /* readFlags= */ 0); + if (result == C.RESULT_BUFFER_READ) { + if (nextInputBuffer.isEndOfStream()) { + inputStreamEnded = true; + waitingForKeyFrame = false; + } else { + @Nullable Format format = formatHolder.format; + if (format == null) { + // We haven't received a format yet. + return; } - } catch (SubtitleDecoderException e) { - handleDecoderError(e); + nextInputBuffer.subsampleOffsetUs = format.subsampleOffsetUs; + nextInputBuffer.flip(); + waitingForKeyFrame &= !nextInputBuffer.isKeyFrame(); + } + if (!waitingForKeyFrame) { + checkNotNull(decoder).queueInputBuffer(nextInputBuffer); + this.nextInputBuffer = null; + } + } else if (result == C.RESULT_NOTHING_READ) { + return; } + } + } catch (SubtitleDecoderException e) { + handleDecoderError(e); } + } - @Override - protected void onDisabled() { - streamFormat = null; - finalStreamEndPositionUs = C.TIME_UNSET; - clearOutput(); - releaseDecoder(); + @Override + protected void onDisabled() { + streamFormat = null; + finalStreamEndPositionUs = C.TIME_UNSET; + clearOutput(); + releaseDecoder(); + } + + @Override + public boolean isEnded() { + return outputStreamEnded; + } + + @Override + public boolean isReady() { + // Don't block playback whilst subtitles are loading. + // Note: To change this behavior, it will be necessary to consider [Internal: b/12949941]. + return true; + } + + private void releaseBuffers() { + nextInputBuffer = null; + nextSubtitleEventIndex = C.INDEX_UNSET; + if (subtitle != null) { + subtitle.release(); + subtitle = null; } - - @Override - public boolean isEnded() { - return outputStreamEnded; + if (nextSubtitle != null) { + nextSubtitle.release(); + nextSubtitle = null; } + } - @Override - public boolean isReady() { - // Don't block playback whilst subtitles are loading. - // Note: To change this behavior, it will be necessary to consider [Internal: b/12949941]. + private void releaseDecoder() { + releaseBuffers(); + checkNotNull(decoder).release(); + decoder = null; + decoderReplacementState = REPLACEMENT_STATE_NONE; + } + + private void initDecoder() { + waitingForKeyFrame = true; + decoder = decoderFactory.createDecoder(checkNotNull(streamFormat)); + } + + private void replaceDecoder() { + releaseDecoder(); + initDecoder(); + } + + private long getNextEventTime() { + if (nextSubtitleEventIndex == C.INDEX_UNSET) { + return Long.MAX_VALUE; + } + checkNotNull(subtitle); + return nextSubtitleEventIndex >= subtitle.getEventTimeCount() + ? Long.MAX_VALUE + : subtitle.getEventTime(nextSubtitleEventIndex); + } + + private void updateOutput(List cues) { + if (outputHandler != null) { + outputHandler.obtainMessage(MSG_UPDATE_OUTPUT, cues).sendToTarget(); + } else { + invokeUpdateOutputInternal(cues); + } + } + + private void clearOutput() { + updateOutput(Collections.emptyList()); + } + + @SuppressWarnings("unchecked") + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_OUTPUT: + invokeUpdateOutputInternal((List) msg.obj); return true; + default: + throw new IllegalStateException(); } + } - private void releaseBuffers() { - nextInputBuffer = null; - nextSubtitleEventIndex = C.INDEX_UNSET; - if (subtitle != null) { - subtitle.release(); - subtitle = null; - } - if (nextSubtitle != null) { - nextSubtitle.release(); - nextSubtitle = null; - } - } + private void invokeUpdateOutputInternal(List cues) { + output.onCues(cues); + output.onCues(new CueGroup(cues)); + } - private void releaseDecoder() { - releaseBuffers(); - checkNotNull(decoder).release(); - decoder = null; - decoderReplacementState = REPLACEMENT_STATE_NONE; - } - - private void initDecoder() { - waitingForKeyFrame = true; - decoder = decoderFactory.createDecoder(checkNotNull(streamFormat)); - } - - private void replaceDecoder() { - releaseDecoder(); - initDecoder(); - } - - private long getNextEventTime() { - if (nextSubtitleEventIndex == C.INDEX_UNSET) { - return Long.MAX_VALUE; - } - checkNotNull(subtitle); - return nextSubtitleEventIndex >= subtitle.getEventTimeCount() - ? Long.MAX_VALUE - : subtitle.getEventTime(nextSubtitleEventIndex); - } - - private void updateOutput(List cues) { - if (outputHandler != null) { - outputHandler.obtainMessage(MSG_UPDATE_OUTPUT, cues).sendToTarget(); - } else { - invokeUpdateOutputInternal(cues); - } - } - - private void clearOutput() { - updateOutput(Collections.emptyList()); - } - - @SuppressWarnings("unchecked") - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE_OUTPUT: - invokeUpdateOutputInternal((List) msg.obj); - return true; - default: - throw new IllegalStateException(); - } - } - - private void invokeUpdateOutputInternal(List cues) { - // See https://github.com/google/ExoPlayer/issues/7934 - // SubripDecoder texts tend to be DIMEN_UNSET which pushes up the - // subs unlike WEBVTT which creates an inconsistency - - List fixedCues = cues.stream().map( - cue -> { - Cue.Builder builder = cue.buildUpon(); - - if (cue.line == DIMEN_UNSET) - builder.setLine(-1f, LINE_TYPE_NUMBER); - - return builder.setSize(DIMEN_UNSET).build(); - } - ).collect(Collectors.toList()); - - output.onCues(fixedCues); - output.onCues(new CueGroup(fixedCues, 0L)); - } - - /** - * Called when {@link #decoder} throws an exception, so it can be logged and playback can - * continue. - * - *

Logs {@code e} and resets state to allow decoding the next sample. - */ - private void handleDecoderError(SubtitleDecoderException e) { - Log.e(TAG, "Subtitle decoding failed. streamFormat=" + streamFormat, e); - clearOutput(); - replaceDecoder(); - } + /** + * Called when {@link #decoder} throws an exception, so it can be logged and playback can + * continue. + * + *

Logs {@code e} and resets state to allow decoding the next sample. + */ + private void handleDecoderError(SubtitleDecoderException e) { + Log.e(TAG, "Subtitle decoding failed. streamFormat=" + streamFormat, e); + clearOutput(); + replaceDecoder(); + } } 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 7faf0cf5..4f16e9f6 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 @@ -35,13 +35,6 @@ class PlayerGeneratorViewModel : ViewModel() { private val _currentStamps = MutableLiveData>(emptyList()) val currentStamps: LiveData> = _currentStamps - private val _currentSubtitleYear = MutableLiveData(null) - val currentSubtitleYear: LiveData = _currentSubtitleYear - - fun setSubtitleYear(year: Int?) { - _currentSubtitleYear.postValue(year) - } - fun getId(): Int? { return generator?.getCurrentId() } @@ -120,9 +113,8 @@ class PlayerGeneratorViewModel : ViewModel() { // Do not post if there's nothing new // Posting will refresh subtitles which will in turn // make the subs to english if previously unselected - if (allSubs != currentSubs) { + if (allSubs != currentSubs) _currentSubs.postValue(allSubs) - } } private var currentJob: Job? = null @@ -172,10 +164,9 @@ class PlayerGeneratorViewModel : ViewModel() { } _loadingLinks.postValue(loadingState) + _currentLinks.postValue(currentLinks) - _currentSubs.postValue( - currentSubs.union(_currentSubs.value ?: emptySet()) - ) + _currentSubs.postValue(currentSubs) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt index 8d85f176..b06812c4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerSubtitleHelper.kt @@ -28,7 +28,7 @@ enum class SubtitleOrigin { /** * @param name To be displayed in the player - * @param url Url for the subtitle, when EMBEDDED_IN_VIDEO this variable is used as the real backend id + * @param url Url for the subtitle, when EMBEDDED_IN_VIDEO this variable is used as the real backend language * @param headers if empty it will use the base onlineDataSource headers else only the specified headers * */ data class SubtitleData( @@ -37,13 +37,7 @@ data class SubtitleData( val origin: SubtitleOrigin, val mimeType: String, val headers: Map -) { - /** Internal ID for exoplayer, unique for each link*/ - fun getId(): String { - return if (origin == SubtitleOrigin.EMBEDDED_IN_VIDEO) url - else "$url|$name" - } -} +) class PlayerSubtitleHelper { private var activeSubtitles: Set = emptySet() @@ -85,11 +79,11 @@ class PlayerSubtitleHelper { } } - fun subtitleStatus(sub: SubtitleData?): SubtitleStatus { - if (activeSubtitles.contains(sub)) { + fun subtitleStatus(sub : SubtitleData?): SubtitleStatus { + if(activeSubtitles.contains(sub)) { return SubtitleStatus.IS_ACTIVE } - if (allSubtitles.contains(sub)) { + if(allSubtitles.contains(sub)) { return SubtitleStatus.REQUIRES_RELOAD } return SubtitleStatus.NOT_FOUND @@ -101,7 +95,7 @@ class PlayerSubtitleHelper { regexSubtitlesToRemoveCaptions = style.removeCaptions subtitleView?.context?.let { ctx -> subStyle = style - Log.i(TAG, "SET STYLE = $style") + Log.i(TAG,"SET STYLE = $style") subtitleView?.setStyle(ctx.fromSaveToStyle(style)) subtitleView?.translationY = -style.elevation.toPx.toFloat() val size = style.fixedTextSize diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt index ba57d2de..62f967d2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt @@ -15,7 +15,6 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.GridLayoutManager -import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia import com.lagradost.cloudstream3.APIHolder.filterSearchResultByFilmQuality import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull @@ -31,7 +30,6 @@ import com.lagradost.cloudstream3.ui.search.SearchAdapter import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchHelper import com.lagradost.cloudstream3.ui.search.SearchViewModel -import com.lagradost.cloudstream3.utils.AppUtils.ownShow import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount @@ -73,8 +71,6 @@ class QuickSearchFragment : Fragment() { private var providers: Set? = null private lateinit var searchViewModel: SearchViewModel - private var bottomSheetDialog: BottomSheetDialog? = null - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -84,7 +80,7 @@ class QuickSearchFragment : Fragment() { WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE ) searchViewModel = ViewModelProvider(this)[SearchViewModel::class.java] - bottomSheetDialog?.ownShow() + return inflater.inflate(R.layout.quick_search, container, false) } @@ -160,9 +156,7 @@ class QuickSearchFragment : Fragment() { // else -> SearchHelper.handleSearchClickCallback(activity, callback) //} }, { item -> - bottomSheetDialog = activity?.loadHomepageList(item, dismissCallback = { - bottomSheetDialog = null - }) + activity?.loadHomepageList(item) }) quick_search_master_recycler?.layoutManager = GridLayoutManager(context, 1) } @@ -220,7 +214,7 @@ class QuickSearchFragment : Fragment() { when (it) { is Resource.Success -> { it.value.let { data -> - (quick_search_autofit_results?.adapter as? SearchAdapter)?.updateList( + (quick_search_autofit_results?.adapter as? SearchAdapter?)?.updateList( context?.filterSearchResultByFilmQuality(data) ?: data ) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index 0932b001..e9fbd5f9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -58,7 +58,6 @@ const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14 const val ACTION_PLAY_EPISODE_IN_WEB_VIDEO = 16 const val ACTION_PLAY_EPISODE_IN_MPV = 17 -const val ACTION_MARK_AS_WATCHED = 18 data class EpisodeClickEvent(val action: Int, val data: ResultEpisode) @@ -218,18 +217,10 @@ class EpisodeAdapter( name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name episodeText.isSelected = true // is needed for text repeating - if (card.videoWatchState == VideoWatchState.Watched) { - // This cannot be done in getDisplayPosition() as when you have not watched something - // the duration and position is 0 - episodeProgress?.max = 1 - episodeProgress?.progress = 1 - episodeProgress?.isVisible = true - } else { - val displayPos = card.getDisplayPosition() - episodeProgress?.max = (card.duration / 1000).toInt() - episodeProgress?.progress = (displayPos / 1000).toInt() - episodeProgress?.isVisible = displayPos > 0L - } + val displayPos = card.getDisplayPosition() + episodeProgress?.max = (card.duration / 1000).toInt() + episodeProgress?.progress = (displayPos / 1000).toInt() + episodeProgress?.isVisible = displayPos > 0L episodePoster?.isVisible = episodePoster?.setImage(card.poster) == true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt index affbcbb4..59a46264 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt @@ -7,13 +7,13 @@ import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.mvvm.logError fun RecyclerView?.setLinearListLayout(isHorizontal: Boolean = true) { - if (this == null) return + if(this == null) return this.layoutManager = this.context?.let { LinearListLayout(it).apply { if (isHorizontal) setHorizontal() else setVertical() } } ?: this.layoutManager } -open class LinearListLayout(context: Context?) : +class LinearListLayout(context: Context?) : LinearLayoutManager(context) { fun setHorizontal() { @@ -24,8 +24,7 @@ open class LinearListLayout(context: Context?) : orientation = VERTICAL } - private fun getCorrectParent(focused: View?): View? { - if (focused == null) return null + private fun getCorrectParent(focused: View): View? { var current: View? = focused val last: ArrayList = arrayListOf(focused) while (current != null && current !is RecyclerView) { @@ -55,17 +54,10 @@ open class LinearListLayout(context: Context?) : linearSmoothScroller.targetPosition = position startSmoothScroll(linearSmoothScroller) }*/ + override fun onInterceptFocusSearch(focused: View, direction: Int): View? { val dir = if (orientation == HORIZONTAL) { - if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) { - // This scrolls the recyclerview before doing focus search, which - // allows the focus search to work better. - - // Without this the recyclerview focus location on the screen - // would change when scrolling between recyclerviews. - (focused.parent as? RecyclerView)?.focusSearch(direction) - return null - } + if (direction == View.FOCUS_DOWN || direction == View.FOCUS_UP) return null if (direction == View.FOCUS_RIGHT) 1 else -1 } else { if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) return null diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 5a3e28b4..26dbb03e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -15,28 +15,24 @@ import android.view.ViewGroup import android.widget.AbsListView import android.widget.ArrayAdapter import android.widget.ImageView -import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged import androidx.lifecycle.ViewModelProvider import androidx.preference.PreferenceManager -import androidx.recyclerview.widget.RecyclerView import com.discord.panels.OverlappingPanelsLayout import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipDrawable import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.updateHasTrailers -import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.mvvm.* -import com.lagradost.cloudstream3.services.SubscriptionWorkManager import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD @@ -53,7 +49,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.openBrowser import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe import com.lagradost.cloudstream3.utils.Coroutines.main -import com.lagradost.cloudstream3.utils.DataStoreHelper.getVideoWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute @@ -88,8 +83,6 @@ import kotlinx.android.synthetic.main.fragment_result.result_next_airing import kotlinx.android.synthetic.main.fragment_result.result_next_airing_time import kotlinx.android.synthetic.main.fragment_result.result_no_episodes import kotlinx.android.synthetic.main.fragment_result.result_play_movie -import kotlinx.android.synthetic.main.fragment_result.result_poster -import kotlinx.android.synthetic.main.fragment_result.result_poster_holder import kotlinx.android.synthetic.main.fragment_result.result_reload_connection_open_in_browser import kotlinx.android.synthetic.main.fragment_result.result_reload_connectionerror import kotlinx.android.synthetic.main.fragment_result.result_resume_parent @@ -111,15 +104,6 @@ import kotlinx.coroutines.runBlocking const val START_ACTION_RESUME_LATEST = 1 const val START_ACTION_LOAD_EP = 2 -/** - * Future proofed way to mark episodes as watched - **/ -enum class VideoWatchState { - /** Default value when no key is set */ - None, - Watched -} - data class ResultEpisode( val headerName: String, val name: String?, @@ -138,10 +122,6 @@ data class ResultEpisode( val isFiller: Boolean?, val tvType: TvType, val parentId: Int, - /** - * Conveys if the episode itself is marked as watched - **/ - val videoWatchState: VideoWatchState ) fun ResultEpisode.getRealPosition(): Long { @@ -178,7 +158,6 @@ fun buildResultEpisode( parentId: Int, ): ResultEpisode { val posDur = getViewPos(id) - val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None return ResultEpisode( headerName, name, @@ -197,7 +176,6 @@ fun buildResultEpisode( isFiller, tvType, parentId, - videoWatchState ) } @@ -281,7 +259,7 @@ open class ResultFragment : ResultTrailerPlayer() { private var downloadButton: EasyDownloadButton? = null override fun onDestroyView() { updateUIListener = null - (result_episodes?.adapter as? EpisodeAdapter)?.killAdapter() + (result_episodes?.adapter as EpisodeAdapter?)?.killAdapter() downloadButton?.dispose() super.onDestroyView() @@ -462,7 +440,7 @@ open class ResultFragment : ResultTrailerPlayer() { temporary_no_focus?.requestFocus() } - (result_episodes?.adapter as? EpisodeAdapter)?.updateList(episodes.value) + (result_episodes?.adapter as? EpisodeAdapter?)?.updateList(episodes.value) if (isTv && hasEpisodes) main { delay(500) @@ -513,10 +491,11 @@ open class ResultFragment : ResultTrailerPlayer() { return StoredData(url, apiName, showFillers, dubStatus, start, playerAction) } - private fun reloadViewModel(forceReload: Boolean) { - if (!viewModel.hasLoaded() || forceReload) { + private fun reloadViewModel(success: Boolean = false) { + if (!viewModel.hasLoaded()) { val storedData = getStoredData(activity ?: context ?: return) ?: return + //viewModel.clear() viewModel.load( activity, storedData.url ?: return, @@ -532,25 +511,6 @@ open class ResultFragment : ResultTrailerPlayer() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - result_cast_items?.layoutManager = object : LinearListLayout(view.context) { - override fun onRequestChildFocus( - parent: RecyclerView, - state: RecyclerView.State, - child: View, - focused: View? - ): Boolean { - // Make the cast always focus the first visible item when focused - // from somewhere else. Otherwise it jumps to the last item. - return if (parent.focusedChild == null) { - scrollToPosition(this.findFirstCompletelyVisibleItemPosition()) - true - } else { - super.onRequestChildFocus(parent, state, child, focused) - } - } - }.apply { - this.orientation = RecyclerView.HORIZONTAL - } result_cast_items?.adapter = ActorAdaptor() updateUIListener = ::updateUI @@ -598,19 +558,6 @@ open class ResultFragment : ResultTrailerPlayer() { ) - observe(viewModel.episodeSynopsis) { description -> - view.context?.let { ctx -> - val builder: AlertDialog.Builder = - AlertDialog.Builder(ctx, R.style.AlertDialogCustom) - builder.setMessage(description.html()) - .setTitle(R.string.synopsis) - .setOnDismissListener { - viewModel.releaseEpisodeSynopsis() - } - .show() - } - } - observe(viewModel.watchStatus) { watchType -> result_bookmark_button?.text = getString(watchType.stringRes) result_bookmark_fab?.text = getString(watchType.stringRes) @@ -710,7 +657,7 @@ open class ResultFragment : ResultTrailerPlayer() { val newList = list.filter { it.isSynced && it.hasAccount } result_mini_sync?.isVisible = newList.isNotEmpty() - (result_mini_sync?.adapter as? ImageAdapter)?.updateList(newList.mapNotNull { it.icon }) + (result_mini_sync?.adapter as? ImageAdapter?)?.updateList(newList.mapNotNull { it.icon }) } var currentSyncProgress = 0 @@ -873,7 +820,6 @@ open class ResultFragment : ResultTrailerPlayer() { } observe(viewModel.page) { data -> - if (data == null) return@observe when (data) { is Resource.Success -> { val d = data.value @@ -923,40 +869,10 @@ open class ResultFragment : ResultTrailerPlayer() { result_cast_items?.isVisible = d.actors != null - (result_cast_items?.adapter as? ActorAdaptor)?.apply { + (result_cast_items?.adapter as ActorAdaptor?)?.apply { updateList(d.actors ?: emptyList()) } - observeNullable(viewModel.subscribeStatus) { isSubscribed -> - result_subscribe?.isVisible = isSubscribed != null - if (isSubscribed == null) return@observeNullable - - val drawable = if (isSubscribed) { - R.drawable.ic_baseline_notifications_active_24 - } else { - R.drawable.baseline_notifications_none_24 - } - - result_subscribe?.setImageResource(drawable) - } - - result_subscribe?.setOnClickListener { - val isSubscribed = - viewModel.toggleSubscriptionStatus() ?: return@setOnClickListener - - val message = if (isSubscribed) { - // Kinda icky to have this here, but it works. - SubscriptionWorkManager.enqueuePeriodicWork(context) - R.string.subscription_new - } else { - R.string.subscription_deleted - } - - val name = (viewModel.page.value as? Resource.Success)?.value?.title - ?: txt(R.string.no_data).asStringNull(context) ?: "" - showToast(activity, txt(message, name), Toast.LENGTH_SHORT) - } - result_open_in_browser?.isVisible = d.url.startsWith("http") result_open_in_browser?.setOnClickListener { val i = Intent(ACTION_VIEW) @@ -1004,6 +920,8 @@ open class ResultFragment : ResultTrailerPlayer() { } + result_tag?.removeAllViews() + d.comingSoon.let { soon -> result_coming_soon?.isVisible = soon result_data_holder?.isGone = soon @@ -1012,7 +930,6 @@ open class ResultFragment : ResultTrailerPlayer() { val tags = d.tags result_tag_holder?.isVisible = tags.isNotEmpty() result_tag?.apply { - removeAllViews() tags.forEach { tag -> val chip = Chip(context) val chipDrawable = ChipDrawable.createFromAttributes( @@ -1027,7 +944,6 @@ open class ResultFragment : ResultTrailerPlayer() { chip.isCheckable = false chip.isFocusable = false chip.isClickable = false - chip.setTextColor(context.colorFromAttribute(R.attr.textColor)) addView(chip) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index 2f232995..9bae8753 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -322,7 +322,9 @@ class ResultFragmentPhone : ResultFragment() { // it?.dismiss() //} builder.setCanceledOnTouchOutside(true) + builder.show() + builder } } @@ -483,7 +485,7 @@ class ResultFragmentPhone : ResultFragment() { result_recommendations?.post { rec?.let { list -> - (result_recommendations?.adapter as? SearchAdapter)?.updateList(list.filter { it.apiName == matchAgainst }) + (result_recommendations?.adapter as SearchAdapter?)?.updateList(list.filter { it.apiName == matchAgainst }) } } } 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 71ecb0e9..0e3ee53e 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 @@ -3,28 +3,30 @@ package com.lagradost.cloudstream3.ui.result import android.app.Dialog import android.os.Bundle import android.view.View +import android.widget.LinearLayout import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetDialog -import com.lagradost.cloudstream3.APIHolder.updateHasTrailers import com.lagradost.cloudstream3.DubStatus -import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.mvvm.ResourceSome import com.lagradost.cloudstream3.mvvm.Some import com.lagradost.cloudstream3.mvvm.observe -import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator -import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.search.SearchAdapter import com.lagradost.cloudstream3.ui.search.SearchHelper -import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.AppUtils.setMaxViewPoolSize import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe -import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage +import kotlinx.android.synthetic.main.fragment_home.* +import kotlinx.android.synthetic.main.fragment_result.* import kotlinx.android.synthetic.main.fragment_result_tv.* +import kotlinx.android.synthetic.main.fragment_result_tv.result_episodes +import kotlinx.android.synthetic.main.fragment_result_tv.result_episodes_text +import kotlinx.android.synthetic.main.fragment_result_tv.result_play_movie +import kotlinx.android.synthetic.main.fragment_result_tv.result_root class ResultFragmentTv : ResultFragment() { override val resultLayout = R.layout.fragment_result_tv @@ -83,31 +85,13 @@ class ResultFragmentTv : ResultFragment() { } } - override fun setTrailers(trailers: List?) { - context?.updateHasTrailers() - if (!LoadResponse.isTrailersEnabled) return - - result_play_trailer?.isGone = trailers.isNullOrEmpty() - result_play_trailer?.setOnClickListener { - if (trailers.isNullOrEmpty()) return@setOnClickListener - activity.navigate( - R.id.global_to_navigation_player, GeneratorPlayer.newInstance( - ExtractorLinkGenerator( - trailers, - emptyList() - ) - ) - ) - } - } - override fun setRecommendations(rec: List?, validApiName: String?) { currentRecommendations = rec ?: emptyList() val isInvalid = rec.isNullOrEmpty() result_recommendations?.isGone = isInvalid result_recommendations_holder?.isGone = isInvalid val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName - (result_recommendations?.adapter as? SearchAdapter)?.updateList(rec?.filter { it.apiName == matchAgainst } + (result_recommendations?.adapter as SearchAdapter?)?.updateList(rec?.filter { it.apiName == matchAgainst } ?: emptyList()) rec?.map { it.apiName }?.distinct()?.let { apiNames -> @@ -126,7 +110,7 @@ class ResultFragmentTv : ResultFragment() { super.onViewCreated(view, savedInstanceState) result_episodes?.layoutManager = - //LinearListLayout(result_episodes ?: return, result_episodes?.context).apply { + //LinearListLayout(result_episodes ?: return, result_episodes?.context).apply { LinearListLayout(result_episodes?.context).apply { setHorizontal() } @@ -176,7 +160,8 @@ class ResultFragmentTv : ResultFragment() { loadingDialog = null } loadingDialog = loadingDialog ?: context?.let { ctx -> - val builder = BottomSheetDialog(ctx) + val builder = + BottomSheetDialog(ctx) builder.setContentView(R.layout.bottom_loading) builder.setOnDismissListener { loadingDialog = null @@ -186,7 +171,9 @@ class ResultFragmentTv : ResultFragment() { // it?.dismiss() //} builder.setCanceledOnTouchOutside(true) + builder.show() + builder } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 46a8c9f6..0c26f69c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.ui.result import android.app.Activity import android.content.* import android.net.Uri -import android.os.Build import android.os.Bundle import android.util.Log import android.widget.Toast @@ -14,10 +13,8 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.getId import com.lagradost.cloudstream3.APIHolder.unixTime -import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.getCastSession import com.lagradost.cloudstream3.CommonActivity.showToast @@ -27,7 +24,6 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId import com.lagradost.cloudstream3.LoadResponse.Companion.isMovie import com.lagradost.cloudstream3.metaproviders.SyncRedirector import com.lagradost.cloudstream3.mvvm.* -import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.providers.Kitsu import com.lagradost.cloudstream3.ui.APIRepository @@ -59,6 +55,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason import com.lagradost.cloudstream3.utils.UIHelper.navigate import kotlinx.coroutines.* import java.io.File +import java.lang.Math.abs import java.util.concurrent.TimeUnit @@ -317,11 +314,6 @@ data class ExtractedTrailerData( class ResultViewModel2 : ViewModel() { private var currentResponse: LoadResponse? = null - fun clear() { - currentResponse = null - _page.postValue(null) - } - data class EpisodeIndexer( val dubStatus: DubStatus, val season: Int, @@ -348,9 +340,9 @@ class ResultViewModel2 : ViewModel() { //private val currentHeaderName get() = currentResponse?.name - private val _page: MutableLiveData?> = - MutableLiveData(null) - val page: LiveData?> = _page + private val _page: MutableLiveData> = + MutableLiveData(Resource.Loading()) + val page: LiveData> = _page private val _episodes: MutableLiveData>> = MutableLiveData(ResourceSome.Loading()) @@ -406,6 +398,7 @@ class ResultViewModel2 : ViewModel() { private val _selectedDubStatusIndex: MutableLiveData = MutableLiveData(-1) val selectedDubStatusIndex: LiveData = _selectedDubStatusIndex + private val _loadedLinks: MutableLiveData> = MutableLiveData(Some.None) val loadedLinks: LiveData> = _loadedLinks @@ -413,12 +406,6 @@ class ResultViewModel2 : ViewModel() { MutableLiveData(Some.None) val resumeWatching: LiveData> = _resumeWatching - private val _episodeSynopsis: MutableLiveData = MutableLiveData(null) - val episodeSynopsis: LiveData = _episodeSynopsis - - private val _subscribeStatus: MutableLiveData = MutableLiveData(null) - val subscribeStatus: LiveData = _subscribeStatus - companion object { const val TAG = "RVM2" private const val EPISODE_RANGE_SIZE = 20 @@ -431,6 +418,7 @@ class ResultViewModel2 : ViewModel() { fun updateWatchStatus(currentResponse: LoadResponse, status: WatchType) { val currentId = currentResponse.getId() + val resultPage = currentResponse DataStoreHelper.setResultWatchState(currentId, status.internalId) val current = DataStoreHelper.getBookmarkedData(currentId) @@ -441,12 +429,12 @@ class ResultViewModel2 : ViewModel() { currentId, current?.bookmarkedTime ?: currentTime, currentTime, - currentResponse.name, - currentResponse.url, - currentResponse.apiName, - currentResponse.type, - currentResponse.posterUrl, - currentResponse.year + resultPage.name, + resultPage.url, + resultPage.apiName, + resultPage.type, + resultPage.posterUrl, + resultPage.year ) ) } @@ -820,42 +808,6 @@ class ResultViewModel2 : ViewModel() { } } - /** - * @return true if the new status is Subscribed, false if not. Null if not possible to subscribe. - **/ - fun toggleSubscriptionStatus(): Boolean? { - val isSubscribed = _subscribeStatus.value ?: return null - val response = currentResponse ?: return null - if (response !is EpisodeResponse) return null - - val currentId = response.getId() - - if (isSubscribed) { - DataStoreHelper.removeSubscribedData(currentId) - } else { - val current = DataStoreHelper.getSubscribedData(currentId) - - DataStoreHelper.setSubscribedData( - currentId, - DataStoreHelper.SubscribedData( - currentId, - current?.bookmarkedTime ?: unixTimeMS, - unixTimeMS, - response.getLatestEpisodes(), - response.name, - response.url, - response.apiName, - response.type, - response.posterUrl, - response.year - ) - ) - } - - _subscribeStatus.postValue(!isSubscribed) - return !isSubscribed - } - private fun startChromecast( activity: Activity?, result: ResultEpisode, @@ -1126,12 +1078,7 @@ class ResultViewModel2 : ViewModel() { 1L } - // Component no longer safe to use in A13 for VLC - // https://code.videolan.org/videolan/vlc-android/-/issues/2776 - // This will likely need to be updated once VLC fixes their documentation. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - component = VLC_COMPONENT - } + component = VLC_COMPONENT putExtra("from_start", !resume) putExtra("position", position) @@ -1166,10 +1113,6 @@ class ResultViewModel2 : ViewModel() { ) ) - fun releaseEpisodeSynopsis() { - _episodeSynopsis.postValue(null) - } - private suspend fun handleEpisodeClickEvent(activity: Activity?, click: EpisodeClickEvent) { when (click.action) { ACTION_SHOW_OPTIONS -> { @@ -1206,17 +1149,6 @@ class ResultViewModel2 : ViewModel() { ) ) - // Do not add mark as watched on movies - if (!listOf(TvType.Movie, TvType.AnimeMovie).contains(click.data.tvType)) { - val isWatched = - DataStoreHelper.getVideoWatchState(click.data.id) == VideoWatchState.Watched - - val watchedText = if (isWatched) R.string.action_remove_from_watched - else R.string.action_mark_as_watched - - options.add(txt(watchedText) to ACTION_MARK_AS_WATCHED) - } - postPopup( txt( activity?.getNameFull( @@ -1249,10 +1181,6 @@ class ResultViewModel2 : ViewModel() { } } } - ACTION_SHOW_DESCRIPTION -> { - _episodeSynopsis.postValue(click.data.description) - } - /* not implemented, not used ACTION_DOWNLOAD_EPISODE_SUBTITLE -> { loadLinks(click.data, isVisible = false, isCasting = false) { links -> @@ -1437,7 +1365,7 @@ class ResultViewModel2 : ViewModel() { R.id.global_to_navigation_player, GeneratorPlayer.newInstance( generator?.also { - it.getAll() // I know kinda shit to iterate all, but it is 100% sure to work + it.getAll() // I know kinda shit to itterate all, but it is 100% sure to work ?.indexOfFirst { value -> value is ResultEpisode && value.id == click.data.id } ?.let { index -> if (index >= 0) @@ -1448,19 +1376,6 @@ class ResultViewModel2 : ViewModel() { ) ) } - ACTION_MARK_AS_WATCHED -> { - val isWatched = - DataStoreHelper.getVideoWatchState(click.data.id) == VideoWatchState.Watched - - if (isWatched) { - DataStoreHelper.setVideoWatchState(click.data.id, VideoWatchState.None) - } else { - DataStoreHelper.setVideoWatchState(click.data.id, VideoWatchState.Watched) - } - - // Kinda dirty to reload all episodes :( - reloadEpisodes() - } } } @@ -1469,128 +1384,79 @@ class ResultViewModel2 : ViewModel() { meta: SyncAPI.SyncResult?, syncs: Map? = null ): Pair { - //if (meta == null) return resp to false + if (meta == null) return resp to false var updateEpisodes = false val out = resp.apply { Log.i(TAG, "applyMeta") - if (meta != null) { - duration = duration ?: meta.duration - rating = rating ?: meta.publicScore - tags = tags ?: meta.genres - plot = if (plot.isNullOrBlank()) meta.synopsis else plot - posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl - actors = actors ?: meta.actors + duration = duration ?: meta.duration + rating = rating ?: meta.publicScore + tags = tags ?: meta.genres + plot = if (plot.isNullOrBlank()) meta.synopsis else plot + posterUrl = posterUrl ?: meta.posterUrl ?: meta.backgroundPosterUrl + actors = actors ?: meta.actors - if (this is EpisodeResponse) { - nextAiring = nextAiring ?: meta.nextAiring - } - - val realRecommendations = ArrayList() - val apiNames = apis.filter { - it.name.contains("gogoanime", true) || - it.name.contains("9anime", true) - }.map { - it.name - } - - meta.recommendations?.forEach { rec -> - apiNames.forEach { name -> - realRecommendations.add(rec.copy(apiName = name)) - } - } - - recommendations = recommendations?.union(realRecommendations)?.toList() - ?: realRecommendations + if (this is EpisodeResponse) { + nextAiring = nextAiring ?: meta.nextAiring } for ((k, v) in syncs ?: emptyMap()) { syncData[k] = v } - argamap( - { - if (this !is AnimeLoadResponse) return@argamap - // already exist, no need to run getTracker - if (this.getAniListId() != null && this.getMalId() != null) return@argamap + val realRecommendations = ArrayList() + // TODO: fix + //val apiNames = listOf(GogoanimeProvider().name, NineAnimeProvider().name) + // meta.recommendations?.forEach { rec -> + // apiNames.forEach { name -> + // realRecommendations.add(rec.copy(apiName = name)) + // } + // } - val res = APIHolder.getTracker( - listOfNotNull( - this.engName, - this.name, - this.japName - ).filter { it.length > 2 } - .distinct(), // the reason why we filter is due to not wanting smth like " " or "?" - TrackerType.getTypes(this.type), - this.year + recommendations = recommendations?.union(realRecommendations)?.toList() + ?: realRecommendations + + argamap({ + addTrailer(meta.trailers) + }, { + if (this !is AnimeLoadResponse) return@argamap + val map = + Kitsu.getEpisodesDetails( + getMalId(), + getAniListId(), + isResponseRequired = false ) - - val ids = arrayOf( - AccountManager.malApi.idPrefix to res?.malId?.toString(), - AccountManager.aniListApi.idPrefix to res?.aniId - ) - - if (ids.any { (id, new) -> - val current = syncData[id] - new != null && current != null && current != new - } - ) { - // getTracker fucked up as it conflicts with current implementation - return@argamap - } - - // set all the new data, prioritise old correct data - ids.forEach { (id, new) -> - new?.let { - syncData[id] = syncData[id] ?: it - } - } - - // set posters, might fuck up due to headers idk - posterUrl = posterUrl ?: res?.image - backgroundPosterUrl = backgroundPosterUrl ?: res?.cover - }, - { - if (meta == null) return@argamap - addTrailer(meta.trailers) - }, { - if (this !is AnimeLoadResponse) return@argamap - val map = - Kitsu.getEpisodesDetails( - getMalId(), - getAniListId(), - isResponseRequired = false - ) - if (map.isNullOrEmpty()) return@argamap - updateEpisodes = DubStatus.values().map { dubStatus -> - val current = - this.episodes[dubStatus]?.mapIndexed { index, episode -> - episode.apply { - this.episode = this.episode ?: (index + 1) - } - }?.sortedBy { it.episode ?: 0 }?.toMutableList() - if (current.isNullOrEmpty()) return@map false - val episodeNumbers = current.map { ep -> ep.episode!! } - var updateCount = 0 - map.forEach { (episode, node) -> - episodeNumbers.binarySearch(episode).let { index -> - current.getOrNull(index)?.let { currentEp -> - current[index] = currentEp.apply { - updateCount++ - this.description = this.description ?: node.description?.en - this.name = this.name ?: node.titles?.canonical - this.episode = - this.episode ?: node.num ?: episodeNumbers[index] - this.posterUrl = - this.posterUrl ?: node.thumbnail?.original?.url - } + if (map.isNullOrEmpty()) return@argamap + updateEpisodes = DubStatus.values().map { dubStatus -> + val current = + this.episodes[dubStatus]?.mapIndexed { index, episode -> + episode.apply { + this.episode = this.episode ?: (index + 1) + } + }?.sortedBy { it.episode ?: 0 }?.toMutableList() + if (current.isNullOrEmpty()) return@map false + val episodeNumbers = current.map { ep -> ep.episode!! } + var updateCount = 0 + map.forEach { (episode, node) -> + episodeNumbers.binarySearch(episode).let { index -> + current.getOrNull(index)?.let { currentEp -> + current[index] = currentEp.apply { + updateCount++ + val currentBack = this + this.description = this.description ?: node.description?.en + this.name = this.name ?: node.titles?.canonical + this.episode = + this.episode ?: node.num ?: episodeNumbers[index] + this.posterUrl = + this.posterUrl ?: node.thumbnail?.original?.url } } } - this.episodes[dubStatus] = current - updateCount > 0 - }.any { it } - }) + } + this.episodes[dubStatus] = current + updateCount > 0 + }.any { it } + }) } return out to updateEpisodes } @@ -1658,13 +1524,7 @@ class ResultViewModel2 : ViewModel() { val end = minOf(list.size, start + length) list.subList(start, end).map { val posDur = getViewPos(it.id) - val watchState = - DataStoreHelper.getVideoWatchState(it.id) ?: VideoWatchState.None - it.copy( - position = posDur?.position ?: 0, - duration = posDur?.duration ?: 0, - videoWatchState = watchState - ) + it.copy(position = posDur?.position ?: 0, duration = posDur?.duration ?: 0) } } ?: emptyList() @@ -1717,33 +1577,24 @@ class ResultViewModel2 : ViewModel() { postResume() } - private fun postSubscription(loadResponse: LoadResponse) { - if (loadResponse.isEpisodeBased()) { - val id = loadResponse.getId() - val data = DataStoreHelper.getSubscribedData(id) - DataStoreHelper.updateSubscribedData(id, data, loadResponse as? EpisodeResponse) - val isSubscribed = data != null - _subscribeStatus.postValue(isSubscribed) - } - } - private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) { if (range == null || indexer == null) { return } + val episodes = currentEpisodes[indexer] val ranges = currentRanges[indexer] if (ranges?.contains(range) != true) { // if the current ranges does not include the range then select the range with the closest matching start episode // this usually happends when dub has less episodes then sub -> the range does not exist - ranges?.minByOrNull { kotlin.math.abs(it.startEpisode - range.startEpisode) } - ?.let { r -> - postEpisodeRange(indexer, r) - return - } + ranges?.minByOrNull { abs(it.startEpisode - range.startEpisode) }?.let { r -> + postEpisodeRange(indexer, r) + return + } } + val size = episodes?.size val isMovie = currentResponse?.isMovie() == true currentIndex = indexer currentRange = range @@ -1753,7 +1604,6 @@ class ResultViewModel2 : ViewModel() { text to r } ?: emptyList()) - val size = currentEpisodes[indexer]?.size _episodesCountText.postValue( some( if (isMovie) null else @@ -1833,12 +1683,9 @@ class ResultViewModel2 : ViewModel() { generator = if (isMovie) { getMovie()?.let { RepoLinkGenerator(listOf(it), page = currentResponse) } } else { - val episodes = currentEpisodes.filter { it.key.dubStatus == indexer.dubStatus } - .toList() - .sortedBy { it.first.season } - .flatMap { it.second } - - RepoLinkGenerator(episodes, page = currentResponse) + episodes?.let { list -> + RepoLinkGenerator(list, page = currentResponse) + } } if (isMovie) { @@ -1863,7 +1710,6 @@ class ResultViewModel2 : ViewModel() { ) { currentResponse = loadResponse postPage(loadResponse, apiRepository) - postSubscription(loadResponse) if (updateEpisodes) postEpisodes(loadResponse, updateFillers) } @@ -2073,7 +1919,7 @@ class ResultViewModel2 : ViewModel() { // this takes the indexer most preferable by the user given the current sorting val min = ranges.keys.minByOrNull { index -> kotlin.math.abs( - index.season - (preferStartSeason ?: 1) + index.season - (preferStartSeason ?: 0) ) + if (index.dubStatus == preferDubStatus) 0 else 100000 } @@ -2129,42 +1975,42 @@ class ResultViewModel2 : ViewModel() { limit: Int = 0 ): List = coroutineScope { - val returnlist = ArrayList() - loadResponse.trailers.windowed(limit, limit, true).takeWhile { list -> - list.amap { trailerData -> - try { - val links = arrayListOf() - val subs = arrayListOf() - if (!loadExtractor( + var currentCount = 0 + return@coroutineScope loadResponse.trailers.amap { trailerData -> + try { + val links = arrayListOf() + val subs = arrayListOf() + if (!loadExtractor( + trailerData.extractorUrl, + trailerData.referer, + { subs.add(it) }, + { links.add(it) }) && trailerData.raw + ) { + arrayListOf( + ExtractorLink( + "", + "Trailer", trailerData.extractorUrl, - trailerData.referer, - { subs.add(it) }, - { links.add(it) }) && trailerData.raw - ) { - arrayListOf( - ExtractorLink( - "", - "Trailer", - trailerData.extractorUrl, - trailerData.referer ?: "", - Qualities.Unknown.value, - trailerData.extractorUrl.contains(".m3u8") - ) - ) to arrayListOf() - } else { - links to subs + trailerData.referer ?: "", + Qualities.Unknown.value, + trailerData.extractorUrl.contains(".m3u8") + ) + ) to arrayListOf() + } else { + links to subs + }.also { (extractor, _) -> + if (extractor.isNotEmpty() && limit != 0) { + currentCount++ + if (currentCount >= limit) { + cancel() + } } - } catch (e: Throwable) { - logError(e) - null } - }.filterNotNull().map { (links, subs) -> ExtractedTrailerData(links, subs) }.let { - returnlist.addAll(it) + } catch (e: Throwable) { + logError(e) + null } - - returnlist.size < limit - } - return@coroutineScope returnlist + }.filterNotNull().map { (links, subs) -> ExtractedTrailerData(links, subs) } } @@ -2223,9 +2069,8 @@ class ResultViewModel2 : ViewModel() { showFillers: Boolean, dubStatus: DubStatus, autostart: AutoResume?, - loadTrailers: Boolean = true, ) = - ioSafe { + viewModelScope.launchSafe { _page.postValue(Resource.Loading(url)) _episodes.postValue(ResourceSome.Loading()) @@ -2243,7 +2088,7 @@ class ResultViewModel2 : ViewModel() { "This provider does not exist" ) ) - return@ioSafe + return@launchSafe } @@ -2251,18 +2096,24 @@ class ResultViewModel2 : ViewModel() { val validUrlResource = safeApiCall { SyncRedirector.redirect( url, - api + api.mainUrl ) } - + // TODO: fix + // val validUrlResource = safeApiCall { + // SyncRedirector.redirect( + // url, + // api.mainUrl.replace(NineAnimeProvider().mainUrl, "9anime") + // .replace(GogoanimeProvider().mainUrl, "gogoanime") + // ) + // } if (validUrlResource !is Resource.Success) { if (validUrlResource is Resource.Failure) { _page.postValue(validUrlResource) } - return@ioSafe + return@launchSafe } - val validUrl = validUrlResource.value val repo = APIRepository(api) currentRepo = repo @@ -2272,16 +2123,16 @@ class ResultViewModel2 : ViewModel() { _page.postValue(data) } is Resource.Success -> { - if (!isActive) return@ioSafe + if (!isActive) return@launchSafe val loadResponse = ioWork { applyMeta(data.value, currentMeta, currentSync).first } - if (!isActive) return@ioSafe + if (!isActive) return@launchSafe val mainId = loadResponse.getId() preferDubStatus = getDub(mainId) ?: preferDubStatus preferStartEpisode = getResultEpisode(mainId) - preferStartSeason = getResultSeason(mainId) ?: 1 + preferStartSeason = getResultSeason(mainId) setKey( DOWNLOAD_HEADER_CACHE, @@ -2296,15 +2147,15 @@ class ResultViewModel2 : ViewModel() { System.currentTimeMillis(), ) ) - if (loadTrailers) - loadTrailers(data.value) + + loadTrailers(data.value) postSuccessful( data.value, updateEpisodes = true, updateFillers = showFillers, apiRepository = repo ) - if (!isActive) return@ioSafe + if (!isActive) return@launchSafe handleAutoStart(activity, autostart) } is Resource.Loading -> { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt index 649641c8..c2523931 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt @@ -10,37 +10,27 @@ import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.ui.AutofitRecyclerView -import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.UIHelper.IsBottomLayout import com.lagradost.cloudstream3.utils.UIHelper.toPx import kotlinx.android.synthetic.main.search_result_compact.view.* import kotlin.math.roundToInt -/** Click */ const val SEARCH_ACTION_LOAD = 0 - -/** Long press */ const val SEARCH_ACTION_SHOW_METADATA = 1 const val SEARCH_ACTION_PLAY_FILE = 2 const val SEARCH_ACTION_FOCUSED = 4 -class SearchClickCallback( - val action: Int, - val view: View, - val position: Int, - val card: SearchResponse -) +class SearchClickCallback(val action: Int, val view: View, val position : Int, val card: SearchResponse) class SearchAdapter( private val cardList: MutableList, private val resView: AutofitRecyclerView, private val clickCallback: (SearchClickCallback) -> Unit, ) : RecyclerView.Adapter() { - var hasNext: Boolean = false + var hasNext : Boolean = false override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val layout = - if (parent.context.IsBottomLayout()) R.layout.search_result_grid_expanded else R.layout.search_result_grid + val layout = if(parent.context.IsBottomLayout()) R.layout.search_result_grid_expanded else R.layout.search_result_grid return CardViewHolder( LayoutInflater.from(parent.context).inflate(layout, parent, false), clickCallback, @@ -81,8 +71,7 @@ class SearchAdapter( val cardView: ImageView = itemView.imageView private val compactView = false//itemView.context.getGridIsCompact() - private val coverHeight: Int = - if (compactView) 80.toPx else (resView.itemWidth / 0.68).roundToInt() + private val coverHeight: Int = if (compactView) 80.toPx else (resView.itemWidth / 0.68).roundToInt() fun bind(card: SearchResponse, position: Int) { if (!compactView) { @@ -99,10 +88,7 @@ class SearchAdapter( } } -class SearchResponseDiffCallback( - private val oldList: List, - private val newList: List -) : +class SearchResponseDiffCallback(private val oldList: List, private val newList: List) : DiffUtil.Callback() { override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = oldList[oldItemPosition].name == newList[newItemPosition].name diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index b4a38216..bcb36f7e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -1,6 +1,5 @@ package com.lagradost.cloudstream3.ui.search -import android.content.DialogInterface import android.content.res.Configuration import android.os.Bundle import android.view.LayoutInflater @@ -11,7 +10,6 @@ import android.widget.AbsListView import android.widget.ArrayAdapter import android.widget.ImageView import android.widget.ListView -import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.SearchView import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -30,7 +28,6 @@ import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings import com.lagradost.cloudstream3.APIHolder.getApiSettings import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.mvvm.Resource @@ -44,10 +41,6 @@ import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageLis import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.updateChips import com.lagradost.cloudstream3.ui.home.ParentItemAdapter import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings -import com.lagradost.cloudstream3.utils.AppUtils.ownHide -import com.lagradost.cloudstream3.utils.AppUtils.ownShow -import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.setKey @@ -77,18 +70,9 @@ class SearchFragment : Fragment() { } } } - - const val SEARCH_QUERY = "search_query" - - fun newInstance(query: String): Bundle { - return Bundle().apply { - putString(SEARCH_QUERY, query) - } - } } private val searchViewModel: SearchViewModel by activityViewModels() - private var bottomSheetDialog: BottomSheetDialog? = null override fun onCreateView( inflater: LayoutInflater, @@ -98,12 +82,7 @@ class SearchFragment : Fragment() { activity?.window?.setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE ) - bottomSheetDialog?.ownShow() - return inflater.inflate( - if (isTvSettings()) R.layout.fragment_search_tv else R.layout.fragment_search, - container, - false - ) + return inflater.inflate(R.layout.fragment_search, container, false) } private fun fixGrid() { @@ -122,7 +101,6 @@ class SearchFragment : Fragment() { override fun onDestroyView() { hideKeyboard() - bottomSheetDialog?.ownHide() super.onDestroyView() } @@ -150,10 +128,9 @@ class SearchFragment : Fragment() { context?.let { ctx -> val default = enumValues().sorted().filter { it != TvType.NSFW } .map { it.ordinal.toString() }.toSet() - val preferredTypes = (PreferenceManager.getDefaultSharedPreferences(ctx) + val preferredTypes = PreferenceManager.getDefaultSharedPreferences(ctx) .getStringSet(this.getString(R.string.prefer_media_type_key), default) - ?.ifEmpty { default } ?: default) - .mapNotNull { it.toIntOrNull() ?: return@mapNotNull null } + ?.mapNotNull { it.toIntOrNull() ?: return@mapNotNull null } ?: default val settings = ctx.getApiSettings() @@ -366,7 +343,7 @@ class SearchFragment : Fragment() { searchViewModel.updateHistory() } - search_history_holder?.isVisible = showHistory + search_history_recycler?.isVisible = showHistory search_master_recycler?.isVisible = !showHistory && isAdvancedSearch search_autofit_results?.isVisible = !showHistory && !isAdvancedSearch @@ -375,41 +352,7 @@ class SearchFragment : Fragment() { } }) - search_clear_call_history?.setOnClickListener { - activity?.let { ctx -> - val builder: AlertDialog.Builder = AlertDialog.Builder(ctx) - val dialogClickListener = - DialogInterface.OnClickListener { _, which -> - when (which) { - DialogInterface.BUTTON_POSITIVE -> { - removeKeys(SEARCH_HISTORY_KEY) - searchViewModel.updateHistory() - } - DialogInterface.BUTTON_NEGATIVE -> { - } - } - } - - try { - builder.setTitle(R.string.clear_history).setMessage( - ctx.getString(R.string.delete_message).format( - ctx.getString(R.string.history) - ) - ) - .setPositiveButton(R.string.sort_clear, dialogClickListener) - .setNegativeButton(R.string.cancel, dialogClickListener) - .show().setDefaultFocus() - } catch (e: Exception) { - logError(e) - // ye you somehow fucked up formatting did you? - } - } - - - } - observe(searchViewModel.currentHistory) { list -> - search_clear_call_history?.isVisible = list.isNotEmpty() (search_history_recycler.adapter as? SearchHistoryAdaptor?)?.updateList(list) } @@ -420,7 +363,7 @@ class SearchFragment : Fragment() { is Resource.Success -> { it.value.let { data -> if (data.isNotEmpty()) { - (search_autofit_results?.adapter as? SearchAdapter)?.updateList(data) + (search_autofit_results?.adapter as SearchAdapter?)?.updateList(data) } } searchExitIcon.alpha = 1f @@ -479,9 +422,7 @@ class SearchFragment : Fragment() { ParentItemAdapter(mutableListOf(), { callback -> SearchHelper.handleSearchClickCallback(activity, callback) }, { item -> - bottomSheetDialog = activity?.loadHomepageList(item, dismissCallback = { - bottomSheetDialog = null - }) + activity?.loadHomepageList(item) }) val historyAdapter = SearchHistoryAdaptor(mutableListOf()) { click -> @@ -509,14 +450,6 @@ class SearchFragment : Fragment() { search_master_recycler?.adapter = masterAdapter search_master_recycler?.layoutManager = GridLayoutManager(context, 1) - // Automatically search the specified query, this allows the app search to launch from intent - arguments?.getString(SEARCH_QUERY)?.let { query -> - if (query.isBlank()) return@let - main_search?.setQuery(query, true) - // Clear the query as to not make it request the same query every time the page is opened - arguments?.putString(SEARCH_QUERY, null) - } - // SubtitlesFragment.push(activity) //searchViewModel.search("iron man") //(activity as AppCompatActivity).loadResult("https://shiro.is/overlord-dubbed", "overlord-dubbed", "Shiro") diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHelper.kt index 45336d5b..1de89809 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHelper.kt @@ -3,13 +3,11 @@ package com.lagradost.cloudstream3.ui.search import android.app.Activity import android.widget.Toast import com.lagradost.cloudstream3.CommonActivity.showToast -import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_PLAY_FILE import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.download.DownloadClickEvent import com.lagradost.cloudstream3.ui.result.START_ACTION_LOAD_EP -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper @@ -56,15 +54,7 @@ object SearchHelper { } } SEARCH_ACTION_SHOW_METADATA -> { - if(!isTvSettings()) { // we only want this on phone as UI is not done yet on tv - (activity as? MainActivity?)?.apply { - loadPopup(callback.card) - } ?: kotlin.run { - showToast(activity, callback.card.name, Toast.LENGTH_SHORT) - } - } else { - showToast(activity, callback.card.name, Toast.LENGTH_SHORT) - } + showToast(activity, callback.card.name, Toast.LENGTH_SHORT) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt index 3447ee32..3afbb8c0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt @@ -1,14 +1,12 @@ package com.lagradost.cloudstream3.ui.search import android.content.Context -import android.graphics.drawable.Drawable import android.view.View import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView import androidx.cardview.widget.CardView import androidx.core.view.isVisible -import androidx.palette.graphics.Palette import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings @@ -43,7 +41,6 @@ object SearchResultBuilder { nextFocusBehavior: Boolean? = null, nextFocusUp: Int? = null, nextFocusDown: Int? = null, - colorCallback : ((Palette) -> Unit)? = null ) { val cardView: ImageView = itemView.imageView val cardText: TextView? = itemView.imageText @@ -103,7 +100,7 @@ object SearchResultBuilder { cardText?.isVisible = showTitle cardView.isVisible = true - if (!cardView.setImage(card.posterUrl, card.posterHeaders, colorCallback = colorCallback)) { + if (!cardView.setImage(card.posterUrl, card.posterHeaders)) { cardView.setImageResource(R.drawable.default_cover) } 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 1ef3cb55..f9627e46 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 @@ -157,28 +157,6 @@ class SettingsAccount : PreferenceFragmentCompat() { ) dialog.dismissSafe() } - - val displayedItems = listOf( - dialog.login_username_input, - dialog.login_email_input, - dialog.login_server_input, - dialog.login_password_input - ).filter { it.isVisible } - - displayedItems.foldRight(displayedItems.firstOrNull()) { item, previous -> - item?.id?.let { previous?.nextFocusDownId = it } - previous?.id?.let { item?.nextFocusUpId = it } - item - } - - displayedItems.firstOrNull()?.let { - dialog.create_account?.nextFocusDownId = it.id - it.nextFocusUpId = dialog.create_account.id - } - dialog.apply_btt?.id?.let { - displayedItems.lastOrNull()?.nextFocusDownId = it - } - dialog.text1?.text = api.name if (api.storesPasswordInPlainText) { 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 4aa859aa..8ea76cda 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 @@ -47,7 +47,7 @@ fun getCurrentLocale(context: Context): String { // Change locale settings in the app. // val dm = res.displayMetrics val conf = res.configuration - return conf?.locale?.toString() ?: "en" + return conf?.locale?.language ?: "en" } // idk, if you find a way of automating this it would be great @@ -55,51 +55,32 @@ fun getCurrentLocale(context: Context): String { // Emoji Character Encoding Data --> C/C++/Java Src // https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes leave blank for auto val appLanguages = arrayListOf( - /* begin language list */ - Triple("", "العربية", "ar"), - Triple("", "български", "bg"), - Triple("", "বাংলা", "bn"), - Triple("\uD83C\uDDE7\uD83C\uDDF7", "português brasileiro", "bp"), - Triple("", "čeština", "cs"), - Triple("", "Deutsch", "de"), - Triple("", "Ελληνικά", "el"), + Triple("", "Spanish", "es"), Triple("", "English", "en"), - Triple("", "Esperanto", "eo"), - Triple("", "español", "es"), - Triple("", "فارسی", "fa"), - Triple("", "français", "fr"), - Triple("", "हिन्दी", "hi"), - Triple("", "hrvatski", "hr"), - Triple("", "magyar", "hu"), - Triple("\uD83C\uDDEE\uD83C\uDDE9", "Bahasa Indonesia", "in"), - Triple("", "italiano", "it"), - Triple("\uD83C\uDDEE\uD83C\uDDF1", "עברית", "iw"), - Triple("", "日本語 (にほんご)", "ja"), - Triple("", "ಕನ್ನಡ", "kn"), - Triple("", "македонски", "mk"), - Triple("", "മലയാളം", "ml"), - Triple("", "bahasa Melayu", "ms"), - Triple("", "Nederlands", "nl"), - Triple("", "norsk nynorsk", "nn"), - Triple("", "norsk bokmål", "no"), - Triple("", "polski", "pl"), - Triple("\uD83C\uDDF5\uD83C\uDDF9", "português", "pt"), - Triple("\uD83E\uDD8D", "mmmm... monke", "qt"), - Triple("", "română", "ro"), - Triple("", "русский", "ru"), - Triple("", "slovenčina", "sk"), - Triple("", "Soomaaliga", "so"), - Triple("", "svenska", "sv"), - Triple("", "தமிழ்", "ta"), + Triple("", "Viet Nam", "vi"), + Triple("", "Dutch", "nl"), + Triple("", "French", "fr"), + Triple("", "Greek", "el"), + Triple("", "Swedish", "sv"), Triple("", "Tagalog", "tl"), - Triple("", "Türkçe", "tr"), - Triple("", "українська", "uk"), - Triple("", "اردو", "ur"), - Triple("", "Tiếng Việt", "vi"), - Triple("", "中文", "zh"), - Triple("\uD83C\uDDF9\uD83C\uDDFC", "文言", "zh-rTW"), -/* end language list */ -).sortedBy { it.second.lowercase() } //ye, we go alphabetical, so ppl don't put their lang on top + Triple("", "Polish", "pl"), + Triple("", "Hindi", "hi"), + Triple("", "Malayalam", "ml"), + Triple("", "Norsk", "no"), + Triple("", "German", "de"), + Triple("", "Arabic", "ar"), + Triple("", "Turkish", "tr"), + Triple("", "Macedonian", "mk"), + Triple("\uD83C\uDDF5\uD83C\uDDF9", "Portuguese", "pt"), + Triple("\uD83C\uDDE7\uD83C\uDDF7", "Brazilian Portuguese", "bp"), + Triple("", "Romanian", "ro"), + Triple("", "Italian", "it"), + Triple("", "Chinese", "zh"), + Triple("\uD83C\uDDEE\uD83C\uDDE9", "Indonesian", "in"), + Triple("", "Czech", "cs"), + Triple("", "Croatian", "hr"), + Triple("", "Bulgarian", "bg"), +).sortedBy { it.second } //ye, we go alphabetical, so ppl don't put their lang on top class SettingsGeneral : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -159,6 +140,9 @@ class SettingsGeneral : PreferenceFragmentCompat() { getPref(R.string.locale_key)?.setOnPreferenceClickListener { pref -> val tempLangs = appLanguages.toMutableList() + //if (beneneCount > 100) { + // tempLangs.add(Triple("\uD83E\uDD8D", "mmmm... monke", "mo")) + //} val current = getCurrentLocale(pref.context) val languageCodes = tempLangs.map { (_, _, iso) -> iso } val languageNames = tempLangs.map { (emoji, name, iso) -> @@ -315,12 +299,6 @@ class SettingsGeneral : PreferenceFragmentCompat() { } ?: emptyList() } - settingsManager.edit().putBoolean(getString(R.string.jsdelivr_proxy_key), getKey(getString(R.string.jsdelivr_proxy_key), false) ?: false).apply() - getPref(R.string.jsdelivr_proxy_key)?.setOnPreferenceChangeListener { _, newValue -> - setKey(getString(R.string.jsdelivr_proxy_key), newValue) - return@setOnPreferenceChangeListener true - } - getPref(R.string.download_path_key)?.setOnPreferenceClickListener { val dirs = getDownloadDirs() @@ -390,4 +368,4 @@ class SettingsGeneral : PreferenceFragmentCompat() { e.printStackTrace() } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt index e10a5a1a..33d41934 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt @@ -113,30 +113,6 @@ class SettingsPlayer : PreferenceFragmentCompat() { return@setOnPreferenceClickListener true } - getPref(R.string.quality_pref_mobile_data_key)?.setOnPreferenceClickListener { - val prefValues = Qualities.values().map { it.value }.reversed().toMutableList() - prefValues.remove(Qualities.Unknown.value) - - val prefNames = prefValues.map { Qualities.getStringByInt(it) } - - val currentQuality = - settingsManager.getInt( - getString(R.string.quality_pref_mobile_data_key), - Qualities.values().last().value - ) - - activity?.showBottomDialog( - prefNames.toList(), - prefValues.indexOf(currentQuality), - getString(R.string.watch_quality_pref_data), - true, - {}) { - settingsManager.edit().putInt(getString(R.string.quality_pref_mobile_data_key), prefValues[it]) - .apply() - } - return@setOnPreferenceClickListener true - } - getPref(R.string.player_pref_key)?.setOnPreferenceClickListener { val prefNames = resources.getStringArray(R.array.player_pref_names) val prefValues = resources.getIntArray(R.array.player_pref_values) 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 42a864a6..4371fc39 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 @@ -2,8 +2,6 @@ package com.lagradost.cloudstream3.ui.settings import android.os.Bundle import android.view.View -import androidx.navigation.NavOptions -import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.* @@ -18,7 +16,8 @@ import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard -import com.lagradost.cloudstream3.utils.UIHelper.navigate +import kotlin.reflect.jvm.internal.impl.descriptors.deserialization.PlatformDependentDeclarationFilter.All + class SettingsProviders : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -59,20 +58,6 @@ class SettingsProviders : PreferenceFragmentCompat() { return@setOnPreferenceClickListener true } - getPref(R.string.test_providers_key)?.setOnPreferenceClickListener { - // Somehow animations do not work without this. - val options = NavOptions.Builder() - .setEnterAnim(R.anim.enter_anim) - .setExitAnim(R.anim.exit_anim) - .setPopEnterAnim(R.anim.pop_enter) - .setPopExitAnim(R.anim.pop_exit) - .build() - - this@SettingsProviders.findNavController() - .navigate(R.id.navigation_test_providers, null, options) - true - } - getPref(R.string.prefer_media_type_key)?.setOnPreferenceClickListener { val names = enumValues().sorted().map { it.name } val default = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index f9ac3fee..6b19042a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -4,14 +4,12 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.os.Bundle -import android.os.TransactionTooLargeException import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceFragmentCompat -import androidx.preference.PreferenceManager -import com.lagradost.cloudstream3.CommonActivity.showToast +import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref @@ -21,7 +19,6 @@ import com.lagradost.cloudstream3.utils.BackupUtils.backup import com.lagradost.cloudstream3.utils.BackupUtils.restorePrompt import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate -import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.VideoDownloadManager @@ -84,17 +81,12 @@ class SettingsUpdates : PreferenceFragmentCompat() { dialog.text1?.text = text dialog.copy_btt?.setOnClickListener { - // Can crash on too much text - try { - val serviceClipboard = - (activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?) - ?: return@setOnClickListener - val clip = ClipData.newPlainText("logcat", text) - serviceClipboard.setPrimaryClip(clip) - dialog.dismissSafe(activity) - } catch (e: TransactionTooLargeException) { - showToast(activity, R.string.clipboard_too_large) - } + val serviceClipboard = + (activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?) + ?: return@setOnClickListener + val clip = ClipData.newPlainText("logcat", text) + serviceClipboard.setPrimaryClip(clip) + dialog.dismissSafe(activity) } dialog.clear_btt?.setOnClickListener { Runtime.getRuntime().exec("logcat -c") @@ -125,37 +117,11 @@ class SettingsUpdates : PreferenceFragmentCompat() { return@setOnPreferenceClickListener true } - getPref(R.string.apk_installer_key)?.setOnPreferenceClickListener { - val settingsManager = PreferenceManager.getDefaultSharedPreferences(it.context) - - val prefNames = resources.getStringArray(R.array.apk_installer_pref) - val prefValues = resources.getIntArray(R.array.apk_installer_values) - - val currentInstaller = - settingsManager.getInt(getString(R.string.apk_installer_key), 0) - - activity?.showBottomDialog( - prefNames.toList(), - prefValues.indexOf(currentInstaller), - getString(R.string.apk_installer_settings), - true, - {}) { - try { - settingsManager.edit() - .putInt(getString(R.string.apk_installer_key), prefValues[it]) - .apply() - } catch (e: Exception) { - logError(e) - } - } - return@setOnPreferenceClickListener true - } - getPref(R.string.manual_check_update_key)?.setOnPreferenceClickListener { ioSafe { if (activity?.runAutoUpdate(false) == false) { activity?.runOnUiThread { - showToast( + CommonActivity.showToast( activity, R.string.no_update_found, Toast.LENGTH_SHORT diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt index 7e60910d..49f40879 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt @@ -26,7 +26,6 @@ import com.lagradost.cloudstream3.ui.result.setText import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.utils.AppUtils.downloadAllPluginsDialog -import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe @@ -108,7 +107,7 @@ class ExtensionsFragment : Fragment() { ) .setPositiveButton(R.string.delete, dialogClickListener) .setNegativeButton(R.string.cancel, dialogClickListener) - .show().setDefaultFocus() + .show() } }) @@ -201,9 +200,7 @@ class ExtensionsFragment : Fragment() { val url = dialog.repo_url_input?.text?.toString() ?.let { it1 -> RepositoryManager.parseRepoUrl(it1) } if (url.isNullOrBlank()) { - main { - showToast(activity, R.string.error_invalid_data, Toast.LENGTH_SHORT) - } + showToast(activity, R.string.error_invalid_data, Toast.LENGTH_SHORT) } else { val fixedName = if (!name.isNullOrBlank()) name else RepositoryManager.parseRepository(url)?.name ?: "No name" diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt index d328d226..bacd26c8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt @@ -8,8 +8,6 @@ import androidx.appcompat.widget.SearchView import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings -import com.lagradost.cloudstream3.AllLanguagesName import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.mvvm.observe @@ -47,15 +45,6 @@ class PluginsFragment : Fragment() { pluginViewModel.languages = listOf() pluginViewModel.search(null) - // Filter by language set on preferred media - activity?.let { - val providerLangs = it.getApiProviderLangSettings().toList() - if (!providerLangs.contains(AllLanguagesName)) { - pluginViewModel.languages = mutableListOf("none") + providerLangs - //Log.i("DevDebug", "providerLang => ${pluginViewModel.languages.toJson()}") - } - } - val name = arguments?.getString(PLUGINS_BUNDLE_NAME) val url = arguments?.getString(PLUGINS_BUNDLE_URL) val isLocal = arguments?.getBoolean(PLUGINS_BUNDLE_LOCAL) == true @@ -143,7 +132,7 @@ class PluginsFragment : Fragment() { } observe(pluginViewModel.filteredPlugins) { (scrollToTop, list) -> - (plugin_recycler_view?.adapter as? PluginAdapter)?.updateList(list) + (plugin_recycler_view?.adapter as? PluginAdapter?)?.updateList(list) if (scrollToTop) plugin_recycler_view?.scrollToPosition(0) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt index 934f65bb..894a9331 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt @@ -9,7 +9,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.CommonActivity.showToast -import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.mvvm.launchSafe @@ -103,12 +102,11 @@ class PluginsViewModel : ViewModel() { ) } }.amap { (repo, metadata) -> - PluginManager.downloadPlugin( + PluginManager.downloadAndLoadPlugin( activity, metadata.url, metadata.internalName, - repo, - metadata.status != PROVIDER_STATUS_DOWN + repo ) }.main { list -> if (list.any { it }) { @@ -153,15 +151,12 @@ class PluginsViewModel : ViewModel() { val (success, message) = if (file.exists()) { PluginManager.deletePlugin(file) to R.string.plugin_deleted } else { - val isEnabled = plugin.second.status != PROVIDER_STATUS_DOWN - val message = if (isEnabled) R.string.plugin_loaded else R.string.plugin_downloaded - PluginManager.downloadPlugin( + PluginManager.downloadAndLoadPlugin( activity, metadata.url, metadata.name, - repo, - isEnabled - ) to message + repo + ) to R.string.plugin_loaded } runOnMainThread { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt deleted file mode 100644 index 34cd67cd..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.lagradost.cloudstream3.ui.settings.testing - -import android.os.Bundle -import androidx.fragment.app.Fragment -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.fragment.app.activityViewModels -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall -import com.lagradost.cloudstream3.mvvm.observe -import com.lagradost.cloudstream3.mvvm.observeNullable -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar -import kotlinx.android.synthetic.main.fragment_testing.* -import kotlinx.android.synthetic.main.view_test.* - - -class TestFragment : Fragment() { - - private val testViewModel: TestViewModel by activityViewModels() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - setUpToolbar(R.string.category_provider_test) - super.onViewCreated(view, savedInstanceState) - - provider_test_recycler_view?.adapter = TestResultAdapter( - mutableListOf() - ) - - testViewModel.init() - if (testViewModel.isRunningTest) { - provider_test?.setState(TestView.TestState.Running) - } - - observe(testViewModel.providerProgress) { (passed, failed, total) -> - provider_test?.setProgress(passed, failed, total) - } - - observeNullable(testViewModel.providerResults) { - normalSafeApiCall { - val newItems = it.sortedBy { api -> api.first.name } - (provider_test_recycler_view?.adapter as? TestResultAdapter)?.updateList( - newItems - ) - } - } - - provider_test?.setOnPlayButtonListener { state -> - when (state) { - TestView.TestState.Stopped -> testViewModel.stopTest() - TestView.TestState.Running -> testViewModel.startTest() - TestView.TestState.None -> testViewModel.startTest() - } - } - - if (isTrueTvSettings()) { - tests_play_pause?.isFocusableInTouchMode = true - tests_play_pause?.requestFocus() - } - - provider_test?.playPauseButton?.setOnFocusChangeListener { _, hasFocus -> - if (hasFocus) { - provider_test_appbar?.setExpanded(true, true) - } - } - - fun focusRecyclerView() { - // Hack to make it possible to focus the recyclerview. - if (isTrueTvSettings()) { - provider_test_recycler_view?.requestFocus() - provider_test_appbar?.setExpanded(false, true) - } - } - - provider_test?.setOnMainClick { - testViewModel.setFilterMethod(TestViewModel.ProviderFilter.All) - focusRecyclerView() - } - provider_test?.setOnFailedClick { - testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Failed) - focusRecyclerView() - } - provider_test?.setOnPassedClick { - testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Passed) - focusRecyclerView() - } - } - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - return inflater.inflate(R.layout.fragment_testing, container, false) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestResultAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestResultAdapter.kt deleted file mode 100644 index d04e2379..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestResultAdapter.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.lagradost.cloudstream3.ui.settings.testing - -import android.app.AlertDialog -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView -import androidx.core.content.ContextCompat -import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.MainAPI -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.mvvm.getAllMessages -import com.lagradost.cloudstream3.mvvm.getStackTracePretty -import com.lagradost.cloudstream3.utils.AppUtils -import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso -import com.lagradost.cloudstream3.utils.TestingUtils -import kotlinx.android.synthetic.main.provider_test_item.view.* - -class TestResultAdapter(override val items: MutableList>) : - AppUtils.DiffAdapter>(items) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return ProviderTestViewHolder( - LayoutInflater.from(parent.context) - .inflate(R.layout.provider_test_item, parent, false), - ) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (holder) { - is ProviderTestViewHolder -> { - val item = items[position] - holder.bind(item.first, item.second) - } - } - } - - inner class ProviderTestViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - private val languageText: TextView = itemView.lang_icon - private val providerTitle: TextView = itemView.main_text - private val statusText: TextView = itemView.passed_failed_marker - private val failDescription: TextView = itemView.fail_description - private val logButton: ImageView = itemView.action_button - - private fun String.lastLine(): String? { - return this.lines().lastOrNull { it.isNotBlank() } - } - - fun bind(api: MainAPI, result: TestingUtils.TestResultProvider) { - languageText.text = getFlagFromIso(api.lang) - providerTitle.text = api.name - - val (resultText, resultColor) = if (result.success) { - R.string.test_passed to R.color.colorTestPass - } else { - R.string.test_failed to R.color.colorTestFail - } - - statusText.setText(resultText) - statusText.setTextColor(ContextCompat.getColor(itemView.context, resultColor)) - - val stackTrace = result.exception?.getStackTracePretty(false)?.ifBlank { null } - val messages = result.exception?.getAllMessages()?.ifBlank { null } - val fullLog = - result.log + (messages?.let { "\n\n$it" } ?: "") + (stackTrace?.let { "\n\n$it" } ?: "") - - failDescription.text = messages?.lastLine() ?: result.log.lastLine() - - logButton.setOnClickListener { - val builder: AlertDialog.Builder = - AlertDialog.Builder(it.context, R.style.AlertDialogCustom) - builder.setMessage(fullLog) - .setTitle(R.string.test_log) - .show() - } - } - } - - -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestView.kt deleted file mode 100644 index 26513f4a..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestView.kt +++ /dev/null @@ -1,119 +0,0 @@ -package com.lagradost.cloudstream3.ui.settings.testing - -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.widget.TextView -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.cardview.widget.CardView -import androidx.core.content.ContextCompat -import androidx.core.view.isVisible -import androidx.core.widget.ContentLoadingProgressBar -import com.google.android.material.button.MaterialButton -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.utils.AppUtils.animateProgressTo - -class TestView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : CardView(context, attrs) { - enum class TestState(@StringRes val stringRes: Int, @DrawableRes val icon: Int) { - None(R.string.start, R.drawable.ic_baseline_play_arrow_24), - - // Paused(R.string.resume, R.drawable.ic_baseline_play_arrow_24), - Stopped(R.string.restart, R.drawable.ic_baseline_play_arrow_24), - Running(R.string.stop, R.drawable.pause_to_play), - } - - var mainSection: View? = null - var testsPassedSection: View? = null - var testsFailedSection: View? = null - - var mainSectionText: TextView? = null - var mainSectionHeader: TextView? = null - var testsPassedSectionText: TextView? = null - var testsFailedSectionText: TextView? = null - var totalProgressBar: ContentLoadingProgressBar? = null - - var playPauseButton: MaterialButton? = null - var stateListener: (TestState) -> Unit = {} - - private var state = TestState.None - - init { - LayoutInflater.from(context).inflate(R.layout.view_test, this, true) - - mainSection = findViewById(R.id.main_test_section) - testsPassedSection = findViewById(R.id.passed_test_section) - testsFailedSection = findViewById(R.id.failed_test_section) - - mainSectionHeader = findViewById(R.id.main_test_header) - mainSectionText = findViewById(R.id.main_test_section_progress) - testsPassedSectionText = findViewById(R.id.passed_test_section_progress) - testsFailedSectionText = findViewById(R.id.failed_test_section_progress) - - totalProgressBar = findViewById(R.id.test_total_progress) - playPauseButton = findViewById(R.id.tests_play_pause) - - attrs?.let { - val typedArray = context.obtainStyledAttributes(it, R.styleable.TestView) - val headerText = typedArray.getString(R.styleable.TestView_header_text) - mainSectionHeader?.text = headerText - typedArray.recycle() - } - - playPauseButton?.setOnClickListener { - val newState = when (state) { - TestState.None -> TestState.Running - TestState.Running -> TestState.Stopped - TestState.Stopped -> TestState.Running - } - setState(newState) - } - } - - fun setOnPlayButtonListener(listener: (TestState) -> Unit) { - stateListener = listener - } - - fun setState(newState: TestState) { - state = newState - stateListener.invoke(newState) - playPauseButton?.setText(newState.stringRes) - playPauseButton?.icon = ContextCompat.getDrawable(context, newState.icon) - } - - fun setProgress(passed: Int, failed: Int, total: Int?) { - val totalProgress = passed + failed - mainSectionText?.text = "$totalProgress / ${total?.toString() ?: "?"}" - testsPassedSectionText?.text = passed.toString() - testsFailedSectionText?.text = failed.toString() - - totalProgressBar?.max = (total ?: 0) * 1000 - totalProgressBar?.animateProgressTo(totalProgress * 1000) - - totalProgressBar?.isVisible = !(totalProgress == 0 || (total ?: 0) == 0) - if (totalProgress == total) { - setState(TestState.Stopped) - } - } - - fun setMainHeader(@StringRes header: Int) { - mainSectionHeader?.setText(header) - } - - fun setOnMainClick(listener: OnClickListener) { - mainSection?.setOnClickListener(listener) - } - - fun setOnPassedClick(listener: OnClickListener) { - testsPassedSection?.setOnClickListener(listener) - } - - fun setOnFailedClick(listener: OnClickListener) { - testsFailedSection?.setOnClickListener(listener) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt deleted file mode 100644 index 2e05baff..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestViewModel.kt +++ /dev/null @@ -1,108 +0,0 @@ -package com.lagradost.cloudstream3.ui.settings.testing - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import com.lagradost.cloudstream3.APIHolder -import com.lagradost.cloudstream3.MainAPI -import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf -import com.lagradost.cloudstream3.utils.TestingUtils -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel - -class TestViewModel : ViewModel() { - data class TestProgress( - val passed: Int, - val failed: Int, - val total: Int - ) - - enum class ProviderFilter { - All, - Passed, - Failed - } - - private val _providerProgress = MutableLiveData(null) - val providerProgress: LiveData = _providerProgress - - private val _providerResults = - MutableLiveData>>( - emptyList() - ) - - val providerResults: LiveData>> = - _providerResults - - private var scope: CoroutineScope? = null - val isRunningTest - get() = scope != null - - private var filter = ProviderFilter.All - private val providers = threadSafeListOf>() - private var passed = 0 - private var failed = 0 - private var total = 0 - - private fun updateProgress() { - _providerProgress.postValue(TestProgress(passed, failed, total)) - postProviders() - } - - private fun postProviders() { - synchronized(providers) { - val filtered = when (filter) { - ProviderFilter.All -> providers - ProviderFilter.Passed -> providers.filter { it.second.success } - ProviderFilter.Failed -> providers.filter { !it.second.success } - } - _providerResults.postValue(filtered) - } - } - - fun setFilterMethod(filter: ProviderFilter) { - if (this.filter == filter) return - this.filter = filter - postProviders() - } - - private fun addProvider(api: MainAPI, results: TestingUtils.TestResultProvider) { - synchronized(providers) { - val index = providers.indexOfFirst { it.first == api } - if (index == -1) { - providers.add(api to results) - if (results.success) passed++ else failed++ - } else { - providers[index] = api to results - } - updateProgress() - } - } - - fun init() { - val apis = APIHolder.allProviders - total = apis.size - updateProgress() - } - - fun startTest() { - scope = CoroutineScope(Dispatchers.Default) - - val apis = APIHolder.allProviders - total = apis.size - failed = 0 - passed = 0 - providers.clear() - updateProgress() - - TestingUtils.getDeferredProviderTests(scope ?: return, apis, ::println) { api, result -> - addProvider(api, result) - } - } - - fun stopTest() { - scope?.cancel() - scope = null - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt index 50fb37d6..bc9bfb1f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt @@ -67,7 +67,7 @@ class SetupFragmentLayout : Fragment() { crash_reporting_text?.text = getText(text) } - val enableCrashReporting = !settingsManager.getBoolean(ACRA.PREF_DISABLE_ACRA, true) + val enableCrashReporting = !settingsManager.getBoolean(ACRA.PREF_DISABLE_ACRA, false) acra_switch.isChecked = enableCrashReporting crash_reporting_text.text = getText( diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt index a76b62fd..cf3fbfde 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -1,12 +1,11 @@ package com.lagradost.cloudstream3.utils -import android.animation.ObjectAnimator import android.annotation.SuppressLint import android.app.Activity import android.app.Activity.RESULT_CANCELED -import android.app.NotificationChannel -import android.app.NotificationManager -import android.content.* +import android.content.ContentValues +import android.content.Context +import android.content.Intent import android.content.pm.PackageManager import android.database.Cursor import android.media.AudioAttributes @@ -16,63 +15,56 @@ import android.media.tv.TvContract.Channels.COLUMN_INTERNAL_PROVIDER_ID import android.net.ConnectivityManager import android.net.NetworkCapabilities import android.net.Uri -import android.os.* +import android.os.Build +import android.os.Environment +import android.os.ParcelFileDescriptor import android.provider.MediaStore import android.text.Spanned import android.util.Log -import android.view.animation.DecelerateInterpolator import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.annotation.WorkerThread import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.text.HtmlCompat import androidx.core.text.toSpanned -import androidx.core.widget.ContentLoadingProgressBar import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import androidx.tvprovider.media.tv.* +import androidx.tvprovider.media.tv.PreviewChannelHelper +import androidx.tvprovider.media.tv.TvContractCompat +import androidx.tvprovider.media.tv.WatchNextProgram import androidx.tvprovider.media.tv.WatchNextProgram.fromCursor -import androidx.viewpager2.widget.ViewPager2 import com.fasterxml.jackson.module.kotlin.readValue import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.cast.framework.CastState import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.common.wrappers.Wrappers -import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.plugins.RepositoryManager -import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching import com.lagradost.cloudstream3.ui.WebviewFragment import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings -import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel.Companion.downloadAll import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main -import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds -import com.lagradost.cloudstream3.utils.DataStoreHelper.getLastWatched import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.toClassDir import com.lagradost.cloudstream3.utils.JsUnpacker.Companion.load import com.lagradost.cloudstream3.utils.UIHelper.navigate -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import okhttp3.Cache import java.io.* import java.net.URL import java.net.URLDecoder -import kotlin.system.measureTimeMillis object AppUtils { fun RecyclerView.setMaxViewPoolSize(maxViewTypeId: Int, maxPoolSize: Int) { @@ -87,19 +79,6 @@ object AppUtils { return if (layoutManager == null || adapter == null) false else layoutManager.findLastCompletelyVisibleItemPosition() < adapter.itemCount - 7 // bit more than 1 to make it more seamless } - fun BottomSheetDialog?.ownHide() { - this?.hide() - } - - fun BottomSheetDialog?.ownShow() { - // the reason for this is because show has a shitty animation we don't want - this?.window?.setWindowAnimations(-1) - this?.show() - Handler(Looper.getMainLooper()).postDelayed({ - this?.window?.setWindowAnimations(R.style.Animation_Design_BottomSheetDialog) - }, 200) - } - //fun Context.deleteFavorite(data: SearchResponse) { // if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return // normalSafeApiCall { @@ -131,8 +110,7 @@ object AppUtils { @SuppressLint("RestrictedApi") private fun buildWatchNextProgramUri( context: Context, - card: DataStoreHelper.ResumeWatchingResult, - resumeWatching: VideoDownloadHelper.ResumeWatching? + card: DataStoreHelper.ResumeWatchingResult ): WatchNextProgram { val isSeries = card.type?.isMovieType() == false val title = if (isSeries) { @@ -151,18 +129,15 @@ object AppUtils { .setWatchNextType(TvContractCompat.WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE) .setTitle(title) .setPosterArtUri(Uri.parse(card.posterUrl)) - .setIntentUri(Uri.parse(card.id?.let { - "$appStringResumeWatching://$it" - } ?: card.url)) + .setIntentUri(Uri.parse(card.url)) //TODO FIX intent .setInternalProviderId(card.url) - .setLastEngagementTimeUtcMillis( - resumeWatching?.updateTime ?: System.currentTimeMillis() - ) + //.setLastEngagementTimeUtcMillis(System.currentTimeMillis()) card.watchPos?.let { builder.setDurationMillis(it.duration.toInt()) builder.setLastPlaybackPositionMillis(it.position.toInt()) } + // .setLastEngagementTimeUtcMillis() //TODO if (isSeries) card.episode?.let { @@ -172,69 +147,6 @@ object AppUtils { return builder.build() } - // https://stackoverflow.com/a/67441735/13746422 - fun ViewPager2.reduceDragSensitivity(f: Int = 4) { - val recyclerViewField = ViewPager2::class.java.getDeclaredField("mRecyclerView") - recyclerViewField.isAccessible = true - val recyclerView = recyclerViewField.get(this) as RecyclerView - - val touchSlopField = RecyclerView::class.java.getDeclaredField("mTouchSlop") - touchSlopField.isAccessible = true - val touchSlop = touchSlopField.get(recyclerView) as Int - touchSlopField.set(recyclerView, touchSlop * f) // "8" was obtained experimentally - } - - fun ContentLoadingProgressBar?.animateProgressTo(to: Int) { - if (this == null) return - val animation: ObjectAnimator = ObjectAnimator.ofInt( - this, - "progress", - this.progress, - to - ) - animation.duration = 500 - animation.setAutoCancel(true) - animation.interpolator = DecelerateInterpolator() - animation.start() - } - - fun Context.createNotificationChannel(channelId: String, channelName: String, description: String) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val importance = NotificationManager.IMPORTANCE_DEFAULT - val channel = - NotificationChannel(channelId, channelName, importance).apply { - this.description = description - } - - // Register the channel with the system. - val notificationManager: NotificationManager = - this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - notificationManager.createNotificationChannel(channel) - } - } - - @SuppressLint("RestrictedApi") - fun getAllWatchNextPrograms(context: Context): Set { - val COLUMN_WATCH_NEXT_ID_INDEX = 0 - val cursor = context.contentResolver.query( - TvContractCompat.WatchNextPrograms.CONTENT_URI, - WatchNextProgram.PROJECTION, - /* selection = */ null, - /* selectionArgs = */ null, - /* sortOrder = */ null - ) - val set = mutableSetOf() - cursor?.use { - if (it.moveToFirst()) { - do { - set.add(cursor.getLong(COLUMN_WATCH_NEXT_ID_INDEX)) - } while (it.moveToNext()) - } - } - return set - } - /** * Find the Watch Next program for given id. * Returns the first instance available. @@ -252,7 +164,7 @@ object AppUtils { WatchNextProgram.PROJECTION, /* selection = */ null, /* selectionArgs = */ null, - /* sortOrder = */ null + /* sortOrder= */ null ) cursor?.use { if (it.moveToFirst()) { @@ -283,32 +195,17 @@ object AppUtils { } } - /** Prevents losing data when removing and adding simultaneously */ - private val continueWatchingLock = Mutex() - // https://github.com/googlearchive/leanback-homescreen-channels/blob/master/app/src/main/java/com/google/android/tvhomescreenchannels/SampleTvProvider.java @SuppressLint("RestrictedApi") @WorkerThread - suspend fun Context.addProgramsToContinueWatching(data: List) { + fun Context.addProgramsToContinueWatching(data: List) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return val context = this - continueWatchingLock.withLock { - // A way to get all last watched timestamps - val timeStampHashMap = HashMap() - getAllResumeStateIds()?.forEach { id -> - val lastWatched = getLastWatched(id) ?: return@forEach - timeStampHashMap[lastWatched.parentId] = lastWatched - } - - val currentProgramIds = data.mapNotNull { episodeInfo -> + ioSafe { + data.forEach { episodeInfo -> try { - val customId = "${episodeInfo.id}|${episodeInfo.apiName}|${episodeInfo.url}" - val (program, id) = getWatchNextProgramByVideoId(customId, context) - val nextProgram = buildWatchNextProgramUri( - context, - episodeInfo, - timeStampHashMap[episodeInfo.id] - ) + val (program, id) = getWatchNextProgramByVideoId(episodeInfo.url, context) + val nextProgram = buildWatchNextProgramUri(context, episodeInfo) // If the program is already in the Watch Next row, update it if (program != null && id != null) { @@ -316,25 +213,13 @@ object AppUtils { nextProgram, id, ) - id } else { PreviewChannelHelper(context) .publishWatchNextProgram(nextProgram) } } catch (e: Exception) { logError(e) - null } - }.toSet() - - val allOldPrograms = getAllWatchNextPrograms(context) - currentProgramIds - - // Ensures synced watch next progress by deleting all old programs. - allOldPrograms.forEach { - context.contentResolver.delete( - TvContractCompat.buildWatchNextProgramUri(it), - null, null - ) } } } @@ -379,46 +264,6 @@ object AppUtils { } } - abstract class DiffAdapter( - open val items: MutableList, - val comparison: (first: T, second: T) -> Boolean = { first, second -> - first.hashCode() == second.hashCode() - } - ) : - RecyclerView.Adapter() { - override fun getItemCount(): Int { - return items.size - } - - fun updateList(newList: List) { - val diffResult = DiffUtil.calculateDiff( - GenericDiffCallback(this.items, newList) - ) - - items.clear() - items.addAll(newList) - - diffResult.dispatchUpdatesTo(this) - } - - inner class GenericDiffCallback( - private val oldList: List, - private val newList: List - ) : - DiffUtil.Callback() { - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = - comparison(oldList[oldItemPosition], newList[newItemPosition]) - - override fun getOldListSize() = oldList.size - - override fun getNewListSize() = newList.size - - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = - oldList[oldItemPosition] == newList[newItemPosition] - } - } - - fun Activity.downloadAllPluginsDialog(repositoryUrl: String, repositoryName: String) { runOnUiThread { val context = this @@ -434,9 +279,9 @@ object AppUtils { downloadAll(context, repositoryUrl, null) } - setNegativeButton(R.string.no) { _, _ -> } + setNegativeButton(R.string.cancel) { _, _ -> } } - builder.show().setDefaultFocus() + builder.show() } } @@ -491,12 +336,6 @@ object AppUtils { } } - fun Context.isNetworkAvailable(): Boolean { - val manager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val activeNetworkInfo = manager.activeNetworkInfo - return activeNetworkInfo != null && activeNetworkInfo.isConnected || manager.allNetworkInfo?.any { it.isConnected } ?: false - } - fun splitQuery(url: URL): Map { val queryPairs: MutableMap = LinkedHashMap() val query: String = url.query @@ -665,17 +504,6 @@ object AppUtils { return false } - /** - * Sets the focus to the negative button when in TV and Emulator layout. - **/ - fun AlertDialog.setDefaultFocus(buttonFocus: Int = DialogInterface.BUTTON_NEGATIVE) { - if (!isTvSettings()) return - this.getButton(buttonFocus).run { - isFocusableInTouchMode = true - requestFocus() - } - } - // Copied from https://github.com/videolan/vlc-android/blob/master/application/vlc-android/src/org/videolan/vlc/util/FileUtils.kt @SuppressLint("Range") fun Context.getUri(data: Uri?): Uri? { @@ -776,13 +604,8 @@ object AppUtils { return networkInfo.any { conManager.getNetworkCapabilities(it) ?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true - } && - !networkInfo.any { - conManager.getNetworkCapabilities(it) - ?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true - } } - + } private fun Activity?.cacheClass(clazz: String?) { clazz?.let { c -> @@ -826,4 +649,4 @@ object AppUtils { } return currentAudioFocusRequest } -} +} \ No newline at end of file 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 2318fda6..338b1ed2 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.annotation.SuppressLint import android.content.ContentValues import android.content.Context import android.net.Uri @@ -9,7 +8,6 @@ import android.provider.MediaStore import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts -import androidx.annotation.WorkerThread import androidx.fragment.app.FragmentActivity import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.module.kotlin.readValue @@ -19,17 +17,17 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.plugins.PLUGINS_KEY import com.lagradost.cloudstream3.plugins.PLUGINS_KEY_LOCAL import com.lagradost.cloudstream3.syncproviders.providers.AniListApi.Companion.ANILIST_CACHED_LIST +import com.lagradost.cloudstream3.syncproviders.providers.AniListApi.Companion.ANILIST_SHOULD_UPDATE_LIST import com.lagradost.cloudstream3.syncproviders.providers.AniListApi.Companion.ANILIST_TOKEN_KEY import com.lagradost.cloudstream3.syncproviders.providers.AniListApi.Companion.ANILIST_UNIXTIME_KEY import com.lagradost.cloudstream3.syncproviders.providers.AniListApi.Companion.ANILIST_USER_KEY import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_CACHED_LIST import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_REFRESH_TOKEN_KEY +import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_SHOULD_UPDATE_LIST import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_TOKEN_KEY import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_UNIXTIME_KEY import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_USER_KEY import com.lagradost.cloudstream3.syncproviders.providers.OpenSubtitlesApi.Companion.OPEN_SUBTITLES_USER_KEY -import com.lagradost.cloudstream3.utils.Coroutines.ioSafe -import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getDefaultSharedPrefs import com.lagradost.cloudstream3.utils.DataStore.getSharedPrefs import com.lagradost.cloudstream3.utils.DataStore.mapper @@ -53,10 +51,12 @@ object BackupUtils { // When sharing backup we do not want to transfer what is essentially the password ANILIST_TOKEN_KEY, ANILIST_CACHED_LIST, + ANILIST_SHOULD_UPDATE_LIST, ANILIST_UNIXTIME_KEY, ANILIST_USER_KEY, MAL_TOKEN_KEY, MAL_REFRESH_TOKEN_KEY, + MAL_SHOULD_UPDATE_LIST, MAL_CACHED_LIST, MAL_UNIXTIME_KEY, MAL_USER_KEY, @@ -74,7 +74,7 @@ object BackupUtils { return !nonTransferableKeys.contains(this) } - private var restoreFileSelector: ActivityResultLauncher>? = null + var restoreFileSelector: ActivityResultLauncher>? = null // Kinda hack, but I couldn't think of a better way data class BackupVars( @@ -91,7 +91,6 @@ object BackupUtils { @JsonProperty("settings") val settings: BackupVars ) - @Suppress("UNCHECKED_CAST") fun Context.getBackup(): BackupFile { val allData = getSharedPrefs().all.filter { it.key.isTransferable() } val allSettings = getDefaultSharedPrefs().all.filter { it.key.isTransferable() } @@ -120,7 +119,6 @@ object BackupUtils { ) } - @WorkerThread fun Context.restore( backupFile: BackupFile, restoreSettings: Boolean, @@ -145,66 +143,64 @@ object BackupUtils { } } - @SuppressLint("SimpleDateFormat") fun FragmentActivity.backup() { try { - if (!checkWrite()) { + if (checkWrite()) { + val subDir = getBasePath().first + val date = SimpleDateFormat("yyyy_MM_dd_HH_mm").format(Date(currentTimeMillis())) + val ext = "json" + val displayName = "CS3_Backup_${date}" + val backupFile = getBackup() + + val steam = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && subDir?.isDownloadDir() == true) { + val cr = this.contentResolver + val contentUri = + MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) // USE INSTEAD OF MediaStore.Downloads.EXTERNAL_CONTENT_URI + //val currentMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) + + val newFile = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, displayName) + put(MediaStore.MediaColumns.TITLE, displayName) + // While it a json file we store as txt because not + // all file managers support mimetype json + put(MediaStore.MediaColumns.MIME_TYPE, "text/plain") + //put(MediaStore.MediaColumns.RELATIVE_PATH, folder) + } + + val newFileUri = cr.insert( + contentUri, + newFile + ) ?: throw IOException("Error creating file uri") + cr.openOutputStream(newFileUri, "w") + ?: throw IOException("Error opening stream") + } else { + val fileName = "$displayName.$ext" + val rFile = subDir?.findFile(fileName) + if (rFile?.exists() == true) { + rFile.delete() + } + val file = + subDir?.createFile(fileName) + ?: throw IOException("Error creating file") + if (!file.exists()) throw IOException("File does not exist") + file.openOutputStream() + } + + val printStream = PrintWriter(steam) + printStream.print(mapper.writeValueAsString(backupFile)) + printStream.close() + + showToast( + this, + R.string.backup_success, + Toast.LENGTH_LONG + ) + } else { showToast(this, getString(R.string.backup_failed), Toast.LENGTH_LONG) requestRW() return } - - val subDir = getBasePath().first - val date = SimpleDateFormat("yyyy_MM_dd_HH_mm").format(Date(currentTimeMillis())) - val ext = "json" - val displayName = "CS3_Backup_${date}" - val backupFile = getBackup() - - val steam = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q - && subDir?.isDownloadDir() == true - ) { - val cr = this.contentResolver - val contentUri = - MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) // USE INSTEAD OF MediaStore.Downloads.EXTERNAL_CONTENT_URI - //val currentMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - - val newFile = ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, displayName) - put(MediaStore.MediaColumns.TITLE, displayName) - // While it a json file we store as txt because not - // all file managers support mimetype json - put(MediaStore.MediaColumns.MIME_TYPE, "text/plain") - //put(MediaStore.MediaColumns.RELATIVE_PATH, folder) - } - - val newFileUri = cr.insert( - contentUri, - newFile - ) ?: throw IOException("Error creating file uri") - cr.openOutputStream(newFileUri, "w") - ?: throw IOException("Error opening stream") - } else { - val fileName = "$displayName.$ext" - val rFile = subDir?.findFile(fileName) - if (rFile?.exists() == true) { - rFile.delete() - } - val file = - subDir?.createFile(fileName) - ?: throw IOException("Error creating file") - if (!file.exists()) throw IOException("File does not exist") - file.openOutputStream() - } - - val printStream = PrintWriter(steam) - printStream.print(mapper.writeValueAsString(backupFile)) - printStream.close() - - showToast( - this, - R.string.backup_success, - Toast.LENGTH_LONG - ) } catch (e: Exception) { logError(e) try { @@ -223,29 +219,31 @@ object BackupUtils { try { restoreFileSelector = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? -> - if (uri == null) return@registerForActivityResult - val activity = this - ioSafe { - try { - val input = activity.contentResolver.openInputStream(uri) - ?: return@ioSafe + this.let { activity -> + uri?.let { + try { + val input = + activity.contentResolver.openInputStream(uri) + ?: return@registerForActivityResult - val restoredValue = - mapper.readValue(input) - - activity.restore( - restoredValue, - restoreSettings = true, - restoreDataStore = true - ) - activity.runOnUiThread { activity.recreate() } - } catch (e: Exception) { - logError(e) - main { // smth can fail in .format - showToast( - activity, - getString(R.string.restore_failed_format).format(e.toString()) + val restoredValue = + mapper.readValue(input) + activity.restore( + restoredValue, + restoreSettings = true, + restoreDataStore = true ) + activity.recreate() + } catch (e: Exception) { + logError(e) + try { // smth can fail in .format + showToast( + activity, + getString(R.string.restore_failed_format).format(e.toString()) + ) + } catch (e: Exception) { + logError(e) + } } } } @@ -266,7 +264,6 @@ object BackupUtils { "application/json", "unknown/unknown", "content/unknown", - "application/octet-stream", ) ) } catch (e: Exception) { 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 516cd990..46c29e3f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -1,23 +1,20 @@ package com.lagradost.cloudstream3.utils import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys import com.lagradost.cloudstream3.AcraApplication.Companion.setKey -import com.lagradost.cloudstream3.syncproviders.AccountManager -import com.lagradost.cloudstream3.syncproviders.SyncAPI +import com.lagradost.cloudstream3.DubStatus +import com.lagradost.cloudstream3.SearchQuality +import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.ui.WatchType -import com.lagradost.cloudstream3.ui.result.VideoWatchState const val VIDEO_POS_DUR = "video_pos_dur" -const val VIDEO_WATCH_STATE = "video_watch_state" const val RESULT_WATCH_STATE = "result_watch_state" const val RESULT_WATCH_STATE_DATA = "result_watch_state_data" -const val RESULT_SUBSCRIBED_STATE_DATA = "result_subscribed_state_data" const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes const val RESULT_RESUME_WATCHING_OLD = "result_resume_watching" const val RESULT_RESUME_WATCHING_HAS_MIGRATED = "result_resume_watching_migrated" @@ -40,37 +37,6 @@ object DataStoreHelper { return this } - /** - * Used to display notifications on new episodes and posters in library. - **/ - data class SubscribedData( - @JsonProperty("id") override var id: Int?, - @JsonProperty("subscribedTime") val bookmarkedTime: Long, - @JsonProperty("latestUpdatedTime") val latestUpdatedTime: Long, - @JsonProperty("lastSeenEpisodeCount") val lastSeenEpisodeCount: Map, - @JsonProperty("name") override val name: String, - @JsonProperty("url") override val url: String, - @JsonProperty("apiName") override val apiName: String, - @JsonProperty("type") override var type: TvType? = null, - @JsonProperty("posterUrl") override var posterUrl: String?, - @JsonProperty("year") val year: Int?, - @JsonProperty("quality") override var quality: SearchQuality? = null, - @JsonProperty("posterHeaders") override var posterHeaders: Map? = null, - ) : SearchResponse { - fun toLibraryItem(): SyncAPI.LibraryItem? { - return SyncAPI.LibraryItem( - name, - url, - id?.toString() ?: return null, - null, - null, - null, - latestUpdatedTime, - apiName, type, posterUrl, posterHeaders, quality, this.id - ) - } - } - data class BookmarkedData( @JsonProperty("id") override var id: Int?, @JsonProperty("bookmarkedTime") val bookmarkedTime: Long, @@ -83,20 +49,7 @@ object DataStoreHelper { @JsonProperty("year") val year: Int?, @JsonProperty("quality") override var quality: SearchQuality? = null, @JsonProperty("posterHeaders") override var posterHeaders: Map? = null, - ) : SearchResponse { - fun toLibraryItem(id: String): SyncAPI.LibraryItem { - return SyncAPI.LibraryItem( - name, - url, - id, - null, - null, - null, - latestUpdatedTime, - apiName, type, posterUrl, posterHeaders, quality, this.id - ) - } - } + ) : SearchResponse data class ResumeWatchingResult( @JsonProperty("name") override val name: String, @@ -104,7 +57,9 @@ object DataStoreHelper { @JsonProperty("apiName") override val apiName: String, @JsonProperty("type") override var type: TvType? = null, @JsonProperty("posterUrl") override var posterUrl: String?, + @JsonProperty("watchPos") val watchPos: PosDur?, + @JsonProperty("id") override var id: Int?, @JsonProperty("parentId") val parentId: Int?, @JsonProperty("episode") val episode: Int?, @@ -114,9 +69,6 @@ object DataStoreHelper { @JsonProperty("posterHeaders") override var posterHeaders: Map? = null, ) : SearchResponse - /** - * A datastore wide account for future implementations of a multiple account system - **/ private var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION fun getAllWatchStateIds(): List? { @@ -223,7 +175,6 @@ object DataStoreHelper { fun setBookmarkedData(id: Int?, data: BookmarkedData) { if (id == null) return setKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString(), data) - AccountManager.localListApi.requireLibraryRefresh = true } fun getBookmarkedData(id: Int?): BookmarkedData? { @@ -231,41 +182,6 @@ object DataStoreHelper { return getKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString()) } - fun getAllSubscriptions(): List { - return getKeys("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA")?.mapNotNull { - getKey(it) - } ?: emptyList() - } - - fun removeSubscribedData(id: Int?) { - if (id == null) return - AccountManager.localListApi.requireLibraryRefresh = true - removeKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString()) - } - - /** - * Set new seen episodes and update time - **/ - fun updateSubscribedData(id: Int?, data: SubscribedData?, episodeResponse: EpisodeResponse?) { - if (id == null || data == null || episodeResponse == null) return - val newData = data.copy( - latestUpdatedTime = unixTimeMS, - lastSeenEpisodeCount = episodeResponse.getLatestEpisodes() - ) - setKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString(), newData) - } - - fun setSubscribedData(id: Int?, data: SubscribedData) { - if (id == null) return - setKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString(), data) - AccountManager.localListApi.requireLibraryRefresh = true - } - - fun getSubscribedData(id: Int?): SubscribedData? { - if (id == null) return null - return getKey("$currentAccount/$RESULT_SUBSCRIBED_STATE_DATA", id.toString()) - } - fun setViewPos(id: Int?, pos: Long, dur: Long) { if (id == null) return if (dur < 30_000) return // too short @@ -277,22 +193,6 @@ object DataStoreHelper { return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null) } - fun getVideoWatchState(id: Int?): VideoWatchState? { - if (id == null) return null - return getKey("$currentAccount/$VIDEO_WATCH_STATE", id.toString(), null) - } - - fun setVideoWatchState(id: Int?, watchState: VideoWatchState) { - if (id == null) return - - // None == No key - if (watchState == VideoWatchState.None) { - removeKey("$currentAccount/$VIDEO_WATCH_STATE", id.toString()) - } else { - setKey("$currentAccount/$VIDEO_WATCH_STATE", id.toString(), watchState) - } - } - fun getDub(id: Int): DubStatus? { return DubStatus.values() .getOrNull(getKey("$currentAccount/$RESULT_DUB", id.toString(), -1) ?: -1) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt index b03c9fb7..1a1afb68 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -52,7 +52,7 @@ data class ExtractorLinkPlayList( ) -open class ExtractorLink constructor( +open class ExtractorLink( open val source: String, open val name: String, override val url: String, @@ -62,24 +62,7 @@ open class ExtractorLink constructor( override val headers: Map = mapOf(), /** Used for getExtractorVerifierJob() */ open val extractorData: String? = null, - open val isDash: Boolean = false, ) : VideoDownloadManager.IDownloadableMinimum { - /** - * Old constructor without isDash, allows for backwards compatibility with extensions. - * Should be removed after all extensions have updated their cloudstream.jar - **/ - constructor( - source: String, - name: String, - url: String, - referer: String, - quality: Int, - isM3u8: Boolean = false, - headers: Map = mapOf(), - /** Used for getExtractorVerifierJob() */ - extractorData: String? = null - ) : this(source, name, url, referer, quality, isM3u8, headers, extractorData, false) - override fun toString(): String { return "ExtractorLink(name=$name, url=$url, referer=$referer, isM3u8=$isM3u8)" } @@ -222,8 +205,6 @@ val extractorApis: MutableList = arrayListOf( VideovardSX(), Mp4Upload(), StreamTape(), - StreamTapeNet(), - ShaveTape(), //mixdrop extractors MixDropBz(), @@ -246,7 +227,6 @@ val extractorApis: MutableList = arrayListOf( StreamSB8(), StreamSB9(), StreamSB10(), - StreamSB11(), SBfull(), // Streamhub(), cause Streamhub2() works Streamhub2(), @@ -272,19 +252,15 @@ val extractorApis: MutableList = arrayListOf( // WatchSB(), 'cause StreamSB.kt works Uqload(), Uqload1(), - Uqload2(), Evoload(), Evoload1(), VoeExtractor(), UpstreamExtractor(), Tomatomatela(), - TomatomatelalClub(), Cinestart(), OkRu(), OkRuHttps(), - Okrulink(), - Sendvid(), // dood extractors DoodCxExtractor(), @@ -296,7 +272,6 @@ val extractorApis: MutableList = arrayListOf( DoodShExtractor(), DoodWatchExtractor(), DoodWfExtractor(), - DoodYtExtractor(), AsianLoad(), @@ -312,7 +287,6 @@ val extractorApis: MutableList = arrayListOf( Supervideo(), GuardareStream(), CineGrabber(), - Vanfem(), // StreamSB.kt works // SBPlay(), @@ -343,9 +317,6 @@ val extractorApis: MutableList = arrayListOf( DesuDrive(), Filesim(), - FileMoon(), - FileMoonSx(), - Vido(), Linkbox(), Acefile(), SpeedoStream(), @@ -383,11 +354,6 @@ val extractorApis: MutableList = arrayListOf( VidSrcExtractor2(), PlayLtXyz(), AStreamHub(), - - Cda(), - Dailymotion(), - ByteShare(), - Ztreamhub() ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index 8b516e8c..54492e6d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -7,12 +7,13 @@ import android.net.Uri import android.util.Log import android.widget.Toast import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CommonActivity.showToast +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.Coroutines.ioSafe @@ -22,11 +23,6 @@ import okio.BufferedSink import okio.buffer import okio.sink import java.io.File -import android.text.TextUtils -import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus -import java.io.BufferedReader -import java.io.IOException -import java.io.InputStreamReader class InAppUpdater { @@ -120,7 +116,6 @@ class InAppUpdater { )?.groupValues?.get(2) } }).toList().lastOrNull() - val foundAsset = found?.assets?.getOrNull(0) val currentVersion = packageName?.let { packageManager.getPackageInfo( @@ -250,9 +245,6 @@ class InAppUpdater { } } - /** - * @param checkAutoUpdate if the update check was launched automatically - **/ suspend fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean { val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) @@ -262,20 +254,13 @@ class InAppUpdater { ) ) { val update = getAppUpdate() - if ( - update.shouldUpdate && - update.updateURL != null) { - - // Check if update should be skipped + if (update.shouldUpdate && update.updateURL != null) { + //Check if update should be skipped val updateNodeId = settingsManager.getString(getString(R.string.skip_update_key), "") - - // Skips the update if its an automatic update and the update is skipped - // This allows updating manually - if (update.updateNodeId.equals(updateNodeId) && checkAutoUpdate) { + if (update.updateNodeId.equals(updateNodeId)) { return false } - runOnUiThread { try { val currentVersion = packageName?.let { @@ -297,51 +282,16 @@ class InAppUpdater { val context = this builder.apply { setPositiveButton(R.string.update) { _, _ -> - // Forcefully start any delayed installations - if (ApkInstaller.delayedInstaller?.startInstallation() == true) return@setPositiveButton - showToast(context, R.string.download_started, Toast.LENGTH_LONG) - - // Check if the setting hasn't been changed - if (settingsManager.getInt( - getString(R.string.apk_installer_key), - -1 - ) == -1 - ) { - if (isMiUi()) // Set to legacy if using miui - settingsManager.edit() - .putInt(getString(R.string.apk_installer_key), 1) - .apply() - } - - val currentInstaller = - settingsManager.getInt( - getString(R.string.apk_installer_key), - 0 - ) - - when (currentInstaller) { - // New method - 0 -> { - val intent = PackageInstallerService.getIntent( - context, - update.updateURL - ) - ContextCompat.startForegroundService(context, intent) - } - // Legacy - 1 -> { - ioSafe { - if (!downloadUpdate(update.updateURL)) - runOnUiThread { - showToast( - context, - R.string.download_failed, - Toast.LENGTH_LONG - ) - } + ioSafe { + if (!downloadUpdate(update.updateURL)) + runOnUiThread { + showToast( + context, + R.string.download_failed, + Toast.LENGTH_LONG + ) } - } } } @@ -352,11 +302,12 @@ class InAppUpdater { settingsManager.edit().putString( getString(R.string.skip_update_key), update.updateNodeId ?: "" - ).apply() + ) + .apply() } } } - builder.show().setDefaultFocus() + builder.show() } catch (e: Exception) { logError(e) } @@ -367,20 +318,5 @@ class InAppUpdater { } return false } - - private fun isMiUi(): Boolean { - return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.name")) - } - - private fun getSystemProperty(propName: String): String? { - return try { - val p = Runtime.getRuntime().exec("getprop $propName") - BufferedReader(InputStreamReader(p.inputStream), 1024).use { - it.readLine() - } - } catch (ex: IOException) { - null - } - } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/JsHunter.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/JsHunter.kt deleted file mode 100644 index 8ccb2c70..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/JsHunter.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.lagradost.cloudstream3.utils - -import com.lagradost.cloudstream3.mvvm.logError -import java.util.regex.Matcher -import java.util.regex.Pattern -import kotlin.math.pow - -//author: https://github.com/daarkdemon -class JsHunter(private val hunterJS: String) { - - /** - * Detects whether the javascript is H.U.N.T.E.R coded. - * - * @return true if it's H.U.N.T.E.R coded. - */ - fun detect(): Boolean { - val p = Pattern.compile("eval\\(function\\(h,u,n,t,e,r\\)") - val searchResults = p.matcher(hunterJS) - return searchResults.find() - } - - /** - * Unpack the javascript - * - * @return the javascript unhunt or null. - */ - - fun dehunt(): String? { - try { - val p: Pattern = - Pattern.compile( - """}\("([^"]+)",[^,]+,\s*"([^"]+)",\s*(\d+),\s*(\d+)""", - Pattern.DOTALL - ) - val searchResults: Matcher = p.matcher(hunterJS) - if (searchResults.find() && searchResults.groupCount() == 4) { - val h = searchResults.group(1)!!.toString() - val n = searchResults.group(2)!!.toString() - val t = searchResults.group(3)!!.toInt() - val e = searchResults.group(4)!!.toInt() - return hunter(h, n, t, e) - } - } catch (e: Exception) { - logError(e) - } - return null - } - - private fun duf(d: String, e: Int, f: Int = 10): Int { - val str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/" - val g = str.toList() - val h = g.take(e) - val i = g.take(f) - val dList = d.reversed().toList() - var j = 0.0 - for ((c, b) in dList.withIndex()) { - if (b in h) { - j += h.indexOf(b) * e.toDouble().pow(c) - } - } - var k = "" - while (j > 0) { - k = i[(j % f).toInt()] + k - j = (j - j % f) / f - } - return k.toIntOrNull() ?: 0 - } - - private fun hunter(h: String, n: String, t: Int, e: Int): String { - var result = "" - var i = 0 - while (i < h.length) { - var j = 0 - var s = "" - while (h[i] != n[e]) { - s += h[i] - i++ - } - while (j < n.length) { - s = s.replace(n[j], j.digitToChar()) - j++ - } - result += (duf(s, e) - t).toChar() - i++ - } - return result - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt deleted file mode 100644 index bc81a5b9..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt +++ /dev/null @@ -1,148 +0,0 @@ -package com.lagradost.cloudstream3.utils - -import android.app.PendingIntent -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.content.IntentSender -import android.content.pm.PackageInstaller -import android.os.Build -import android.widget.Toast -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall -import com.lagradost.cloudstream3.utils.Coroutines.main -import java.io.InputStream - -const val INSTALL_ACTION = "ApkInstaller.INSTALL_ACTION" - - -class ApkInstaller(private val service: PackageInstallerService) { - - companion object { - /** - * Used for postponed installations - **/ - var delayedInstaller: DelayedInstaller? = null - } - - inner class DelayedInstaller( - private val session: PackageInstaller.Session, - private val intent: IntentSender - ) { - fun startInstallation(): Boolean { - return try { - session.commit(intent) - true - } catch (e: Exception) { - false - }.also { delayedInstaller = null } - } - } - - private val packageInstaller = service.packageManager.packageInstaller - - enum class InstallProgressStatus { - Preparing, - Downloading, - Installing, - Failed, - } - - private val installActionReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - when (intent.getIntExtra( - PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE - )) { - PackageInstaller.STATUS_PENDING_USER_ACTION -> { - val userAction = intent.getParcelableExtra(Intent.EXTRA_INTENT) - userAction?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - context.startActivity(userAction) - } - } - } - } - - fun installApk( - context: Context, - inputStream: InputStream, - size: Long, - installProgress: (bytesRead: Int) -> Unit, - installProgressStatus: (InstallProgressStatus) -> Unit - ) { - installProgressStatus.invoke(InstallProgressStatus.Preparing) - var activeSession: Int? = null - - try { - val installParams = - PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - installParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED) - } - - activeSession = packageInstaller.createSession(installParams) - installParams.setSize(size) - - val session = packageInstaller.openSession(activeSession) - installProgressStatus.invoke(InstallProgressStatus.Downloading) - - session.openWrite(context.packageName, 0, size) - .use { outputStream -> - val buffer = ByteArray(4 * 1024) - var bytesRead = inputStream.read(buffer) - - while (bytesRead >= 0) { - outputStream.write(buffer, 0, bytesRead) - bytesRead = inputStream.read(buffer) - installProgress.invoke(bytesRead) - } - - session.fsync(outputStream) - inputStream.close() - } - - - val intentSender = PendingIntent.getBroadcast( - service, - activeSession, - Intent(INSTALL_ACTION), - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0, - ).intentSender - - // Use delayed installations on android 13 and only if "allow from unknown sources" is enabled - // if the app lacks installation permission it cannot ask for the permission when it's closed. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && - context.packageManager.canRequestPackageInstalls() - ) { - // Save for later installation since it's more jarring to have the app exit abruptly - delayedInstaller = DelayedInstaller(session, intentSender) - main { - // Use real toast since it should show even if app is exited - Toast.makeText(context, R.string.delayed_update_notice, Toast.LENGTH_LONG) - .show() - } - } else { - installProgressStatus.invoke(InstallProgressStatus.Installing) - session.commit(intentSender) - } - } catch (e: Exception) { - logError(e) - - service.unregisterReceiver(installActionReceiver) - installProgressStatus.invoke(InstallProgressStatus.Failed) - - activeSession?.let { sessionId -> - packageInstaller.abandonSession(sessionId) - } - } - } - - init { - service.registerReceiver(installActionReceiver, IntentFilter(INSTALL_ACTION)) - service.receivers.add(installActionReceiver) - } -} - diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstallerService.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstallerService.kt deleted file mode 100644 index dcb1e047..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstallerService.kt +++ /dev/null @@ -1,189 +0,0 @@ -package com.lagradost.cloudstream3.utils - -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.app.Service -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.os.Build -import android.os.IBinder -import android.util.Log -import androidx.core.app.NotificationCompat -import com.lagradost.cloudstream3.MainActivity -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel -import com.lagradost.cloudstream3.utils.Coroutines.ioSafe -import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute -import kotlinx.coroutines.delay -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlin.math.roundToInt - - -class PackageInstallerService : Service() { - val receivers = mutableListOf() - - private val baseNotification by lazy { - val flag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - PendingIntent.FLAG_MUTABLE - } else 0 - - val intent = Intent(this, MainActivity::class.java) - val pendingIntent = - PendingIntent.getActivity(this, 0, intent, flag) - - NotificationCompat.Builder(this, UPDATE_CHANNEL_ID) - .setAutoCancel(false) - .setColorized(true) - .setOnlyAlertOnce(true) - .setSilent(true) - // If low priority then the notification might not show :( - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setColor(this.colorFromAttribute(R.attr.colorPrimary)) - .setContentTitle(getString(R.string.update_notification_downloading)) - .setContentIntent(pendingIntent) - .setSmallIcon(R.drawable.rdload) - } - - override fun onCreate() { - this.createNotificationChannel( - UPDATE_CHANNEL_ID, - UPDATE_CHANNEL_NAME, - UPDATE_CHANNEL_DESCRIPTION - ) - startForeground(UPDATE_NOTIFICATION_ID, baseNotification.build()) - } - - private val updateLock = Mutex() - - private suspend fun downloadUpdate(url: String): Boolean { - try { - Log.d(LOG_TAG, "Downloading update: $url") - - // Delete all old updates - ioSafe { - val appUpdateName = "CloudStream" - val appUpdateSuffix = "apk" - - this@PackageInstallerService.cacheDir.listFiles()?.filter { - it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix - }?.forEach { - it.deleteOnExit() - } - } - - updateLock.withLock { - updateNotificationProgress( - 0f, - ApkInstaller.InstallProgressStatus.Downloading - ) - - val body = app.get(url).body - val inputStream = body.byteStream() - val installer = ApkInstaller(this) - val totalSize = body.contentLength() - var currentSize = 0 - - installer.installApk(this, inputStream, totalSize, { - currentSize += it - // Prevent div 0 - if (totalSize == 0L) return@installApk - - val percentage = currentSize / totalSize.toFloat() - updateNotificationProgress( - percentage, - ApkInstaller.InstallProgressStatus.Downloading - ) - }) { status -> - updateNotificationProgress(0f, status) - } - } - return true - } catch (e: Exception) { - updateNotificationProgress(0f, ApkInstaller.InstallProgressStatus.Failed) - return false - } - } - - private fun updateNotificationProgress( - percentage: Float, - state: ApkInstaller.InstallProgressStatus - ) { -// Log.d(LOG_TAG, "Downloading app update progress $percentage | $state") - val text = when (state) { - ApkInstaller.InstallProgressStatus.Installing -> R.string.update_notification_installing - ApkInstaller.InstallProgressStatus.Preparing, ApkInstaller.InstallProgressStatus.Downloading -> R.string.update_notification_downloading - ApkInstaller.InstallProgressStatus.Failed -> R.string.update_notification_failed - } - - val newNotification = baseNotification - .setContentTitle(getString(text)) - .apply { - if (state == ApkInstaller.InstallProgressStatus.Failed) { - setSmallIcon(R.drawable.rderror) - setAutoCancel(true) - } else { - setProgress( - 10000, (10000 * percentage).roundToInt(), - state != ApkInstaller.InstallProgressStatus.Downloading - ) - } - } - .build() - - val notificationManager = - getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - // Persistent notification on failure - val id = - if (state == ApkInstaller.InstallProgressStatus.Failed) UPDATE_NOTIFICATION_ID + 1 else UPDATE_NOTIFICATION_ID - notificationManager.notify(id, newNotification) - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - val url = intent?.getStringExtra(EXTRA_URL) ?: return START_NOT_STICKY - ioSafe { - downloadUpdate(url) - // Close the service after the update is done - // If no sleep then the install prompt may not appear and the notification - // will disappear instantly - delay(10_000) - this@PackageInstallerService.stopSelf() - } - return START_NOT_STICKY - } - - override fun onDestroy() { - receivers.forEach { - try { - this.unregisterReceiver(it) - } catch (_: IllegalArgumentException) { - // Receiver not registered - } - } - super.onDestroy() - } - - override fun onBind(i: Intent?): IBinder? = null - - companion object { - private const val EXTRA_URL = "EXTRA_URL" - private const val LOG_TAG = "PackageInstallerService" - - const val UPDATE_CHANNEL_ID = "cloudstream3.updates" - const val UPDATE_CHANNEL_NAME = "App Updates" - const val UPDATE_CHANNEL_DESCRIPTION = "App updates notification channel" - const val UPDATE_NOTIFICATION_ID = -68454136 // Random unique - - fun getIntent( - context: Context, - url: String, - ): Intent { - return Intent(context, PackageInstallerService::class.java) - .putExtra(EXTRA_URL, url) - } - } -} diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt index 2dc6846c..b3bce446 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt @@ -99,8 +99,7 @@ object SingleSelectionHelper { val textView = dialog.text1//.findViewById(R.id.text1)!! val applyButton = dialog.apply_btt//.findViewById(R.id.apply_btt) val cancelButton = dialog.cancel_btt//findViewById(R.id.cancel_btt) - val applyHolder = - dialog.apply_btt_holder//.findViewById(R.id.apply_btt_holder) + val applyHolder = dialog.apply_btt_holder//.findViewById(R.id.apply_btt_holder) applyHolder?.isVisible = realShowApply if (!realShowApply) { @@ -250,17 +249,6 @@ object SingleSelectionHelper { ) } - fun showBottomDialog( - items: List, - selectedIndex: Int, - name: String, - showApply: Boolean, - dismissCallback: () -> Unit, - callback: (Int) -> Unit, - ) { - - } - /** Only for a low amount of items */ fun Activity?.showBottomDialog( items: List, diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt index e5f2f2dc..7dda3e18 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SyncUtil.kt @@ -4,7 +4,6 @@ package com.lagradost.cloudstream3.utils import android.util.Log import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.APIHolder.apis //import com.lagradost.cloudstream3.animeproviders.AniflixProvider import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError @@ -79,21 +78,17 @@ object SyncUtil { return null } - suspend fun getUrlsFromId(id: String, type: String = "anilist"): List { - val url = - "https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/$type/anime/$id.json" - val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).parsed() - val pages = response.pages ?: return emptyList() - val current = - pages.gogoanime.values.union(pages.nineanime.values).union(pages.twistmoe.values) - .mapNotNull { it.url }.toMutableList() - - if (type == "anilist") { // TODO MAKE BETTER - apis.filter { it.name.contains("Aniflix", ignoreCase = true) }.forEach { - current.add("${it.mainUrl}/anime/$id") - } - } - return current + suspend fun getUrlsFromId(id: String, type: String = "anilist") : List { + return arrayListOf() + // val url = + // "https://raw.githubusercontent.com/MALSync/MAL-Sync-Backup/master/data/$type/anime/$id.json" + // val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).parsed() + // val pages = response.pages ?: return emptyList() + // val current = pages.gogoanime.values.union(pages.nineanime.values).union(pages.twistmoe.values).mapNotNull { it.url }.toMutableList() + // if(type == "anilist") { // TODO MAKE BETTER + // current.add("${AniflixProvider().mainUrl}/anime/$id") + // } + // return current } data class SyncPage( diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/TestingUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/TestingUtils.kt deleted file mode 100644 index 66e1e504..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/TestingUtils.kt +++ /dev/null @@ -1,267 +0,0 @@ -package com.lagradost.cloudstream3.utils - -import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.mvvm.logError -import kotlinx.coroutines.* -import org.junit.Assert - -object TestingUtils { - open class TestResult(val success: Boolean) { - companion object { - val Pass = TestResult(true) - val Fail = TestResult(false) - } - } - - class TestResultSearch(val results: List) : TestResult(true) - class TestResultLoad(val extractorData: String) : TestResult(true) - - class TestResultProvider(success: Boolean, val log: String, val exception: Throwable?) : - TestResult(success) - - @Throws(AssertionError::class, CancellationException::class) - suspend fun testHomepage( - api: MainAPI, - logger: (String) -> Unit - ): TestResult { - if (api.hasMainPage) { - try { - val f = api.mainPage.first() - val homepage = - api.getMainPage(1, MainPageRequest(f.name, f.data, f.horizontalImages)) - when { - homepage == null -> { - logger.invoke("Homepage provider ${api.name} did not correctly load homepage!") - } - homepage.items.isEmpty() -> { - logger.invoke("Homepage provider ${api.name} does not contain any items!") - } - homepage.items.any { it.list.isEmpty() } -> { - logger.invoke("Homepage provider ${api.name} does not have any items on result!") - } - } - } catch (e: Throwable) { - if (e is NotImplementedError) { - Assert.fail("Provider marked as hasMainPage, while in reality is has not been implemented") - } else if (e is CancellationException) { - throw e - } - logError(e) - } - } - return TestResult.Pass - } - - @Throws(AssertionError::class, CancellationException::class) - private suspend fun testSearch( - api: MainAPI - ): TestResult { - val searchQueries = listOf("over", "iron", "guy") - val searchResults = searchQueries.firstNotNullOfOrNull { query -> - try { - api.search(query).takeIf { !it.isNullOrEmpty() } - } catch (e: Throwable) { - if (e is NotImplementedError) { - Assert.fail("Provider has not implemented search()") - } else if (e is CancellationException) { - throw e - } - logError(e) - null - } - } - - return if (searchResults.isNullOrEmpty()) { - Assert.fail("Api ${api.name} did not return any valid search responses") - TestResult.Fail // Should not be reached - } else { - TestResultSearch(searchResults) - } - - } - - - @Throws(AssertionError::class, CancellationException::class) - private suspend fun testLoad( - api: MainAPI, - result: SearchResponse, - logger: (String) -> Unit - ): TestResult { - try { - Assert.assertEquals( - "Invalid apiName on SearchResponse on ${api.name}", - result.apiName, - api.name - ) - - val loadResponse = api.load(result.url) - - if (loadResponse == null) { - logger.invoke("Returned null loadResponse on ${result.url} on ${api.name}") - return TestResult.Fail - } - - Assert.assertEquals( - "Invalid apiName on LoadResponse on ${api.name}", - loadResponse.apiName, - result.apiName - ) - Assert.assertTrue( - "Api ${api.name} on load does not contain any of the supportedTypes: ${loadResponse.type}", - api.supportedTypes.contains(loadResponse.type) - ) - - val url = when (loadResponse) { - is AnimeLoadResponse -> { - val gotNoEpisodes = - loadResponse.episodes.keys.isEmpty() || loadResponse.episodes.keys.any { loadResponse.episodes[it].isNullOrEmpty() } - - if (gotNoEpisodes) { - logger.invoke("Api ${api.name} got no episodes on ${loadResponse.url}") - return TestResult.Fail - } - - (loadResponse.episodes[loadResponse.episodes.keys.firstOrNull()])?.firstOrNull()?.data - } - is MovieLoadResponse -> { - val gotNoEpisodes = loadResponse.dataUrl.isBlank() - if (gotNoEpisodes) { - logger.invoke("Api ${api.name} got no movie on ${loadResponse.url}") - return TestResult.Fail - } - - loadResponse.dataUrl - } - is TvSeriesLoadResponse -> { - val gotNoEpisodes = loadResponse.episodes.isEmpty() - if (gotNoEpisodes) { - logger.invoke("Api ${api.name} got no episodes on ${loadResponse.url}") - return TestResult.Fail - } - loadResponse.episodes.firstOrNull()?.data - } - is LiveStreamLoadResponse -> { - loadResponse.dataUrl - } - else -> { - logger.invoke("Unknown load response: ${loadResponse.javaClass.name}") - return TestResult.Fail - } - } ?: return TestResult.Fail - - return TestResultLoad(url) - -// val loadTest = testLoadResponse(api, load, logger) -// if (loadTest is TestResultLoad) { -// testLinkLoading(api, loadTest.extractorData, logger).success -// } else { -// false -// } -// if (!validResults) { -// logger("Api ${api.name} did not load on the first search results: ${smallSearchResults.map { it.name }}") -// } - -// return TestResult(validResults) - } catch (e: Throwable) { - if (e is NotImplementedError) { - Assert.fail("Provider has not implemented load()") - } - throw e - } - } - - @Throws(AssertionError::class, CancellationException::class) - private suspend fun testLinkLoading( - api: MainAPI, - url: String?, - logger: (String) -> Unit - ): TestResult { - Assert.assertNotNull("Api ${api.name} has invalid url on episode", url) - if (url == null) return TestResult.Fail // Should never trigger - - var linksLoaded = 0 - try { - val success = api.loadLinks(url, false, {}) { link -> - logger.invoke("Video loaded: ${link.name}") - Assert.assertTrue( - "Api ${api.name} returns link with invalid url ${link.url}", - link.url.length > 4 - ) - linksLoaded++ - } - if (success) { - logger.invoke("Links loaded: $linksLoaded") - return TestResult(linksLoaded > 0) - } else { - Assert.fail("Api ${api.name} returns false on loadLinks() with $linksLoaded links loaded") - } - } catch (e: Throwable) { - when (e) { - is NotImplementedError -> { - Assert.fail("Provider has not implemented loadLinks()") - } - else -> { - logger.invoke("Failed link loading on ${api.name} using data: $url") - throw e - } - } - } - return TestResult.Pass - } - - fun getDeferredProviderTests( - scope: CoroutineScope, - providers: List, - logger: (String) -> Unit, - callback: (MainAPI, TestResultProvider) -> Unit - ) { - providers.forEach { api -> - scope.launch { - var log = "" - fun addToLog(string: String) { - log += string + "\n" - logger.invoke(string) - } - fun getLog(): String { - return log.removeSuffix("\n") - } - - val result = try { - addToLog("Trying ${api.name}") - - // Test Homepage - val homepage = testHomepage(api, logger).success - Assert.assertTrue("Homepage failed to load", homepage) - - // Test Search Results - val searchResults = testSearch(api) - Assert.assertTrue("Failed to get search results", searchResults.success) - searchResults as TestResultSearch - - // Test Load and LoadLinks - // Only try the first 3 search results to prevent spamming - val success = searchResults.results.take(3).any { searchResponse -> - addToLog("Testing search result: ${searchResponse.url}") - val loadResponse = testLoad(api, searchResponse, ::addToLog) - if (loadResponse !is TestResultLoad) { - false - } else { - testLinkLoading(api, loadResponse.extractorData, ::addToLog).success - } - } - - if (success) { - logger.invoke("Success ${api.name}") - TestResultProvider(true, getLog(), null) - } else { - logger.invoke("Error ${api.name}") - TestResultProvider(false, getLog(), null) - } - } catch (e: Throwable) { - TestResultProvider(false, getLog(), e) - } - callback.invoke(api, result) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index c300d615..553860ba 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -9,9 +9,7 @@ import android.content.Context import android.content.pm.PackageManager import android.content.res.Configuration import android.content.res.Resources -import android.graphics.Bitmap import android.graphics.Color -import android.graphics.drawable.Drawable import android.os.Build import android.os.Bundle import android.view.* @@ -30,21 +28,15 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.graphics.alpha import androidx.core.graphics.blue -import androidx.core.graphics.drawable.toBitmapOrNull import androidx.core.graphics.green import androidx.core.graphics.red import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.navigation.fragment.NavHostFragment -import androidx.palette.graphics.Palette import androidx.preference.PreferenceManager -import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions -import com.bumptech.glide.request.RequestListener -import com.bumptech.glide.request.target.Target import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings @@ -65,10 +57,7 @@ object UIHelper { this, Manifest.permission.WRITE_EXTERNAL_STORAGE ) - == PackageManager.PERMISSION_GRANTED - // Since Android 13, we can't request external storage permission, - // so don't check it. - || Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) + == PackageManager.PERMISSION_GRANTED) } fun Activity.requestRW() { @@ -113,7 +102,7 @@ object UIHelper { listView.requestLayout() } - fun Context?.getSpanCount(): Int? { + fun Activity?.getSpanCount(): Int? { val compactView = false val spanCountLandscape = if (compactView) 2 else 6 val spanCountPortrait = if (compactView) 1 else 3 @@ -166,27 +155,12 @@ object UIHelper { return color } - var createPaletteAsyncCache: HashMap = hashMapOf() - fun createPaletteAsync(url: String, bitmap: Bitmap, callback: (Palette) -> Unit) { - createPaletteAsyncCache[url]?.let { palette -> - callback.invoke(palette) - return - } - Palette.from(bitmap).generate { paletteNull -> - paletteNull?.let { palette -> - createPaletteAsyncCache[url] = palette - callback(palette) - } - } - } - fun ImageView?.setImage( url: String?, headers: Map? = null, @DrawableRes errorImageDrawable: Int? = null, - fadeIn: Boolean = true, - colorCallback: ((Palette) -> Unit)? = null + fadeIn: Boolean = true ): Boolean { if (this == null || url.isNullOrBlank()) return false @@ -200,33 +174,6 @@ object UIHelper { else req } - if (colorCallback != null) { - builder.listener(object : RequestListener { - @SuppressLint("CheckResult") - override fun onResourceReady( - resource: Drawable?, - model: Any?, - target: Target?, - dataSource: DataSource?, - isFirstResource: Boolean - ): Boolean { - resource?.toBitmapOrNull() - ?.let { bitmap -> createPaletteAsync(url, bitmap, colorCallback) } - return false - } - - @SuppressLint("CheckResult") - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target?, - isFirstResource: Boolean - ): Boolean { - return false - } - }) - } - val res = if (errorImageDrawable != null) builder.error(errorImageDrawable).into(this) else diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UnshortenUrl.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UnshortenUrl.kt index 46b232f6..a2e50762 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UnshortenUrl.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UnshortenUrl.kt @@ -176,7 +176,7 @@ object ShortLink { suspend fun unshortenUprot(uri: String): String { val page = app.get(uri).text - Regex("""]+href="([^"]+)".*Continue""").findAll(page) + Regex("""]+href="([^"]+)""").findAll(page) .map { it.value.replace(""" if (link.contains("https://maxstream.video") || link.contains("https://uprot.net") && link != uri) { @@ -193,4 +193,4 @@ object ShortLink { val doc = app.get(uri).document return doc.selectFirst("iframe")?.attr("src")?.trim()?: uri } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 2902b76b..a629dad9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -20,7 +20,6 @@ import androidx.work.Data import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager -import com.bumptech.glide.load.model.GlideUrl import com.fasterxml.jackson.annotation.JsonProperty import com.hippo.unifile.UniFile import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull @@ -214,7 +213,7 @@ object VideoDownloadManager { } private val cachedBitmaps = hashMapOf() - fun Context.getImageBitmapFromUrl(url: String, headers: Map? = null): Bitmap? { + private fun Context.getImageBitmapFromUrl(url: String): Bitmap? { try { if (cachedBitmaps.containsKey(url)) { return cachedBitmaps[url] @@ -222,14 +221,12 @@ object VideoDownloadManager { val bitmap = GlideApp.with(this) .asBitmap() - .load(GlideUrl(url) { headers ?: emptyMap() }) - .into(720, 720) + .load(url).into(720, 720) .get() - if (bitmap != null) { cachedBitmaps[url] = bitmap } - return bitmap + return null } catch (e: Exception) { logError(e) return null @@ -429,7 +426,7 @@ object VideoDownloadManager { } private const val reservedChars = "|\\?*<\":>+[]/\'" - fun sanitizeFilename(name: String, removeSpaces: Boolean = false): String { + fun sanitizeFilename(name: String, removeSpaces: Boolean= false): String { var tempName = name for (c in reservedChars) { tempName = tempName.replace(c, ' ') @@ -1615,7 +1612,7 @@ object VideoDownloadManager { .mapIndexed { index, any -> DownloadQueueResumePackage(index, any) } .toTypedArray() setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue) - } catch (t: Throwable) { + } catch (t : Throwable) { logError(t) } } diff --git a/app/src/main/res/color/item_select_color.xml b/app/src/main/res/color/item_select_color.xml index 3d69c540..0d2834dd 100644 --- a/app/src/main/res/color/item_select_color.xml +++ b/app/src/main/res/color/item_select_color.xml @@ -1,7 +1,5 @@ - - \ No newline at end of file diff --git a/app/src/main/res/color/white_transparent_toggle.xml b/app/src/main/res/color/white_transparent_toggle.xml deleted file mode 100644 index a94501bd..00000000 --- a/app/src/main/res/color/white_transparent_toggle.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/baseline_network_ping_24.xml b/app/src/main/res/drawable/baseline_network_ping_24.xml deleted file mode 100644 index 1caae667..00000000 --- a/app/src/main/res/drawable/baseline_network_ping_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/baseline_notifications_none_24.xml b/app/src/main/res/drawable/baseline_notifications_none_24.xml deleted file mode 100644 index cf589c6d..00000000 --- a/app/src/main/res/drawable/baseline_notifications_none_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/baseline_text_snippet_24.xml b/app/src/main/res/drawable/baseline_text_snippet_24.xml deleted file mode 100644 index c1f3654b..00000000 --- a/app/src/main/res/drawable/baseline_text_snippet_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/delete_all.xml b/app/src/main/res/drawable/delete_all.xml deleted file mode 100644 index a5e8775c..00000000 --- a/app/src/main/res/drawable/delete_all.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_collections_bookmark_24.xml b/app/src/main/res/drawable/ic_baseline_collections_bookmark_24.xml deleted file mode 100644 index fc90e300..00000000 --- a/app/src/main/res/drawable/ic_baseline_collections_bookmark_24.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_left_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_left_24.xml index 916c761c..ff93af1f 100644 --- a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_left_24.xml +++ b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_left_24.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml index ea924625..fc07eaae 100644 --- a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml +++ b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_right_24.xml @@ -1,11 +1,5 @@ - - + + diff --git a/app/src/main/res/drawable/ic_baseline_sort_24.xml b/app/src/main/res/drawable/ic_baseline_sort_24.xml deleted file mode 100644 index 96d46231..00000000 --- a/app/src/main/res/drawable/ic_baseline_sort_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_star_24.xml b/app/src/main/res/drawable/ic_baseline_star_24.xml index 2dcadb7f..ab099425 100644 --- a/app/src/main/res/drawable/ic_baseline_star_24.xml +++ b/app/src/main/res/drawable/ic_baseline_star_24.xml @@ -1,5 +1,5 @@ - + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/app/src/main/res/drawable/ic_cloudstream_monochrome_big.xml b/app/src/main/res/drawable/ic_cloudstream_monochrome_big.xml deleted file mode 100644 index 4b8964f8..00000000 --- a/app/src/main/res/drawable/ic_cloudstream_monochrome_big.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_outline_account_circle_24.xml b/app/src/main/res/drawable/ic_outline_account_circle_24.xml deleted file mode 100644 index cc564471..00000000 --- a/app/src/main/res/drawable/ic_outline_account_circle_24.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/indicator_background.xml b/app/src/main/res/drawable/indicator_background.xml deleted file mode 100644 index ef44fb7c..00000000 --- a/app/src/main/res/drawable/indicator_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/progress_drawable_vertical.xml b/app/src/main/res/drawable/progress_drawable_vertical.xml index a9535131..207196f5 100644 --- a/app/src/main/res/drawable/progress_drawable_vertical.xml +++ b/app/src/main/res/drawable/progress_drawable_vertical.xml @@ -3,14 +3,14 @@ - + - + diff --git a/app/src/main/res/drawable/rating_bg_color.xml b/app/src/main/res/drawable/rating_bg_color.xml deleted file mode 100644 index 60e62bab..00000000 --- a/app/src/main/res/drawable/rating_bg_color.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b6290865..4905d454 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -14,11 +14,10 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/homeRoot" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|keyboard|navigation" + android:paddingTop="0dp"> + android:layout_height="match_parent" + android:layout_width="match_parent"> + android:id="@+id/nav_host_fragment" + android:name="androidx.navigation.fragment.NavHostFragment" + android:layout_width="0dp" + android:layout_height="0dp" + app:defaultNavHost="true" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintLeft_toRightOf="parent" + app:layout_constraintBottom_toTopOf="@id/cast_mini_controller_holder" + app:navGraph="@navigation/mobile_navigation" + app:layout_constraintStart_toEndOf="@id/nav_rail_view" + app:layout_constraintEnd_toEndOf="parent" /> + app:menuGravity="center" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:labelVisibilityMode="unlabeled" + app:menu="@menu/bottom_nav_menu"> + app:layout_constraintStart_toEndOf="@+id/nav_rail_view" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + tools:layout_height="100dp" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:id="@+id/cast_mini_controller_holder"> + app:customCastBackgroundColor="?attr/primaryGrayBackground" + app:castControlButtons="@array/cast_mini_controller_control_buttons" + android:id="@+id/cast_mini_controller" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + class="com.lagradost.cloudstream3.ui.MyMiniControllerFragment" + tools:ignore="FragmentTagUsage" /> \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_resultview_preview.xml b/app/src/main/res/layout/bottom_resultview_preview.xml deleted file mode 100644 index ce41cb65..00000000 --- a/app/src/main/res/layout/bottom_resultview_preview.xml +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/bottom_selection_dialog_direct.xml b/app/src/main/res/layout/bottom_selection_dialog_direct.xml index cf31ba1f..0d179ebb 100644 --- a/app/src/main/res/layout/bottom_selection_dialog_direct.xml +++ b/app/src/main/res/layout/bottom_selection_dialog_direct.xml @@ -1,34 +1,34 @@ + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + android:id="@+id/text1" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:layout_marginTop="20dp" + android:layout_marginBottom="10dp" + android:textStyle="bold" + android:textSize="20sp" + android:textColor="?attr/textColor" + android:layout_width="match_parent" + android:layout_rowWeight="1" + tools:text="Test" + android:layout_height="wrap_content" /> + android:nextFocusRight="@id/cancel_btt" + android:nextFocusLeft="@id/apply_btt" + + android:id="@+id/listview1" + android:layout_marginBottom="60dp" + android:paddingTop="10dp" + android:requiresFadingEdge="vertical" + tools:listitem="@layout/sort_bottom_single_choice_no_checkmark" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_rowWeight="1" /> diff --git a/app/src/main/res/layout/dialog_online_subtitles.xml b/app/src/main/res/layout/dialog_online_subtitles.xml index 7803e261..824d8089 100644 --- a/app/src/main/res/layout/dialog_online_subtitles.xml +++ b/app/src/main/res/layout/dialog_online_subtitles.xml @@ -1,158 +1,140 @@ - - + android:background="@null" + android:orientation="vertical"> + + + android:id="@+id/sort_subtitles_holder" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="50" + android:orientation="vertical"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + tools:ignore="UseCompoundDrawables"> + android:layout_width="match_parent" + android:layout_height="40dp" + android:layout_margin="10dp" + android:background="@drawable/search_background" + android:visibility="visible"> + android:layout_width="match_parent" + android:layout_height="30dp" + android:layout_gravity="center_vertical" + android:layout_marginEnd="30dp"> + android:imeOptions="actionSearch" + android:inputType="text" + android:paddingStart="-10dp" + tools:ignore="RtlSymmetry"> + + + - - - - - - - - - + android:layout_gravity="end|center_vertical" + android:layout_margin="10dp" + android:background="?selectableItemBackgroundBorderless" + android:contentDescription="@string/change_providers_img_des" + android:nextFocusLeft="@id/main_search" + android:nextFocusRight="@id/main_search" + android:nextFocusUp="@id/nav_rail_view" + android:nextFocusDown="@id/search_autofit_results" + android:src="@drawable/ic_baseline_tune_24" /> + android:layout_rowWeight="1" + android:background="?attr/primaryBlackBackground" + android:nextFocusLeft="@id/sort_providers" + android:nextFocusRight="@id/cancel_btt" + android:requiresFadingEdge="vertical" + tools:listfooter="@layout/sort_bottom_footer_add_choice" + tools:listitem="@layout/sort_bottom_single_choice" /> + android:id="@+id/apply_btt_holder" + android:layout_width="match_parent" + android:layout_height="60dp" + android:layout_gravity="bottom" + android:layout_marginTop="-60dp" + android:gravity="bottom|end" + android:orientation="horizontal"> + android:id="@+id/apply_btt" + style="@style/WhiteButton" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|end" + android:text="@string/sort_apply" /> + android:id="@+id/cancel_btt" + style="@style/BlackButton" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|end" + android:text="@string/sort_cancel" /> diff --git a/app/src/main/res/layout/fragment_downloads.xml b/app/src/main/res/layout/fragment_downloads.xml index 65f36209..f88e39d0 100644 --- a/app/src/main/res/layout/fragment_downloads.xml +++ b/app/src/main/res/layout/fragment_downloads.xml @@ -5,6 +5,7 @@ android:id="@+id/download_root" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="?attr/primaryGrayBackground" android:orientation="vertical" tools:context=".ui.download.DownloadFragment"> @@ -131,8 +132,7 @@ - + android:layout_height="match_parent" + android:background="?attr/primaryBlackBackground" + android:visibility="gone" + tools:visibility="visible"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home_head_tv.xml b/app/src/main/res/layout/fragment_home_head_tv.xml deleted file mode 100644 index b9b4fec0..00000000 --- a/app/src/main/res/layout/fragment_home_head_tv.xml +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ 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 ebcd3e9f..5b092927 100644 --- a/app/src/main/res/layout/fragment_home_tv.xml +++ b/app/src/main/res/layout/fragment_home_tv.xml @@ -1,88 +1,87 @@ - - + + android:id="@+id/home_root" + tools:context=".ui.home.HomeFragment"> + + + android:layout_gravity="center" + android:visibility="gone" + tools:visibility="gone" + android:layout_width="50dp" + android:layout_height="50dp" /> - - - + android:orientation="vertical"> + + + android:layout_margin="@dimen/loading_margin" + android:layout_gravity="center" + app:cardCornerRadius="@dimen/loading_radius" + android:background="@color/grayShimmer" + android:translationX="-164dp" + android:layout_width="125dp" + android:layout_height="200dp" /> + android:layout_margin="@dimen/loading_margin" + android:layout_gravity="center" + app:cardCornerRadius="@dimen/loading_radius" + android:background="@color/grayShimmer" + android:layout_width="148dp" + android:layout_height="234dp" /> + android:layout_margin="@dimen/loading_margin" + android:layout_gravity="center" + app:cardCornerRadius="@dimen/loading_radius" + android:background="@color/grayShimmer" + android:translationX="164dp" + android:layout_width="125dp" + android:layout_height="200dp" /> + android:layout_width="match_parent" + android:layout_height="wrap_content"> @@ -94,82 +93,355 @@ + android:id="@+id/home_loading_statusbar" + android:layout_width="match_parent" + android:layout_height="70dp"> + android:src="@drawable/ic_baseline_keyboard_arrow_down_24" + android:layout_width="30dp" + android:layout_height="30dp" + android:contentDescription="@string/home_change_provider_img_des" /> + android:visibility="gone" + tools:visibility="gone" + android:id="@+id/home_loading_error" + android:orientation="vertical" + android:layout_gravity="center" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + app:icon="@drawable/ic_baseline_autorenew_24" + android:text="@string/reload_error" + android:id="@+id/home_reload_connectionerror" + android:layout_width="wrap_content" + android:minWidth="200dp" /> + app:icon="@drawable/ic_baseline_public_24" + android:text="@string/result_open_in_browser" + android:id="@+id/home_reload_connection_open_in_browser" + android:layout_width="wrap_content" + android:minWidth="200dp" /> + android:layout_margin="5dp" + android:gravity="center" + android:layout_gravity="center" + android:id="@+id/result_error_text" + android:textColor="?attr/textColor" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:visibility="gone" + tools:visibility="visible" + android:id="@+id/home_api_fab" + app:icon="@drawable/ic_baseline_filter_list_24" + style="@style/ExtendedFloatingActionButton" + tools:ignore="ContentDescription" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml deleted file mode 100644 index 985d055d..00000000 --- a/app/src/main/res/layout/fragment_library.xml +++ /dev/null @@ -1,177 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_player.xml b/app/src/main/res/layout/fragment_player.xml index 250551cb..412f7b5b 100644 --- a/app/src/main/res/layout/fragment_player.xml +++ b/app/src/main/res/layout/fragment_player.xml @@ -18,7 +18,6 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" - android:focusable="false" app:auto_show="true" app:backgroundTint="@android:color/black" app:controller_layout_id="@layout/player_custom_layout" diff --git a/app/src/main/res/layout/fragment_player_tv.xml b/app/src/main/res/layout/fragment_player_tv.xml index a0826087..fec649a8 100644 --- a/app/src/main/res/layout/fragment_player_tv.xml +++ b/app/src/main/res/layout/fragment_player_tv.xml @@ -17,10 +17,10 @@ android:id="@+id/player_view" android:layout_width="match_parent" android:layout_height="match_parent" - android:focusable="false" app:auto_show="true" app:controller_layout_id="@layout/player_custom_layout_tv" app:hide_on_touch="false" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index a481ed6b..f88fc173 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -129,9 +129,9 @@ + android:background="?attr/primaryGrayBackground"> @@ -516,7 +517,6 @@ - - - + + + + + @@ -380,9 +354,8 @@ android:visibility="gone" /> - @@ -423,7 +396,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp" - android:animateLayoutChanges="true" android:orientation="horizontal" tools:visibility="visible"> @@ -437,7 +409,7 @@ android:layout_marginBottom="10dp" android:layout_weight="1" android:minWidth="250dp" - android:nextFocusRight="@id/result_play_trailer" + android:nextFocusRight="@id/result_download_movie" android:nextFocusUp="@id/result_cast_items" android:nextFocusDown="@id/result_resume_series_button_play" android:text="@string/play_movie_button" @@ -446,22 +418,6 @@ - - - diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index bed08570..083cd64e 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -10,86 +10,79 @@ android:orientation="vertical" tools:context=".ui.search.SearchFragment"> - + android:layout_height="40dp" + android:layout_margin="10dp" + android:background="@drawable/search_background" + android:visibility="visible"> + android:layout_height="30dp" + android:layout_gravity="center_vertical" + android:layout_marginEnd="30dp"> - - - - - - - - - - - - - + android:paddingStart="-10dp" + app:iconifiedByDefault="false" + app:queryBackground="@color/transparent" + app:queryHint="@string/search_hint" + app:searchIcon="@drawable/search_icon" + tools:ignore="RtlSymmetry"> + + + + + + + + - - + + + + - + android:layout_height="match_parent" - - - - - + android:background="?attr/primaryBlackBackground" + android:descendantFocusability="afterDescendants" + android:nextFocusLeft="@id/nav_rail_view" + android:visibility="visible" + tools:listitem="@layout/search_history_item" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search_tv.xml b/app/src/main/res/layout/fragment_search_tv.xml deleted file mode 100644 index 0a85a471..00000000 --- a/app/src/main/res/layout/fragment_search_tv.xml +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_testing.xml b/app/src/main/res/layout/fragment_testing.xml deleted file mode 100644 index 1426f59e..00000000 --- a/app/src/main/res/layout/fragment_testing.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/home_result_big_grid.xml b/app/src/main/res/layout/home_result_big_grid.xml index 1b37e654..0482a97c 100644 --- a/app/src/main/res/layout/home_result_big_grid.xml +++ b/app/src/main/res/layout/home_result_big_grid.xml @@ -26,7 +26,10 @@ android:contentDescription="@string/search_poster_img_des" /> + tools:text="@string/quality_hd" + android:id="@+id/text_quality" + style="@style/SearchBox" + android:background="@drawable/type_bg_color" /> + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:foreground="@drawable/outline_drawable" + android:layout_margin="2dp" + android:layout_width="114dp" + android:layout_height="180dp" + android:layout_marginBottom="2dp" + android:elevation="10dp" + app:cardCornerRadius="@dimen/rounded_image_radius" + android:id="@+id/background_card" + app:cardBackgroundColor="?attr/primaryGrayBackground"> + android:duplicateParentState="true" + android:id="@+id/imageView" + tools:src="@drawable/example_poster" + android:scaleType="centerCrop" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:foreground="?android:attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/search_poster_img_des" /> + android:focusable="false" + android:clickable="false" + android:layout_width="match_parent" + android:layout_height="50dp" + android:id="@+id/title_shadow" + android:src="@drawable/title_shadow" + android:layout_gravity="bottom" + tools:ignore="ContentDescription" /> + tools:text="The Perfect Run" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:layout_gravity="bottom" + android:paddingBottom="5dp" + android:paddingTop="5dp" + android:textColor="@color/textColor" + android:id="@+id/imageText" + android:textStyle="bold" + android:maxLines="2" + android:paddingStart="5dp" + android:paddingEnd="5dp" + android:ellipsize="end" /> + android:id="@+id/search_item_download_play" + android:layout_gravity="center" + android:src="@drawable/play_button" + android:layout_width="60dp" + android:layout_height="60dp" /> + android:layout_marginBottom="-1.5dp" + android:id="@+id/watchProgress" + android:progressTint="?attr/colorPrimary" + android:progressBackgroundTint="?attr/colorPrimary" + style="@android:style/Widget.Material.ProgressBar.Horizontal" + android:layout_width="match_parent" + tools:progress="50" + android:layout_gravity="bottom" + android:layout_height="5dp" /> - + + android:orientation="vertical" + android:layout_gravity="end" + android:layout_width="match_parent" + android:layout_height="match_parent"> + android:text="@string/app_dubbed_text" + android:id="@+id/text_is_dub" + style="@style/SearchBox" + android:layout_gravity="end" + android:background="@drawable/dub_bg_color" /> + android:id="@+id/text_is_sub" + android:text="@string/app_subbed_text" + style="@style/SearchBox" + android:layout_gravity="end" + android:background="@drawable/sub_bg_color" /> + tools:visibility="visible" + android:visibility="gone" + android:textSize="20sp" + android:id="@+id/text_flag" + tools:text="🇸🇪" + style="@style/SearchBox" + android:layout_gravity="end" + android:background="@color/transparent" /> diff --git a/app/src/main/res/layout/home_result_grid_expanded.xml b/app/src/main/res/layout/home_result_grid_expanded.xml index b697c1de..3cf4e7f9 100644 --- a/app/src/main/res/layout/home_result_grid_expanded.xml +++ b/app/src/main/res/layout/home_result_grid_expanded.xml @@ -5,58 +5,58 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:backgroundTint="@color/transparent" + android:foreground="@drawable/outline_drawable" + app:cardElevation="0dp" + android:layout_height="wrap_content" + app:cardCornerRadius="@dimen/rounded_image_radius" + android:layout_margin="2dp" + android:id="@+id/background_card" + android:layout_width="wrap_content"> + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + android:id="@+id/image_holder" + android:layout_width="114dp" + android:layout_height="180dp" + android:elevation="10dp" + app:cardCornerRadius="@dimen/rounded_image_radius" + app:cardBackgroundColor="?attr/primaryGrayBackground"> + android:duplicateParentState="true" + android:id="@+id/imageView" + tools:src="@drawable/example_poster" + android:scaleType="centerCrop" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:foreground="?android:attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/search_poster_img_des" /> + android:id="@+id/search_item_download_play" + android:layout_gravity="center" + android:src="@drawable/play_button" + android:layout_width="60dp" + android:layout_height="60dp" /> + android:layout_marginBottom="-1.5dp" + android:id="@+id/watchProgress" + android:progressTint="?attr/colorPrimary" + android:progressBackgroundTint="?attr/colorPrimary" + style="@android:style/Widget.Material.ProgressBar.Horizontal" + android:layout_width="match_parent" + tools:progress="50" + android:layout_gravity="bottom" + android:layout_height="5dp" /> - + + android:orientation="vertical" + android:layout_gravity="end" + android:layout_width="match_parent" + android:layout_height="match_parent"> + android:text="@string/app_dubbed_text" + android:id="@+id/text_is_dub" + style="@style/SearchBox" + android:layout_gravity="end" + android:background="@drawable/dub_bg_color" /> + android:id="@+id/text_is_sub" + android:text="@string/app_subbed_text" + style="@style/SearchBox" + android:layout_gravity="end" + android:background="@drawable/sub_bg_color" /> + tools:visibility="visible" + android:visibility="gone" + android:textSize="20sp" + android:id="@+id/text_flag" + tools:text="🇸🇪" + style="@style/SearchBox" + android:layout_gravity="end" + android:background="@color/transparent" /> + tools:text="The Perfect Run\nThe Perfect Run\nhello" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="13sp" + android:gravity="center" + android:layout_gravity="bottom" + android:paddingBottom="5dp" + android:paddingTop="5dp" + android:textColor="?attr/textColor" + android:id="@+id/imageText" + android:minLines="2" + android:maxLines="2" + android:paddingStart="5dp" + android:paddingEnd="5dp" + android:ellipsize="end" /> diff --git a/app/src/main/res/layout/home_scroll_view_tv.xml b/app/src/main/res/layout/home_scroll_view_tv.xml deleted file mode 100644 index e0da8197..00000000 --- a/app/src/main/res/layout/home_scroll_view_tv.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/homepage_parent.xml b/app/src/main/res/layout/homepage_parent.xml index 9c5dc84d..e982c81a 100644 --- a/app/src/main/res/layout/homepage_parent.xml +++ b/app/src/main/res/layout/homepage_parent.xml @@ -6,24 +6,35 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - + + + + + + + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + android:id="@+id/home_parent_item_title" + android:padding="12dp" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/WatchHeaderText" + android:layout_marginEnd="0dp" + tools:text="Trending"> + + android:nextFocusUp="@id/home_child_more_info" + android:paddingHorizontal="5dp" + android:clipToPadding="false" + + android:descendantFocusability="afterDescendants" + + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + android:id="@+id/home_child_recyclerview" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:listitem="@layout/home_result_grid" /> \ No newline at end of file diff --git a/app/src/main/res/layout/library_viewpager_page.xml b/app/src/main/res/layout/library_viewpager_page.xml deleted file mode 100644 index f69f68b5..00000000 --- a/app/src/main/res/layout/library_viewpager_page.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/app/src/main/res/layout/loading_list.xml b/app/src/main/res/layout/loading_list.xml index 1ed01c8e..ccd4a8d6 100644 --- a/app/src/main/res/layout/loading_list.xml +++ b/app/src/main/res/layout/loading_list.xml @@ -1,59 +1,59 @@ + android:orientation="vertical" + android:paddingTop="@dimen/loading_margin" + android:paddingBottom="@dimen/loading_margin" + android:layout_height="200dp" + android:layout_width="match_parent"> + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:layout_width="@dimen/loading_margin" /> + android:layout_height="wrap_content" + android:layout_width="@dimen/loading_margin" /> + android:layout_height="wrap_content" + android:layout_width="@dimen/loading_margin" /> + android:layout_height="wrap_content" + android:layout_width="@dimen/loading_margin" /> + android:layout_height="wrap_content" + android:layout_width="@dimen/loading_margin" /> + android:layout_height="match_parent" + android:layout_width="@dimen/loading_margin" /> + android:layout_height="wrap_content" + android:layout_width="@dimen/loading_margin" /> diff --git a/app/src/main/res/layout/loading_poster_dynamic.xml b/app/src/main/res/layout/loading_poster_dynamic.xml deleted file mode 100644 index 11855acb..00000000 --- a/app/src/main/res/layout/loading_poster_dynamic.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/layout/player_custom_layout.xml b/app/src/main/res/layout/player_custom_layout.xml index 683a1077..2cdcbf22 100644 --- a/app/src/main/res/layout/player_custom_layout.xml +++ b/app/src/main/res/layout/player_custom_layout.xml @@ -96,36 +96,33 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + app:layout_constraintTop_toTopOf="parent" + tools:text="Hello world" /> - - - - + @@ -322,23 +319,23 @@ + tools:text="Skip Opening" /> - - + android:background="@null" + android:orientation="vertical"> + + + android:id="@+id/sort_sources_holder" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="50" + android:orientation="vertical"> - - - - - - - - - - + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:paddingBottom="10dp" + android:text="@string/pick_source" + android:textColor="?attr/textColor" + android:textSize="20sp" + android:textStyle="bold" /> + + + + + + + + + + + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:text="@string/pick_subtitle" + android:textColor="?attr/textColor" + android:textSize="20sp" + android:textStyle="bold" /> + android:textSize="15sp" + android:id="@+id/subtitles_encoding_format" + android:textColor="?attr/textColor" + android:layout_gravity="center" + android:gravity="center" + tools:text="Thai (TIS 620-2533/ISO 8859-11)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + android:contentDescription="@string/home_change_provider_img_des" + android:src="@drawable/ic_outline_settings_24" + android:visibility="gone" /> + android:requiresFadingEdge="vertical" + android:id="@+id/sort_subtitles" + android:layout_width="match_parent" + + android:layout_height="match_parent" + android:layout_rowWeight="1" + android:background="?attr/primaryBlackBackground" + android:nextFocusLeft="@id/sort_providers" + android:nextFocusRight="@id/cancel_btt" + tools:listfooter="@layout/sort_bottom_footer_add_choice" + tools:listitem="@layout/sort_bottom_single_choice" /> + android:id="@+id/apply_btt_holder" + android:orientation="horizontal" + android:layout_gravity="bottom" + android:gravity="bottom|end" + android:layout_marginTop="-60dp" + android:layout_width="match_parent" + android:layout_height="60dp"> + style="@style/WhiteButton" + android:layout_gravity="center_vertical|end" + android:text="@string/sort_apply" + android:id="@+id/apply_btt" + android:layout_width="wrap_content" /> + style="@style/BlackButton" + android:layout_gravity="center_vertical|end" + android:text="@string/sort_cancel" + android:id="@+id/cancel_btt" + android:layout_width="wrap_content" /> diff --git a/app/src/main/res/layout/provider_test_item.xml b/app/src/main/res/layout/provider_test_item.xml deleted file mode 100644 index 065b1bd8..00000000 --- a/app/src/main/res/layout/provider_test_item.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/search_history_item.xml b/app/src/main/res/layout/search_history_item.xml index ef441234..34cd59bc 100644 --- a/app/src/main/res/layout/search_history_item.xml +++ b/app/src/main/res/layout/search_history_item.xml @@ -1,42 +1,39 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:id="@+id/home_history_tab" + android:foreground="?android:attr/selectableItemBackgroundBorderless" + android:nextFocusRight="@id/home_history_remove"> + android:id="@+id/home_history_title" + android:textColor="?attr/textColor" + android:textSize="18sp" + tools:text="Hello World" + android:padding="10dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" /> + android:src="@drawable/ic_baseline_close_24" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|end" + android:layout_height="match_parent" + app:tint="?attr/white" + tools:ignore="ContentDescription" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> diff --git a/app/src/main/res/layout/search_result_grid.xml b/app/src/main/res/layout/search_result_grid.xml index f3c35ca4..98fe5812 100644 --- a/app/src/main/res/layout/search_result_grid.xml +++ b/app/src/main/res/layout/search_result_grid.xml @@ -1,89 +1,98 @@ + android:foreground="@drawable/outline_drawable" + android:focusable="true" + android:clickable="true" + android:id="@+id/search_result_root"> - - + android:layout_marginBottom="2dp" + android:elevation="10dp" + app:cardCornerRadius="@dimen/rounded_image_radius" + android:id="@+id/background_card" + app:cardBackgroundColor="?attr/primaryGrayBackground"> + android:id="@+id/imageView" + + android:duplicateParentState="true" + android:scaleType="centerCrop" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:foreground="?android:attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/search_poster_img_des" /> + + + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:layout_gravity="bottom" + android:paddingBottom="5dp" + android:paddingTop="5dp" + android:textColor="@color/textColor" + android:textStyle="bold" + android:maxLines="2" + android:paddingStart="5dp" + android:paddingEnd="5dp" + android:ellipsize="end" /> - + - - - - - - + android:layout_width="match_parent" + android:layout_height="match_parent"> + + + + + + diff --git a/app/src/main/res/layout/search_result_grid_expanded.xml b/app/src/main/res/layout/search_result_grid_expanded.xml index cf6ab3b2..710c6cf8 100644 --- a/app/src/main/res/layout/search_result_grid_expanded.xml +++ b/app/src/main/res/layout/search_result_grid_expanded.xml @@ -1,128 +1,90 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:orientation="vertical" + + android:foreground="@drawable/outline_drawable" + android:focusable="true" + android:clickable="true" + android:id="@+id/search_result_root"> + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sort_bottom_single_choice.xml b/app/src/main/res/layout/sort_bottom_single_choice.xml index 3d0a70ff..6e81cf2e 100644 --- a/app/src/main/res/layout/sort_bottom_single_choice.xml +++ b/app/src/main/res/layout/sort_bottom_single_choice.xml @@ -14,7 +14,7 @@ --> + xmlns:tools="http://schemas.android.com/tools" + style="@style/CheckLabel" + tools:text="hello" + android:id="@android:id/text1" /> diff --git a/app/src/main/res/layout/tvtypes_chips.xml b/app/src/main/res/layout/tvtypes_chips.xml index 6b13546b..33ca7762 100644 --- a/app/src/main/res/layout/tvtypes_chips.xml +++ b/app/src/main/res/layout/tvtypes_chips.xml @@ -1,6 +1,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml index cb620bb8..3a5e0929 100644 --- a/app/src/main/res/menu/bottom_nav_menu.xml +++ b/app/src/main/res/menu/bottom_nav_menu.xml @@ -1,23 +1,20 @@

+ + android:id="@+id/navigation_home" + android:icon="@drawable/home_alt" + android:title="@string/title_home"/> + android:id="@+id/navigation_search" + android:icon="@drawable/search_icon" + android:title="@string/title_search"/> + android:id="@+id/navigation_downloads" + android:icon="@drawable/netflix_download" + android:title="@string/title_downloads"/> - + android:id="@+id/navigation_settings" + android:icon="@drawable/settings_alt" + android:title="@string/title_settings"/> \ No newline at end of file diff --git a/app/src/main/res/menu/library_menu.xml b/app/src/main/res/menu/library_menu.xml deleted file mode 100644 index f21d998d..00000000 --- a/app/src/main/res/menu/library_menu.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index e59f670e..6ae2fa04 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -144,15 +144,6 @@ app:popEnterAnim="@anim/enter_anim" app:popExitAnim="@anim/exit_anim" /> - - - - + tools:layout="@layout/fragment_search" /> - - - ملصق - الصورة الدعائية + @string/result_poster_img_des ملصق الحلقة الملصق الرئيسي التالي عشوائي - رجوع + + ارجع للخلف + تغيير المصدر معاينة الخلفية + سرعة (%.2fx) تقييم: %.1f - !تم العثور على تحديث جديد -\n%s -> %s + + !تم العثور علي تحديث جديد\n%s -> %s %d دقيقة + CloudStream تشغيل بواسطة CloudStream - الرئيسية + الصفحة الرئيسية البحث التنزيلات الإعدادات - بحث… + + …بحث بحث %s… + لايوجد بيانات المزيد من الخيارات الحلقة التالية - النوع - مشاركة + أنواع + شارك فتح في الويب تخطي التحميل - تحميل… + …تحميل + أشاهده - في الإنتظار + في الانتظار مكتمل مهمل أخطط لمشاهدته لا شيء إعادة المشاهدة + مشاهدة الفيلم تشغيل بث حي تشغيل تورنت المصادر الترجمة - إعادة محاولة الاتصال … + …إعادة محاولة الاتصال ارجع للخلف تشغيل الحلقة + تحميل تم التنزيل جاري التنزيل @@ -56,14 +64,18 @@ تم إلغاء التنزيل تم التنزيل تشغيل + خطأ في تحميل الرابط التخزين الداخلي + مدبلج مترجم + حذف ملف تشغيل الملف متابعة التنزيل إيقاف التنزيل مؤقتًا + قم بتعطيل الإبلاغ عن الأخطاء تلقائيًا مزيد من المعلومات إخفاء @@ -79,35 +91,46 @@ إغلاق مسح حفظ + سرعة المُشغل + إعدادات الترجمة لون الخط لون المخطط التفصيلي لون الخلفية لون النافذة نوع الحافة - إرتفاع الترجمة + ارتفاع الترجمة الخط حجم الخط + البحث باستخدام المصادر البحث باستخدام الأنواع + %d الموزات المعطاة الى المطورين لم يتم إعطاء موز + تحديد اللغة تلقائيًا - لغات التحميل + تحميل اللغات لغة الترجمة إضغط بإستمرار لإعادة التعيين للإعدادات الافتراضية إستيراد خطوط بوضعها هنا %s متابعة المشاهدة + حذف مزيد من المعلومات + قد تكون هناك حاجة إلى VPN لكي يعمل هذا المزود بشكل صحيح - هذا المزود هو تورنت ، يوصى باستخدام شبكة ظاهرية خاصة - لا يتم توفير البيانات الوصفية بواسطة الموقع، وسيفشل تحميل الفيديو إذا لم يكن موجودًا في الموقع. + هذا المزود هو تورنت ، يوصى باستخدام شبكة ظاهرية خاصة + + .سيظهر إعلان مدته خمسة عشر ثانية إذا لم يتم توفير الفيديو في الموقع + الوصف لم يتم العثور على وصف لم يتم العثور على وصف - عرض سجل الاخطاء 🐈 + + 🐈 logcat عرض + نافذة منبثقة يستمر في التشغيل في مُشغل مصغر فوق التطبيقات الأخرى زر تغيير حجم المُشغل @@ -116,33 +139,41 @@ إعدادات ترجمة المُشغل ترجمة كروم كاست إعدادات ترجمة كروم كاست + وضع إيغنغرافي يضيف خيار السرعة في المُشغل - السحب لتقديم - اسحب من جانب إلى آخر للتحكم في موضعك في مقطع فيديو + السحب للسعي + إسحب إلى اليسار أو اليمين للتحكم في الوقت في مُشغل الفيديو السحب لتغيير الإعدادات - مرر لأعلى أو لأسفل على الجانب الأيسر أو الأيمن لتغيير السطوع أو مستوى الصوت + إسحب على الجانب الأيسر أو الأيمن لتغيير السطوع أو مستوى الصوت + تشغيل الحلقة التالية تلقائيًا تبدأ الحلقة التالية عندما تنتهي الحالية - النقر مرتان للتقديم للأمام أو للخلف + + النقر مرتان للسعي للأمام أو للخلف الضغط مرتان لإيقاف مؤقت - التحكم في مدى تقديم المُشغل(ثوان) - إضغط مرتين على الجانب الأيمن أو الأيسر للتقديم للأمام أو للخلف - اضغط مرتين في المنتصف للتوقف + التحكم في سعي المُشغل + إضغط مرتين على الجانب الأيمن أو الأيسر للسعي للأمام أو للخلف + إضغط في الوسط لإيقاف مؤقت استخدم سطوع النظام استخدم سطوع النظام في مُشغل التطبيق بدلاً من التراكب الداكن + تحديث تقدم المشاهدة مزامنة التقدم في الحلقة الحالية تلقائيًا - إسترجاع البيانات من النسخة الإحتياطية - نسخ إحتياطي للبيانات + + إسترجاع البيانات من نسخة إحتياطية + + نسخ إحتياطي تم تحميل ملف النسخة الاحتياطية فشل استيراد البيانات من الملف %s - البيانات المخزنة - إذن الوصول الى ذاكرة التخزين مفقود, من فضلك حاول مجددا. + تم تخزين البيانات بنجاح + إذن الوصول الي ذاكرة التخزين مفقود, من فضلك حاول مجددا فشل إنشاء نسخة احتياطية %s + بحث الحسابات التحديثات والنسخ الاحتياطية + معلومات البحث المتقدم يعطيك نتائج البحث مفصولة عن طريق المزود @@ -150,27 +181,31 @@ لا ترسل أي بيانات عرض حلقات الفلر للأنمي عرض المقاطع الدعائية - عرض ملصقات من كيتسو + عرض ملصقات من kitsu إخفاء جودة الفيديو المختارة من نتائج البحث + تحديث الإضافات تلقائيًا - تنزيل الإضافات تلقائيًا التحديث التلقائي - ابحث تلقائيا عن التحديثات الجديدة بعد بدء التطبيق. - التحديث إلى الاصدارات التجريبية (بيتا) + البحث تلقائيًا عن التحديثات الجديدة عند البداية + التحديث إلى الاصدارات التجريبيه (بيتا) البحث عن التحديثات التجريبية بدلاً من الإصدارات الكاملة فقط - غيت هاب + Github تطبيق روايات خفيف من نفس المطورين تطبيق أنمي من نفس المطورين انضم إلى الديسكورد إعط موزة للمطورين الموز المُعطي + لغة التطبيق + هذا المزود ليس لديه دعم كروم كاست لم يتم العثور على روابط تم نسخ الرابط إلى الحافظة تشغيل الحلقة إعادة التعيين إلى القيمة الافتراضية - عذرا، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين + عذرا ، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين + + موسم لا موسم حلقة @@ -178,16 +213,16 @@ م ح لم يتم العثور على أي حلقات + حذف الملف حذف إيقاف مؤقت إستئناف -٣٠ +٣٠ - سوف يتم الحذف نهائيا %s -\nهل أنت متأكد\? - %dm -\nمتبقية + سوف يتم الحذف نهائيا %s\nهل أنت متأكد? + %dm\nمتبقية + جاري التنفيذ اكتمل الحالة @@ -195,13 +230,16 @@ تقييم المدة الزمنية موقع - القصة + ملخص + في قائمة الانتظار الترجمة ليست موجودة الإفتراضي + فارغ مستخدم التطبيق + أفلام مسلسلات @@ -217,30 +255,33 @@ فيلم مسلسل - كرتون - أنيمي - أوفا + كارتون + @string/anime + @string/ova تورنت وثائقي دراما آسيوية بث حي +18 فيديو + خطأ في المصدر خطأ بعيد خطأ في جهاز العرض خطأ غير متوقع في المُشغل خطأ في التنزيل ، تحقق من أذونات التخزين + حلقة كروم كاست مرآة كروم كاست تشغيل في التطبيق %s تشغيل في - تشغيل في الويب + تشغيل في الويب نسخ الرابط التحميل التلقائي تحميل بجودات مختلفة إعادة تحميل الروابط تحميل الترجمة + ملصق الجودة ملصق مدبلج ملصق مترجم @@ -249,69 +290,102 @@ show_dub_key show_sub_key show_title_key - التحكم في عناصر الواجهة على الملصق + التحكم في عناصر الواجهة علي الملصق + لم يتم العثور على تحديثات تحقق من التحديثات + قفل تغيير الحجم مصدر تخطي المقدمة + لا تظهر مرة أخرى تخطي هذا التحديث تحديث - جودة المشاهدة المفضلة (WiFi) - أقصى عدد لحروف عنوان مُشغل الفيديو + جودة المشاهدة المفضلة + أقصي عدد حروف لعنوان مُشغل الفيديو أبعاد مُشغل الفيديو + حجم ذاكرة التخزين المؤقت للفيديو طول التخزين المؤقت - التخزين المؤقت للفيديو على القرص + التخزين المؤقت للفيديو علي القرص مسح التخزين المؤقت للصورة والفيديو - يتسبب في حدوث أعطال إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات الذاكرة المنخفضة، مثل تلفزيون أندرويد. - يسبب مشاكل إذا تم ضبطه على مستوى مرتفع جدا على الأجهزة ذات مساحة التخزين المنخفضة، مثل تلفزيون أندرويد. - إستخدام DNS بدلا من HTTPS + + سوف يسبب بعض الأعطال إذا تم إعداد قيمة عالية جدًا. لا\تغير القيمة إذا كان لديك ذاكرة تخزين عشوائية منخفضة كتلفاز أندرويد أو هاتف قديم + قد يسبب مشاكل مع حجم ذاكرة التخزين المنخفضة كأجهزة تلفاز الأندرويد إذا أعددت قيمة عالية جدًا + + DNS فوق HTTPS مفيد لتجاوز حجب مزود خدمة الإنترنت - موقع بديل (نسخة) + + نسخ موقع حذف موقع - إضافة نسخة من موقع موجود، بعنوان URL مختلف + إضافة نسخة من موقع موجود, بعنوان رابط مختلف + مسار التنزيل - عنوان URL لخادم NGINX + + عنوان رابط سيرفر Nginx + عرض أنمي مدبلج / مترجم + ملائمة الشاشة امتداد تكبير + إخلاء مسؤولية legal_notice_key - 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. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. + 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. + + In case of copyright infringement, please directly contact the responsible parties or the streaming websites. + + The app is purely for educational and personal use. + + CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. + CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or + manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, + user-friendly interface. + + It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. + عام زر العشوائي - يظهر الزر على الصفحة الرئيسية والذي يمكنه اختيار فيلم عشوائي أو مسلسل تلفزيوني من الصفحة الرئيسية - لغات المزود + إظهار زر العشوائي علي الشاشة الرئيسية + لغات الموفر واجهة التطبيق - المحتوى المفضل - تفعيل محتوى خاص للبالغين داخل المزودين المدعومين + المحتوي المفضل + تفعيل محتوي البالغين داخل المزودين المدعومين فك تشفير الترجمة المصادر الواجهة + + أوتوماتيك - واجهة التلفاز - واجهة الهاتف - واجهة المحاكي + واجهة تلفاز + واجهة هاتف + واجهة محاكي + اللون الأساسي مظهر التطبيق - مكان عنوان الملصق + موضع عنوان الملصق وضع العنوان تحت الملصق + + anilist_key mal_key opensubtitles_key nginx_key - كلمة المرور - إسم المستخدم - البريد الإلكتروني + password123 + MyCoolUsername + hello@world.com 127.0.0.1 - إسم الموقع - رابط الموقع - اللغة (الإنجليزية) + MyCoolSite + example.com + Language code (en) + %s %s حساب - تسجيل الخروج + تسجيل خروج تسجيل الدخول تبديل الحساب إضافة حساب @@ -332,10 +406,11 @@ مزامنة تقييم %d / 10 - /\?\? + /?? /%d - %s تم التوثيق - تعذر تسجيل الدخول في %s + تم توثيق %s + فشل توثيق %s + لا شيء عادي @@ -348,11 +423,12 @@ ظل رفع مزامنة الترجمة - 1000 مللي ثانية + 1000ms تأخير الترجمة - إستعملْ هذا إذا تَظْهرُ الترجماتَ %d m في وقت مبكر جداً - استخدم هذا إذا تم عرض الترجمة %d مللي ثانية متأخرًا جدًا + استخدم هذا إذا كانت الترجمة تُعرض %dms مبكرًا جدًا + استخدم هذا إذا كانت الترجمة تُعرض %dms متأخرًا جدًا لا تأخير في الترجمة + نصٌّ حكيمٌ لهُ سِرٌّ قاطِعٌ وَذُو شَأنٍ عَظيمٍ مكتوبٌ على ثوبٍ أخضرَ ومُغلفٌ بجلدٍ أزرق - مُوصى به + + مُوصي به تم تحميل %s إختيار ملف تحميل من الانترنت @@ -368,59 +445,63 @@ رئيسي مساعد الخلفية + مصدر عشوائي + قريبا… - تصوير كام - تصوير كام قطع - تصوير كام جودة عالية + + Cam + Cam + Cam HQ - عالِ الدقة + HD TS TC - بلو راي + BlueRay WP DVD 4K SD - دقة فائقة - المستوى الديناميكي العالي + UHD + HDR SDR - الويب + Web + صورة الملصق المُشغل الأبعاد والعنوان العنوان الأبعاد - معرف غير صالح + هوية غير صالحة بيانات غير صالحة عنون رابط غير صالح خطأ ازالة التسميات التوضيحية من الترجمة ازالة البرمجيات الخبيثة من الترجمة - تصفية حسب لغة الوسائط المفضلة + فلترة تبعا للغة المحتوي المفضلة اكسترا مقطع دعائي رابط الفيديو - المرجع + Referer التالي شاهد الفيديوهات بهذه اللغات السابق تخطي الإعداد - قم بتغيير مظهر التطبيق ليناسب جهازك - إبلاغ الأعطال + تغيير شكل البرنامح حتي يلائم جهازك + ابلاغ الاعطال ماذا تريد ان تري تم الإضافات إضافة مستودع إسم المستودع - عنوان URL للمستودع + عنوان رابط المستودع تم تحميل الإضافة تم إزالة الإضافة - تعذر التحميل %s + فشل التحميل %s 18+ - بدأ تنزيل %d %s … - تم التنزيل %d %s + بدأ تنزيل %d %s + تم تنزيل %d %s بنجاح جميع %s محملة بالفعل تحميل مكثف إضافة @@ -431,23 +512,22 @@ تم تحميل: %d مُعطل %d غير مُحمل: %d - لا يحتوي CloudStream على مواقع مثبتة بشكل افتراضي. تحتاج إلى تثبيت المواقع من المستودعات. -\n -\nبسبب إزالة قانون الألفية الجديدة لحقوق طبع ونشر المواد الرقمية بدون تفكير مسبق بواسطة Sky UK Limited 🤮 ، لا يمكننا ربط موقع المستودع في التطبيق. -\n -\nانضم إلى Discord أو ابحث عبر الإنترنت. + قم بإضافة مستودع لكي يتم تثبيت إضافات المواقع عرض مستودعات المجتمع قائمة عامة جميع الترجمات حروف كبيرة - تحميل جميع الإضافات من هذا المستودع\? - %s (معطل) + + تحميل جميع الإضافات من هذا المستودع? + %s (Disabled) المسارات مسار الصوت مسار الفيديو - تطبيق بعد إعادة التشغيل - الوضع الآمن قيد التشغيل - تم إيقاف تشغيل جميع الملحقات بسبب عطل لمساعدتك في العثور على الإضافة التي تسبب مشكلة. + تطبيق بعد إعادة الفتح + + وضع الامان مفعل + An unrecoverable crash occurred and we\'ve automatically disabled all extensions, so you can find and remove the extension which is causing trouble. عرض بيانات الاعطال + تقييم: %s الوصف إصدار @@ -456,109 +536,7 @@ المؤلفون مدعوم اللغة + قائمة HLS مُشغل الفيديو المفضل - مسح السجل - السجل - عرض زر تخطي المقدمة/الخاتمة - طاقم العمل: %s - %d يوم %d ساعة %d دقيقة - %d ساعة %d دقيقة - الفيلير - فتح(تشغيل) - %s %d%s - المكونات الإضافية المحدثة %d - VLC - MPV - اسقاط فيديو الويب - متصفح الإنترنت - تخطي %s - الافتتاح - النهاية - الخلاصة - نهاية مختلطة - افتتاح مختلط - المقدمة - النص كثير جدا. تعذر الحفظ في الحافظة. - علّمه كفيديو تمت مشاهدته - نعم - - %s الحلقة %d - سيتم إصدار الحلقة %d في - تعذر تثبيت الإصدار الجديد من التطبيق - تثبيت الإضافة أولا - مشغل داخلي - لم يتم العثور على التطبيق - جميع اللغات - الإعتمادات - ‌تنزيل تحديث التطبيق… - ‏تثبيت تحديث التطبيق… - %d دقيقة - %d-%d - %d %s - هل أنت متأكد أنك تريد الخروج؟ - قم بتثبيت جميع المكونات الإضافية التي لم يتم تثبيتها بعد تلقائيا من المستودعات المضافة. - مثبت الحزم - بعض الهواتف لا تدعم مثبت الحزمة الجديد. جرب الخيار القديم إذا لم يتم تثبيت التحديثات. - قياسي - مثبت الحزمة - التخطيط - إعادة عملية الإعداد - الروابط - تحديثات التطبيق - النسخ الإحتياطي - الإضافات - الإجراءات - الذاكرة المؤقتة - الإيماءات - ميزات المشغل - الترجمات - الإفتراضي - المميزات - المظهر - تشغيل المقطع الدعائي - سيتم تحديث التطبيق عند الخروج - بدأ التحديث - تم تنزيل الإضافة - إزالة من المشاهدة - الترتيب الأبجدي (من الألف إلى الياء) - اختر المكتبة - المتصفح - محدث (من الأحدث إلى الأقدم) - يبدو أن هذه القائمة فارغة ، حاول التبديل إلى قائمة أخرى - التقييم (من الأعلى إلى الأدنى) - التقييم (من الأدنى إلى الأعلى) - الترتيب الأبجدي (من ي إلى أ) - يبدو أن مكتبتك فارغة :( -\nتسجيل الدخول إلى حساب مكتبة أو إضافة عروض إلى مكتبتك المحلية - محدث (من القديم إلى الجديد) - فرز حسب - افرز - فتح بواسطة - المكتبة - تم العثور على ملف الوضع الآمن! -\nلا يتم تحميل أي ملحقات عند بدء التشغيل حتى تتم إزالة الملف. - مدة التقديم عنما يكون المشغل مخفيا - مدة التقديم - المشغل مخفي - تلفزيون أندرويد - مدة التقديم عنما يكون المشغل مرئيا - مدة التقديم- المشغل المرئي - فشل - نجح - إختبار المزود - إعادة التشغيل - سجل - بَدأ - إيقاف - تحديث العروض التي تم الاشتراك فيها - إلغاء الاشتراك من %s - تم إصدار الحلقة %d! - مشترك - مشترك في %s - تجاوز مزود خدمة الإنترنت - استرجاع - فشل الوصول إلى GitHub ، وتمكين وكيل jsdelivr. - باستخدام jsdelivr ، يمكن تجاوز حظر GitHub. قد يؤخر التحديثات لبضعة أيام. - وكيل raw.githubusercontent.com - جودة المشاهدة المفضلة (بيانات الجوال) diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 301242cd..e32d3d93 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -1,34 +1,41 @@ - + %s еп. %d Актьори: %s Епизод %d ще бъде пуснат след - %dd %dh %dm + %DD %dh %dm %dh %dm %dm - Poster - Episode Poster - Main Poster + + + Плакат + @string/result_poster_img_des + Плакат на епизод + Основен плакат Следващ произволен Върни се Смяна на доставчика Визуализация на фона + + Скорост (%.2fx) Оценка: %.1f - Намерена е нова актуализация! -\n%s -> %s + Намерена е нова актуализация!\n%s -> %s Шаблон %d мин + CloudStream Пусни с CloudStream Начало Търсене Изтегляния Настройки + Търсене… - Търси %s… + Търсете %s... + Няма данни Още опций Следващ епизод @@ -37,6 +44,7 @@ Отвори в браузъра Пропусни зареждането Зареждане… + Гледане На изчакване Завършено @@ -44,14 +52,17 @@ План за гледане Нито един Повторно гледане + Пускане на филм Възпроизвеждане на живо Поточно предаване на торент Източници субтитри - Повторен опит за свързване… + Повторен опит за свързване... Върни се Пусни епизод + + Изтегли Изтеглено Изтегля се @@ -61,14 +72,18 @@ Изтеглянето е отменено Изтеглянето е завършено Поток + Грешка при зареждане на връзки Вътрешна памет + Дублаж Субтитри + Изтрий файла Възпроизвеждане на файл Възобновете изтеглянето Пауза на изтеглянето + Деактивирайте автоматичното докладване на грешки Повече информация Скрий @@ -84,7 +99,9 @@ Затвори Изчисти Запазване + Скорост на възпроизвеждане + Настройки на субтитрите Цвят на текста Цвят на контура @@ -94,26 +111,35 @@ Височина на субтитрите Шрифт Размер на шрифта + Търсене чрез доставчици Търсене с помощта на типове + %d Банан/а даден/и на разработчиците Не е/са даден/и Банан/и + Автоматичен избор на език Изтегляне на езици Език на субтитрите Задръжте, за да нулирате по подразбиране Импортирайте шрифтове, като ги поставите в %s Продължете да гледате + Премахване Повече информация @string/home_play + Може да е необходим VPN, за да работи правилно този доставчик Този доставчик е торент, препоръчва се VPN + Метаданните не се предоставят от сайта, зареждането на видео ще бъде неуспешно, ако не съществува на сайта. + Описание Няма намерен съдържание Няма намерено описание - Покажи Logcat 🐈 + + Покажи logcat 🐈 + Картина в картина Продължава възпроизвеждането в миниатюрен плейър върху други приложения Бутон за преоразмеряване на плейъра @@ -122,33 +148,42 @@ Настройки на субтитрите на плейъра Chromecast субтитри Настройки за субтитри на Chromecast + Режим Eigengravy (промяна скорост на възпроизвеждане) Добавя опция за скорост в плейъра Плъзнете за преместване Плъзнете наляво или надясно, за да контролирате времето във видеоплейъра Плъзнете, за да промените настройките Плъзнете наляво или надясно, за да промените яркостта или силата на звука + Автоматично пускане на следващ епизод Започнете следващия епизод, когато текущият приключи + Докоснете двукратно за превъртане Докоснете два пъти за пауза Размер на превъртане Докоснете два пъти от дясната или лявата страна, за да превъртите напред или назад Докоснете в средата, за да направите пауза Използвайте яркостта на системата - Използвайте системна яркост в плейъра на приложението вместо тъмно наслагване + Използвайте системна яркост в плейъра на приложението вместо тъмно + наслагване + Актуализирайте прогреса на гледане Автоматично синхронизирайте прогреса на текущия си епизод + Възстановете данните от архив + Архивиране на данни Зареден архивен файл Неуспешно възстановяване на данни от файл %s - Съхранени данни - Липсват разрешения за съхранение. Моля, опитайте отново. + Успешно съхранени данни + Липсват разрешения за съхранение, моля, опитайте отново Грешка при архивирането на %s + Търсене Акаунти Актуализации и архивиране + Информация Подробно търсене Дава ви резултатите от търсенето, разделени по доставчик @@ -156,8 +191,9 @@ Не изпраща данни Показване заместващ епизод за аниме Показване на трейлъри - Покажете плакати от Kitsu - Скрий избраното видео качество в резултатите от търсенето + Покажете плакати от kitsu + Скриване на избраното видео качество в резултатите от търсенето + Автоматични актуализации на плъгини Показвай актуализации на приложението Автоматично търси нови актуализации при стартиране @@ -169,13 +205,17 @@ Присъединете се към Discord Дайте банан/и разработчиците Даден/и банан/и + Език на приложението + Този доставчик няма поддръжка за Chromecast Няма намерени връзки Връзката е копирана в клипборда Пусни епизода Възстановяване на стойността по подразбиране - За съжаление приложението се срина. Анонимен доклад за грешка ще бъде изпратен до разработчиците + За съжаление приложението се срина. Анонимен доклад за грешка ще бъде изпратен до + разработчиците + Сезон %s %d%s Без сезон @@ -186,16 +226,17 @@ С Е Няма намерени епизоди + Изтрий файла Изтрий Пауза Продължи -30 30 - Това ще изтрие за постоянно %s -\nСигурни ли сте\? - %dm -\nостава + Това ще изтрие за постоянно %s\nСигурни ли сте? + %dm\nостава + + Продължава Завършен Статус @@ -204,12 +245,16 @@ Продължителност Уебсайт Синопсис + На опашката Без субтитри По подразбиране + Безплатно Използвано Приложения + + Филми Телевизионен сериал Анимационни филми @@ -221,6 +266,8 @@ На живо Разпоретини други + + Филм Серия Анимационен филм @@ -232,11 +279,13 @@ Поток на живо Разпоретинa Видео + Грешка в източника Дистанционна грешка Грешка в рендъра Неочаквана грешка на плеъра Грешка при изтегляне, проверете разрешенията за съхранение + Епизод за Chromecast Chromecast огледало Пусни в приложението @@ -247,41 +296,55 @@ Изтегляне на огледало Презареждане на връзки Изтегляне на субтитри + покажи качество Покажи дублаж Покажи субтитри Заглавие Превключване на елементите на потребителския интерфейс на плаката + Няма намерена актуализация Проверка за актуализация + Заключен Преоразмеряване Източник Пропусни + Не показвай отново Пропуснете тази актуализация Актуализация Предпочитано качество за гледане Максимален брой знаци за заглавие във видеоплейъра Разделителна способност на видео плейъра + Размер на видео буфера Дължина на видео буфера Видео кеш на диск Изчистете кеша за видео и изображения - Причинява сривове, ако е зададено твърде високо на устройства с малко памет, като Android TV. - Причинява проблеми, ако е зададено твърде високо на устройства с малко място за съхранение, като Android TV. + + Ще предизвика случайни сривове, ако е зададено твърде високо. Не променяйте, ако имате малко количество RAM, като Android TV или стар телефон + Може да причини проблеми на системи с малко място за съхранение, като устройства с Android TV, ако го зададете твърде високо + DNS през HTTPS Полезно за заобикаляне на блокирания от ISP доставчик + Сайт за клониране Премахване на сайта - Добавяне на клонинг на съществуващ сайт с различен URL + Добавяне на клонинг на съществуващ сайт с различен URL адрес + Път за изтегляне - URL адрес на сървър NGINX + + URL адрес на сървъра на Nginx + Показване на дублирани/субирани аниме + Побиране в екрана Разтягане Мащабиране + Опровержение + Общ Случаен бутон Показване на произволен бутон на началната страница @@ -292,21 +355,25 @@ Кодиране на субтитрите Доставчици Оформление + Автоматично ТВ оформление Оформление като телефон Оформление като емулатор + Основен цвят Тема на приложението Местоположение на заглавието на плаката Поставете заглавието под плаката + парола123 - Моето готино потребителско име + MyCoolUsername hello@world.com 127.0.0.1 - Моят готин сайт + MyCoolSite example.com Езиков код (en) + %s %s @@ -321,10 +388,12 @@ Синхронизиране Оценен %d / 10 - /\?\? + /?? /%d - %s удостоверен - Не можах да вляза в %s + Удостоверен %s + Неуспешно удостоверяване на %s + + Нито един Нормално Всичко @@ -335,11 +404,13 @@ Сянка Повдигнати Синхронизиране на суб - 1000.ms + 1000ms Забавяне на субтитрите - Използвайте това, ако субтитрите се показват %d ms твърде рано - Използвайте това, ако субтитрите се показват %d ms твърде късно + Използвайте това, ако субтитрите се показват %dms твърде рано + Използвайте това, ако субтитрите се показват %dms твърде късно Без забавяне на субтитрите + + Бързата кафява лисица прескача мързеливото куче + Препоръчва се Заредено %s Зареди от файл @@ -355,9 +427,12 @@ Главен Поддържащ Заден план + Източник Случаен + Очаквайте скоро… + Cam Cam Cam @@ -365,7 +440,7 @@ HD TS TC - Blu-ray + BlueRay WP DVD 4K @@ -374,14 +449,15 @@ HDR SDR Web + Снимка на плакат Плеър Резолюция и заглавие Заглавие Резолюция - Невалиден ID + Невалиден идентификатор Невалидни данни - Невалиден URL + Невалиден адрес Грешка Премахнете затворените надписи от субтитрите Премахнете рекламирането в субтитрите @@ -401,13 +477,13 @@ Разширения Добавяне на хранилище Име на хранилище - URL на хранилището + URL адрес на хранилището Приставката е заредена Приставката е изтрита - Не може да зареди %s + Неуспешно зареждане %s 18+ - Започна да изтегля %d %s… - Изтеглено %d %s + Започна да изтегля %d %s + Изтеглено %d %s успешно Всички %s вече са изтеглени Пакетно изтегляне Плъгин @@ -419,23 +495,22 @@ Деактивирано: %d Не е изтеглено: %d Актуализирани %d плъгини - CloudStream няма инсталирани сайтове по подразбиране. Трябва да инсталирате сайтовете от хранилища. -\n -\nПоради безмозъчно премахване на DMCA от Sky UK Limited 🤮 не можем да свържем сайта на хранилището в приложението. -\n -\nПрисъединете се към нашия Дискорд или търсете онлайн. + CloudStream няма инсталирани сайтове по подразбиране. Трябва да инсталирате сайтовете от хранилища.\n\nПоради безмозъчно премахване на DMCA от Sky Uk Limited 🤮 не можем да свържем сайтовете на хранилищата в приложението.\n\nПрисъединете се към нашия дискорд за връзки или търсете онлайн. Вижте хранилищата на общността Публичен списък Всички субтитри с главни букви - Изтегляне на всички добавки от това хранилище\? - %s (Деактивиран) + + Изтегляне на всички добавки от това хранилище? + %s (Disabled) Потоци Аудио потоци Видео потоци Приложете при рестартиране - Безопасен режим включен - Всички разширения бяха изключени поради срив, за да ви помогне да намерите това, което създава проблеми. + + Безопасният режим е активиран + Възникна непоправим срив и ние автоматично деактивирахме всички разширения, така че можете да намерите и премахнете разширението, което причинява проблеми. Вижте информация за срива + Оценка: %s Описание Версия @@ -445,57 +520,14 @@ Поддържани език Първо инсталирайте разширението + HLS плейлист + Предпочитан видео плеър Вътрешен плеър VLC MPV Уеб видео предаване - Уеб браузър + Браузър Приложението не е намерено - Автоматично изтегли добавки - Всички езици - Пропусни %s - Отваряне - Затваряне - Резюме - История - Твърде много текст. Не може да се запази в клипборда. - Маркирай като гледано - Сигурни ли сте, че искате да излезете\? - Да - Не - Инсталиране на актуализация на приложението… - Не можа да се инсталира новата версия на приложението - Покажи изскачащи прозорци при отваряне/затваряне - Изтегля се актуализация на приложението… - Смесено отваряне - Смесено затваряне - Кредити - въведение - Изчистване на историята - Автоматично инсталиране на всички все още неинсталирани добавки от добавени хранилища. - APK Инсталатор - Някои телефони не поддържат новия пакет за инсталиране. Опитайте предпоследената опция, ако актуализациите не се инсталират. - Пусни трейлър - Връзки - Актуализации на приложението - Разширения - Действия - Предишна версия - Пакетен инсталатор - Възможности на плеъра - По подразбиране - Изгледи - Субтитри - Възможности - Повторете процеса на настройка - Архивиране - Кеш - Жестове - Шаблон - Приставката е изтеглена - Приложението ще се актуализира при изход от него - Започна Актуализация - Премахване от гледани - + \ No newline at end of file diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml deleted file mode 100644 index 71d5d6d0..00000000 --- a/app/src/main/res/values-bn/strings.xml +++ /dev/null @@ -1,151 +0,0 @@ - - - পোস্টার - ক্লাউডস্ট্রিম দিয়ে চালান - হোম - অভিনয়েঃ %s - %dদিন %dঘন্টা %dমিনিট - %dঘন্টা %dমিনিট - %d মিনিট - এপিসোড পোস্টার - মূল পোস্টার - পরবর্তী র‍্যান্ডম - ফিরে যান - প্রোভাইডার পরিবর্তন করুন - ব্যাকগ্রাউন্ড দেখান - গতি (%.2f গুণ) - মূল্যায়নঃ %.1f - নতুন আপডেট এসেছে! -\n%s -> %s - ফিলার - %d মিনিট - ক্লাউডস্ট্রিম - খুঁজুন - ডাউনলোডসমূহ - সেটিংস - %s এপি %d - এপিসোড %d রিলিজ হবে - শেয়ার - ব্রাঊজারে খুলুন - জনরা - খুঁজুন… - খুঁজুন %s… - মুভি চালান - স্ট্রিম চালান - পিছনে যান - কোন তথ্য নেই - আরো অপশন - পরবর্তি এপিসোড - লোডিং বাদ দিন - লোডিং… - দেখা হচ্ছে - স্থগিত - শেষ - বাদ - দেখার ইচ্ছায় - কোন কিছুই না - পুনরায় দেখা হচ্ছে - টরেন্ট স্ট্রিম করুন - সোর্সসমূহ - সাবটাইটেলসমূহ - আবার সংযোগ দেওয়ার চেষ্টা করুন… - এপিসোড চালান - ডাউনলোড - ডাউনলোড হয়েছে - ডাউনলোড চলছে - ডাউনলোড স্থগিত - ডাউনলোড শুরু - ডাউনলোড বাদ - ডাউনলোড শেষ - স্ট্রিম - লিংক লোডিং ব্যর্থ - ডাব - সাব - ফাইল প্লে করুন - ডাউনলোড থামান - আটো বাগ রিপোর্ট বন্ধ করুন - আরো তথ্য - বন্ধ করুন - প্লে - তথ্য - বুকমার্কসমূহ - বাদ দিন - বুকমার্ক করুন - এপ্লাই করুন - বাদ দিন - কপি করুন - বন্ধ করুন - মুছুন - সেভ করুন - ভিডিও এর গতি - ফন্ট আউটলাইন কালার - সাবটাইটেল এজের ধরণ - সাবটাইটেল এর উচ্চতা - প্রভাইডার অনুযায়ি খুঁজুন - ধরণ অনুযায়ি খুঁজুন - %d টা বেনেনা ডেভদের দেয়া হয়েছে - কোন বেনেনা দেয়া হয়নি - ভাষা ডাউনলোড - সাবটাইটেল ভাষা - প্রাথমিক সেটিংসে নিতে চেপে রাখুন - দেখতে থাকুন - মুছুন - আরো তথ্য - বিবরণ - কোন প্লট পাওয়া যায়নি - কোন বিবরণ পাওয়া যায়নি - পিকচার ইন পিকচার - অন্য অ্যাপ এর উপর ভিডিও চালাতে থাকুন - প্লেয়ারের সাইজ পরিবর্তন বাটন - ডাউনলোড ব্যর্থ - ইন্টারনাল স্টোরেজ - বুকমার্ক ফিল্টার করুন - ফাইল ডিলিট করুন - ডাউনলোড চালু করুন - সাবটাইটেল সেটিংস - সাবটাইটেল ব্যাকগ্রাউন্ড কালার - ফন্ট কালার - এটা একটি টরেন্ট প্রভাইডার, ভিপিএন ব্যবহার করা উচিৎ - উইন্ডো কালার - ফন্টের সাইজ - ফন্ট - স্বয়ংক্রিয় ভাষা - সাবটাইটেল ফন্ট যোগ করতে এখানে ফাইলটি রাখুন %s - লগক্যাট দেখান 🐈 - এই প্রভাইডার ঠিকভাবে চালাতে ভিপিএন লাগতে পারে - প্রভাইডার থেকে মেটাডেটা পাওয়া যায়নি, যদি সম্পূর্ন সাইটে না পাওয়া যায় তাইলে ভিডিও লোড হবে না। - ইতিহাস - সাবটাইটেল - প্লেয়ার এর সাবটাইটেলসমূহের সেটিংস - ক্রোমক্যাস্ট এ সাবটাইটেলসমূহ - ক্রোমক্যাস্ট এ সাবটাইটেল সমূহের সেটিংস - কালো প্রান্ত অপসারণ করুন - প্লেয়ার এ দ্রুততা এর অপশন যুক্ত করে - অনুসন্ধান করুন - অ্যাকাউন্টসমূহ - কোনো উপাত্ত পাঠাবে না - বিরতি দিতে মাঝে চাপুন - সিস্টেম এর উজ্জ্বলতা ব্যবহার করুন - ট্রেইলার চালু করুন - ভিডিওপ্লেয়ার এ সময় নিয়ন্ত্রণ করতে, ডানে অথবা বামে সোয়াইপ করুন - সেটিংস পরিবর্তন করতে সোয়াইপ করুন - উজ্জ্বলতা অথবা স্বরমাত্রা পরিবর্তন করতে যথাক্রমে বামে অথবা ডানে সোয়াইপ করুন - স্বয়ংক্রিয়ভাবে পরবর্তী এপিসোড প্লে করুন - পরবর্তী এপিসোডটি চালু করুন যখন চলতি এপিসোডটি শেষ হয় - থামতে দুইবার চাপুন - ডাটা জমা হয়েছে - স্টোরেজ এর অনুমতি অনুপস্থিত। দয়া করে আবার চেষ্টা করুন। - তথ্য - ট্রেইলার প্রদর্শন করুন - কিটসু হতে পোস্টারসমূহ প্রদর্শন করুন - স্বয়ংক্রিয়ভাবে প্লাগিন এর হালনাগাদ - স্বয়ংক্রিয়ভাবে প্লাগিনসমুহের ডাউনলোড - হালনাগাদ ও ব্যাকআপ - অ্যাপ এর হালনাগাদ দেখান - খুঁজতে সোয়াইপ করুন - @string/result_poster_img_des - @string/home_play - আগাতে ডবল ট্যাপ করুন - আইজেনগ্রাভি মোড - আপডেট শুরু হয়েছে - diff --git a/app/src/main/res/values-bp/strings.xml b/app/src/main/res/values-bp/strings.xml index 13b34872..bffaf804 100644 --- a/app/src/main/res/values-bp/strings.xml +++ b/app/src/main/res/values-bp/strings.xml @@ -1,7 +1,7 @@ - + %s Ep %d Elenco: %s @@ -9,6 +9,7 @@ %dd %dh %dm %dh %dm %dm + Poster @string/result_poster_img_des @@ -18,20 +19,23 @@ Go back Change Provider Preview Background + Velocidade (%.2fx) Nota: %.1f - Nova atualização encontrada! -\n%s -> %s + Nova atualização encontrada!\n%s -> %s Filler %d min + CloudStream Início Procurar Downloads Configurações + Procurar… Procurar no %s… + Sem dados Mais Opções Próximo episódio @@ -40,6 +44,7 @@ Abrir no Navegador Pular Carregamento Carregando… + Assistindo Em espera Completado @@ -47,6 +52,7 @@ Planejando assistir Nenhum Reassistindo + Assistir Filme Transmitir Torrent Fontes @@ -55,6 +61,7 @@ Voltar Assistir Episódio + Baixar Baixado Baixando @@ -64,14 +71,18 @@ Download Cancelado Download Finalizado Transmitir + Erro Carregando Links Armazenamento Interno + Dub Leg + Deletar Arquivo Assistir Arquivo Retomar Download Pausar Download + Desativar relatório automático de erros Mais info Esconder @@ -87,7 +98,9 @@ Fechar Limpar Salvar + Velocidade do Reprodutor + Configurar Legendas Cor do Texto Cor do Contorno @@ -97,26 +110,35 @@ Elevação da Legenda Fonte Tamanho da Fonte + Pesquisar usando fornecedor Pesquisar usando genêros + %d Benenes doados aos desenvolvedores Nenhuma Benenes doada + Autosseleção de Lingua Baixar Linguas Lingua da legenda Segure para retornar a configuração padrão Importe fontes colocando elas em %s Continue Assistindo + Remover Mais Info - @string/home_play + @string/home_play + Uma VPN pode ser necessária para esse fornecedor funcionar corretamente Esse fornecedor é um torrent, uma VPN é recomendada + Metadados não são oferecidas pelo site, o carregamento do video pode falhar se ele não existir no site. + Descrição Sinopse não encontrada Descrição não encontrada - Mostrar Logcat 🐈 + + Mostrar logcat 🐈 + Picture-in-picture Continua a reprodução em um player miniatura que sobrepõe outros aplicativos Redimensionar player @@ -125,6 +147,7 @@ Configurações de legendas do Player Legendas do Chromecast Configurações de legendas do Chromecast + Modo Eigengravy Adiciona um botão de velocidade no player Deslize para avançar o vídeo @@ -138,18 +161,23 @@ Toque no meio para pausar Usar brilho do sistema Usar brilho do sistema no player ao invés da sobreposição escura + Atualizar progresso assistido Sincronize automaticamente seu progresso do episódio + Restaurar dados de backup + Fazer Backup Arquivo de Backup carregado Falha em restaurar dados do arquivo %s Dados salvos com sucesso Permissões de armazenamento faltando, por favor tente de novo Erro no backup de %s + Procurar Contas Atualizações e backup + Info Procura Avançada Mostrar resultados separados por fornecedor @@ -158,8 +186,10 @@ Mostrar episódios de Filler em anime Mostrar trailers Mostrar posters do kitsu - Esconder qualidades de vídeo selecionadas nos resultados da Pesquisa - Atualizações de plugin automáticas + + Esconder qualidades de vídeo selecionadas nos resultados da Pesquisa + Atualizações de plugin automáticas + Mostrar atualizações do app Automaticamente procurar por novas atualizações ao abrir Atualizar para pré-lançamento @@ -170,13 +200,18 @@ Junte-se ao Discord Dar um benene para os desenvolvedores Benene dada + Linguagem do App + Esse fornecedor não possui suporte para Chromecast Nenhum link encontrado Link copiado para área de transferência Assistir Episódio Restaurar para o padrão - Desculpe, a aplicação travou. Um relatório de erro anônimo será enviado para os desenvolvedores + Desculpe, a aplicação travou. Um relatório de erro anônimo será enviado para os + desenvolvedores + + Temporada Nenhuma Temporada Episódio @@ -184,16 +219,17 @@ S E Nenhum Episódio encontrado + Deletar Arquivo Deletar Pausar Retomar -30 +30 - Isso apagará %s permanentemente -\nVocê tem certeza\? - %dm -\nsobrando + Isso apagará %s permanentemente\nVocê tem certeza? + %dm\nsobrando + + Em andamento Concluído Estado @@ -202,12 +238,15 @@ Duração Site Sinopse + Na fila Sem Legendas Padrão + Livre Usado App + Filmes Séries @@ -217,8 +256,9 @@ Documentários OVA Dramas Asiáticos - Transmissões em Direto - Outros + Transmissões em Direto + Outros + Filme Série @@ -228,12 +268,14 @@ Torrent Documentário Drama Asiático - Transmissão em Direto + Transmissão em Direto + Erro de fornecimento Erro remoto Erro de renderização Erro de player inesperado Erro ao baixar, verifique as permissões de armazenamento + Episódio pelo Chromecast Alternativa pelo Chromecast Assistir no App @@ -244,42 +286,70 @@ Baixar por servidor alternativo Recarregar links Baixar legendas + Etiqueta de qualidade Etiqueta Dub Etiqueta Sub Título Alternar elementos da interface no pôster + Nenhuma Atualização encontrada Procurar nova Atualização + Bloquear Mudar Tamanho Fonte Pular OP + Não mostrar de novo Pular essa Atualização Atualizar Qualidade preferida Máximo de caracteres do título de vídeos Resolução do player de vídeo + Tamanho do buffer do vídeo Comprimento do buffer do vídeo Cache do vídeo em disco Limpar cache de vídeo e imagem + Causará travamentos aleatórios se definido muito alto. Não mude caso tiver pouca memória RAM, como um Android TV ou um telefone antigo Pode causar problemas em sistemas com pouco espaço de armazenamento se definido muito alto, como em dispositivos Android TV + DNS sobre HTTPS Útil para burlar bloqueios de provedores de internet + Clonar site Remover site - Adiciona um clone de um site existente, com uma URL diferente + Adiciona um clone de um site existente, com uma url diferente + Caminho para Download + Url do servidor Nginx + Mostrar Anime Dublado/Legendado + Ajustar para a Tela Esticar Zoom + Aviso Legal - 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. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. + 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. + + In case of copyright infringement, please directly contact the responsible parties or the streaming websites. + + The app is purely for educational and personal use. + + CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. + CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or + manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, + user-friendly interface. + + It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. + Geral Botão Aleatório Mostra o botão Aleatório na página inicial @@ -288,14 +358,18 @@ Mídia preferida Codificação das legendas Layout + Auto Layout de TV Layout de celular Layout de emulador + Cor principal Tema do App Local do título do poster Coloca o título debaixo do poster + + senha123 MeuNomeLegal @@ -304,6 +378,7 @@ MeuSiteLegal examplo.com Codigo da Língua (bp) + Nenhum Normal @@ -344,6 +420,7 @@ Use isto se as legendas forem mostradas %dms adiantadas Use isto se as legendas forem mostradas %dms atrasadas Sem atraso de legenda + Já fiz vinho com toque de kiwi para belga sexy. + Recomendada %s carregada Carregar de arquivo @@ -359,9 +437,12 @@ Protagonista Coadjuvante Figurante + Fonte Aleatório + Em breve… + Cam Cam Cam @@ -369,7 +450,7 @@ HD TS TC - Blu-ray + BlueRay WP DVD 4K @@ -378,6 +459,7 @@ HDR SDR Web + Imagem de Poster Player Resolução e título @@ -385,17 +467,19 @@ Resolução Id invalida Dado invalido - URL invalido +URL invalido Erro Remover legendas ocultas(CC) das legendas Remover bloat das legendas - Filtrar por linguagem preferida +Filtrar por linguagem preferida Extras Trailer - Próximo + +Próximo Ver vídeos nestas linguagens Anterior Saltar setup + Change the look of the app to suit your device Crash reporting What do you want to see @@ -407,6 +491,7 @@ Plugin Carregado Plugin Apagado Falha ao carregar %s + Iniciada a transferência %d %s Transferido %d %s com sucesso Tudo %s já transferido @@ -423,10 +508,13 @@ Ver repositórios da comunidade Lista pública Todas as legendas em maiúsculas - Transferir todos os plugins deste repositório\? + + Transferir todos os plugins deste repositório? %s (Desativado) - Reproduzir automaticamente próximo episódio + +Reproduzir automaticamente próximo episódio Começa o próximo episódio quando o atual termina - Ativar NSFW em fornecedores compatíveis - Fornecedores + +Ativar NSFW em fornecedores compatíveis +Fornecedores diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 1501a5d9..9e00f17b 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1,33 +1,37 @@ - + %s Ep %d Hrají: %s + - Plakát - Plakát + Poster + @string/result_poster_img_des Episode Poster Main Poster Next Random Go back Change Provider Preview Background + Rychlost (%.2fx) Hodnocení: %.1f - Nalezena nová aktualizace! -\n%s -> %s + Nalezena nová aktualizace!\n%s -> %s Výplň %d min + CloudStream Domů Hledat Stahování Nastavení + Hledat… Hledat %s… + Žádná data Další možnosti Další epizoda @@ -36,6 +40,7 @@ Otevřít v prohlížeči Přeskočit načítání Načítání… + Sledování Pozastaveno Dokončeno @@ -43,6 +48,7 @@ Plánuji sledovat Žádné Opětovné sledování + Přehrát film Streamovat torrent Zdroje @@ -51,6 +57,7 @@ Návrat zpět Přehrát epizodu + Stáhnout Stáhnuto Stahování @@ -59,14 +66,18 @@ Stahování selhalo Stahování zrušeno Stahování dokončeno + Chyba při načítání odkazů Interní úložiště + Dab Tit + Smazat soubor Přehrát soubor Pokračovat ve stahování Pozastavit stahování + Zakázat automatické nahlašování chyb Více informací Skrýt @@ -82,7 +93,9 @@ Zavřít Vymazat Uložit + Rychlost přehrávání + Nastavení titulků Barva texta Barva ohraničení @@ -92,26 +105,35 @@ Zvýšení titulků Písmo Velikost písma + Hledat pomocí poskytovatelů Hledat pomocí typů + %d benénů darováno vývojářům Nedarovali jste žádné benény + Automaticky vybrat jazyk Stáhnout jazyky Jazyk titulků Podržte pro obnovení na výchozí Importovat písma umístěním jich do %s Pokračovat ve sledování + Odebrat Další informace - @string/home_play + @string/home_play + Aby tento poskytovatel fungoval správně, budete možná potřebovat VPN Tento poskytovatel je torrent, je doporučená VPN + Web neposkytnul žádná metadata, načítání videa selže, pokud na webu neexistuje. + Popis Příběh nenalezen Popis nenalezen - Zobrazit Logcat 🐈 + + Zobrazit logcat 🐈 + Obraz v obraze Přehrávání bude pokračovat v miniaturním přehrávači na vrchu ostatních aplikací Tlačítko pro změnu velikosti přehrávače @@ -120,31 +142,40 @@ Nastavení titulků přehrávače Titulky Chromecastu Natavení titulků Chromecastu + Rychlostní režim Přidá do přehrávače možnost rychlosti Přejet pro posun - Přejeďte prstem ze strany na stranu pro ovládání své pozice ve videu + Přejeďte prstem vlevo nebo vpravo pro ovládání času v přehrávači Přejet pro změnu nastavení - Přejeďte prstem nahoru nebo dolů na levé nebo pravé straně pro změnu jasu nebo hlasitosti + Přejeďte prstem na levé nebo pravé straně pro změnu jasu nebo hlasitosti Dvojité klepnutí pro posun Dvojité klepnutí pro pozastavení - Množství času k posunu (sekundy) - Klepněte dvakrát vpravo nebo vlevo pro posun vpřed nebo vzad - Klepněte dvakrát doprostřed pro pozastavení + Množství času k posunu + Klepněte dvakrát vpravo nebo vlevo pro posun vpřed nebo vzad + + Klepněte doprostřed pro pozastavení Použít systémový jas - V přehrávači použít systémov překrytí + V přehrávači použít systémov + překrytí + + Aktualizovat postup sledování Automaticky synchronizovat postup sledování současné epizody + Obnovit data ze zálohy + Zálohovat data Načten soubor zálohy Nepodařilo se obnovit data ze soubory %s - Data uložena - Chybí oprávnění k úložišti. Zkuste to prosím znovu. + Data úspěšně uložena + Chybí oprávnění k úložišti, zkuste to prosím znovu Chyba při zálohování %s + Search Účty Aktualizace a záloha + Informace Pokročilé hledání Zobrazí vám výsledky hledání oddělené poskytovatelem @@ -152,7 +183,7 @@ Nebude odesílat žádná data Zobrazit výplňové epizody u anime Zobrazit aktualizace aplikace - Při spuštění aplikace automaticky zkontrolovat nové aktualizace. + Při spuštění automaticky zkontrolovat nové aktualizace Aktualizovat na předběžná vydání Kontrolovat aktualizace předběžných vydání, místo normálních plných vydání GitHub @@ -161,13 +192,18 @@ Připojte se na Discord Darujte benén vývojářům Benén darován + Jazyk aplikace + Tento poskytovatel nemá podporu pro Chromecast Nenalezeny žádné odkazy Odkaz zkopírován do schránky Přehrát epizodu Obnovit na výchozí hodnoty - Omlouváme se, aplikace spadla. Vývojářům bude odesláno anonymní hlášení o pádu + Omlouváme se, aplikace spadla. Vývojářům bude odesláno anonymní hlášení + o pádu + + Sezóna Žádná sezóna Epizoda @@ -175,16 +211,17 @@ S E Nenalezeny žádné epizody + Smazat soubor Smazat Pozastavit Pokračovat -30 +30 - Toto nevratně smaže %s -\nJste si jisti\? - %dm -\nzbývá + Toto nevratně smaže %s\nJste si jisti? + %dm\nzbývá + + Probíhající Dokončena Stav @@ -193,12 +230,15 @@ Trvání Místo Synopse + ve frontě Žádné titulky Výchozí + Volné Použito Aplikace + Filmy Seriály @@ -208,20 +248,23 @@ Dokumenty OVA Asijská dramata + Film Seriál Animovaný - Anime - OVA + @string/anime + @string/ova Torrent Dokument Asijské drama + Chyba zdroje Chyba vzdáleného zdroje Chyba vykreslování Neočekávaná chyba přehrávače Chyba stahování, zkontrolujte oprávnění úložiště + Chromecastovat epizodu Chromecast jako zrcadlo Přehrát v aplikace @@ -232,60 +275,92 @@ Zrcadlo stahování Obnovit odkazy Stáhnout titulky + Štítek s kvalitou Štítek dabingu Štítek titulků Název Přepnout prvky UI na plakátu + Nenalezeny žádné aktualizace Zkontrolovat aktualizace + Uzamknout Změnit velikost Zdroj Přeskočit OP + Již nezobrazovat Přeskočit tuto aktualizace Aktualizovat - Upřednostněná kvalita sledování (WiFi) + Upřednostněná kvalita sledování Maximální počet znaků v názvu přehrávače Rozlišení přehrávače + Velikost vyrovnávací paměti videa Délka vyrovnávací paměti videa Mezipaměť videa na disku Vymazat mezipamět videí a obrázků - Při nastavení příliš vysoké hodnoty na zařízeních s malou pamětí, jako je například Android TV, může způsobit pády. - Při nastavení příliš vysoké hodnoty na zařízeních s malým dostupným úložištěm, jako je například Android TV, může způsobit pády. + + Při nastavení příliš vysoké hodnoty způsobí náhodné pády. Neměňte, pokud máte málo paměti RAM, například u televize s Androidem nebo starého telefonu. + Pokud ji nastavíte příliš vysoko, může způsobit problémy v systémech s malým úložným prostorem, například v zařízeních Android TV. + DNS přes HTTPS Užitečné pro obcházení blokací ISP + Cesta stahování - URL serveru NGINX + + URL serveru Nginx + Zobrazit dabované anime/anime s titulky + Vyplnit na obrazovku Roztáhnout Přiblížit + Odmítnutí odpovědnosti - Jakékoli právní otázky týkající se obsahu této aplikace je třeba řešit se samotnými hostiteli a poskytovateli souborů, protože s nimi nejsme nijak spojeni. V případě porušení autorských práv se obraťte přímo na odpovědné strany nebo na webové stránky, na kterých se streamování odehrává. Aplikace je určena výhradně pro vzdělávací a osobní účely. CloudStream 3 v aplikaci nehostuje žádný obsah a nemá žádnou kontrolu nad tím, jaká média jsou v aplikaci umístěna nebo odstraněna. CloudStream 3 funguje jako jakýkoli jiný vyhledávač, například Google. Služba CloudStream 3 nehostuje, nenahrává ani nespravuje žádná videa, filmy ani obsah. Pouze vyhledává, agreguje a zobrazuje odkazy v pohodlném, uživatelsky přívětivém rozhraní. Pouze shromažďuje webové stránky třetích stran, které jsou veřejně přístupné prostřednictvím jakéhokoli běžného webového prohlížeče. Je odpovědností uživatele, aby se vyvaroval jakýchkoli akcí, které by mohly porušovat zákony platné v jeho lokalitě. Použijte CloudStream 3 na vlastní nebezpečí. + Jakékoli právní otázky týkající se obsahu této aplikace + je třeba řešit se samotnými hostiteli a poskytovateli souborů, protože s nimi nejsme nijak spojeni. + + V případě porušení autorských práv se obraťte přímo na odpovědné strany nebo na webové stránky, na kterých se streamování odehrává. + + Aplikace je určena výhradně pro vzdělávací a osobní účely. + + CloudStream 3 v aplikaci nehostuje žádný obsah a nemá žádnou kontrolu nad tím, jaká média jsou v aplikaci umístěna nebo odstraněna. + CloudStream 3 funguje jako jakýkoli jiný vyhledávač, například Google. Služba CloudStream 3 nehostuje, nenahrává ani + nespravuje žádná videa, filmy ani obsah. Pouze vyhledává, agreguje a zobrazuje odkazy v pohodlném, + uživatelsky přívětivém rozhraní. + + Pouze shromažďuje webové stránky třetích stran, které jsou veřejně přístupné prostřednictvím jakéhokoli běžného webového prohlížeče. Je + odpovědností uživatele, aby se vyvaroval jakýchkoli akcí, které by mohly porušovat zákony platné v jeho lokalitě. Použijte + CloudStream 3 na vlastní nebezpečí. + Obecné Náhodné tlačítko - Zobrazit na domovské stránce tlačítko, kterým lze vybrat náhodný film nebo seriál z domovské stránky + Zobrazit na domovské stránce náhodné tlačítko Jazyk poskytovatelů Rozložení aplikace Preferovaná média Kódování titulků Rozložení + Automaticky TV rozložení Mobilní rozložení Rozložení emulátoru + Primární barva Motiv aplikace Umístění názvu plakátu Umístit název pod plakát + + heslo123 MojeSuperJmeno ahoj@svete.cz 127.0.0.1 + Žádné Normální @@ -321,11 +397,12 @@ Stín Vyvýšené Synch. titulky - 1000 ms + 1000ms Zpoždění titulků - Toto použijte, pokud jsou titulky zobrazeny o %d ms dříve - Toto použijte, pokud jsou titulky zobrazeny o %d ms později + Toto použijte, pokud jsou titulky zobrazeny o %dms dříve + Toto použijte, pokud jsou titulky zobrazeny o %dms později Žádné zpoždění titulků + Příliš žluťoučký kůň úpěl ďábelské ódy + Doporučeno Načteno %s Načíst ze souboru @@ -341,9 +419,12 @@ Hlavní Podpora Pozadí + Zdroj Náhodný + Již brzy… + Kam Kam Kam @@ -351,7 +432,7 @@ HD TS TC - Blu-ray + BlueRay WP DVD 4K @@ -360,6 +441,7 @@ HDR SDR Web + Obrázek plakátu Přehrávač Rozlišení a název @@ -369,189 +451,4 @@ Odstranit skryté titulky z titulků Odstranit nadbytečné titulky Extra - Označit jako zhlédnuté - Historie - %dm - Přehrát s CloudStream - Přehrát další epizodu po skončení aktuální - Zobrazit trailery - Skrýt vybranou kvalitu videa ve výsledcích vyhledávání - Automatické aktualizace doplňků - Odebrat web - Neplatné údaje - Chyba - Název repozitáře - Doplněk načten - Stáhněte si seznam webů, které chcete používat - Staženo: %d - Zvukové stopy - Videostopy - Použít při restartu - Bezpečný režim povolen - Velikost - Autoři - Adresa URL repozitáře - Neplatná adresa URL - Automaticky instalovat všechny dosud nenainstalované doplňky z přidaných repozitářů. - Ostatní - Trailer - Odkaz na stream - Přeskočit nastavení - Přidat repozitář - Doplněk odstraněn - Nepodařilo se načíst %s - Veřejný seznam - Velká písmena u všech titulků - Playlist HLS - MPV - Webové vysílání videa - Aplikace nenalezena - Přeskočit %s - Úvod - Konec - Příliš mnoho textu. Nepodařilo se uložit do schránky. - Ano - Prohlížeč - %d-%d - Knihovna - Zobrazit plakáty z Kitsu - Automaticky stahovat doplňky - Znovu provést proces nastavení - Instalátor APK - %d %s - Některé telefony nepodporují nový instalátor balíčků. Pokud se aktualizace nenainstalují, zkuste použít starší možnost. - Mezipaměť - Epizoda %d bude vydána za - %dh %dm - Přehrát přímý přenos - Rozšíření - Akce - Vzhled - Odkazy - Funkce - MůjSuperWeb - Povolit NSFW u podporovaných poskytovatelů - Poskytovatelé - Hlášení pádů - Předchozí - Změnit vzhled aplikace tak, aby vám vyhovoval - Co chcete vidět - Doplněk stažen - 18+ - Spuštěno stahování %d %s… - CloudStream nemá ve výchozím nastavení nainstalované žádné weby. Stránky je třeba nainstalovat z úložišť. -\n -\nKvůli nesmyslnému podání stížnosti DMCA společností Sky UK Limited 🤮 nemůžeme v aplikaci propojit stránky repozitářů. -\n -\nPřipojte se k našemu Discordu nebo hledejte na internetu. - Zakázáno: %d - Aktualizováno %d doplňků - Zobrazit informace o pádu - Hodnocení: %s - Odebrat ze zhlédnutých - Instalace aktualizace aplikace… - Všechna rozšíření byla vypnuta z důvodu pádu, abyste mohli najít to, které způsobuje potíže. - Verze - Preferovaný přehrávač videí - Popis - Stav - Nejprve nainstalujte rozšíření - VLC - Smíšený konec - Jazyk - Interní přehrávač - Rekapitulace - Vymazat historii - Webový prohlížeč - Všechny jazyky - Smíšený úvod - Poděkování - Znělka - Zobrazit vyskakovací okna pro přeskočení úvodu/konce - Stahování aktualizace aplikace… - Opravdu chcete opustit aplikaci\? - Nepodařilo se nainstalovat novou verzi aplikace - Původní - Aplikace bude po ukončení aktualizována - Vypadá to, že vaše knihovna je prázdná :( -\nPřihlaste se k účtu v knihovně nebo přidejte pořady do místní knihovny - Vybrat knihovnu - Hodnocení (od nejvyššího) - Hodnocení (od nejnižšího) - Abecedně (od Z do A) - Seřadit podle - Řazení - Vypadá to, že tento seznam je prázdný, zkuste přepnout na jiný - Nalezen soubor bezpečného režimu! -\nDo odebrání souboru nebudeme načítat žádná rozšíření. - Aktualizováno (od nejnovějšího) - Aktualizováno (od nejstaršího) - Abecedně (od A do Z) - Otevřít pomocí - Záloha - Gesta - Klonovat web - Přidat klon existujícího webu s jinou adresou URL - example.com - Kód jazyka (cs) - Stáhnout všechny doplňky z tohoto repozitáře\? - %s (zakázáno) - Stopy - NSFW - Video - Funkce přehrávače - Titulky - Rozložení - Výchozí hodnoty - Instalátor balíčků - Aktualizace aplikace - Hotovo - Podporováno - %s %d%s - Živý přenos - NSFW - Rozšíření - Přehrát trailer - %dd %dh %dm - Zobrazit komunitní repozitáře - Aktualizace zahájena - Stream - Automaticky přehrát další epizodu - Živé přenosy - Filtrování podle preferovaného jazyka médií - Referent - Další - Sledovat videa v těchto jazycích - Staženo %d %s - Všechny %s jsou již staženy - Hromadné stahování - doplněk - doplňků - Tímto také odstraníte všechny doplňky repozitářů - Odstranit repozitář - Nestaženo: %d - Ne - Skrytý přehrávač - doba hledání - Množství vyhledávané doby při skrytém přehrávači - Zobrazený přehrávač - doba hledání - Android TV - Množství vyhledávané doby při zobrazeném přehrávači - Protokol - Test poskytovatele - Neúspěšné - Úspěšné - Restart - Spustit - Zastavit - Aktualizace odebíraných pořadů - Přihlášeno k odběru %s - Odhlášen odběr od %s - Byla vydána epizoda %d! - Odebíráno - Proxy raw.githubusercontent.com - Nepodařilo se připojit ke GitHubu, povolování proxy jsdelivr. - Upřednostněná kvalita sledování (mobilní data) - Vrátit zpět - Pomocí jsdelivr lze obejít blokování GitHubu. Může dojít ke zpoždění aktualizací o několik dní. - Obcházení ISP diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings-de.xml similarity index 63% rename from app/src/main/res/values-de/strings.xml rename to app/src/main/res/values-de/strings-de.xml index 8fbcc2d0..e1d657c7 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings-de.xml @@ -1,59 +1,48 @@ - - %s Ep %d - Besetzung: %s - Episode %d wird veröffentlicht in + Vorschaubild - Vorschaubild - Halten, um auf die Standardeinstellungen zurückzusetzen - Wiederherstellung der Daten aus der Datei %s fehlgeschlagen - Daten erfolgreich gesichert - Fehler beim Sichern von %s - Dieser Anbieter hat keine Chromecast-Unterstützung - Chromecast-Mirror - In App wiedergeben - Vermischte Openings - Abspann - Intro - Verlauf löschen - Verlauf - Überspringen Knopf für Openings/Endings anzeigen - Zu viel Text. Kann nicht in der Zwischenablage gespeichert werden. + @string/result_poster_img_des Episodenvorschaubild Medienvorschaubild Nächstes zufällig Zurück Anbieter wechseln Hintergrundbildvorschau + + Geschwindigkeit (%.2fx) Bewertung: %.1f - Neues Update gefunden! -\n%s -> %s + Neues Update gefunden!\n%s -> %s Füller %d Min + CloudStream Mit CloudStream abspielen Startseite - Suchen + Suche Downloads Einstellungen - Suchen… + + Suche… Suche %s… + Keine Daten vorhanden Mehr Optionen - Nächste Episode + Nächste Epsisode Genres Teilen In Browser öffnen - Puffern überspringen + Buffern überspringen Lädt… + Am schauen Pausiert Abgeschlossen Abgebrochen - Geplant - Nichts - Erneut schauen + Möchte schauen + None + Erneut anschauen + Film abspielen Livestream abspielen Torrent streamen @@ -62,23 +51,29 @@ Erneuter Verbindungsaufbau… Zurück Episode abspielen + + Herunterladen Heruntergeladen Lädt herunter Download pausiert - Download gestartet + Download startet Download fehlgeschlagen Download abgebrochen Download abgeschlossen Stream + Fehler beim Laden von Links Interner Speicher + Dub Sub + Datei löschen Datei abspielen Download fortsetzen Download pausieren + Automatische Fehlerberichterstattung deaktivieren Mehr Infos Verstecken @@ -94,79 +89,106 @@ Schließen Löschen Speichern - Player-Geschwindigkeit + + Players Geschwindigkeit + Untertiteleinstellungen Textfarbe Konturfarbe Hintergrundfarbe Fensterfarbe - Kantentyp + Kanten-Typ Untertitelhöhe Schriftart Schriftgröße + Suche anhand Anbietern Suche anhand Typen + %d Benenes an die Devs verteilt Noch keine Benenes verteilt + Sprache automatisch wählen Sprachen herunterladen Untertitelsprache + Halten, um auf die Standardeinstellungen zurückzusetzen Fonts in %s importieren Weiterschauen + Entfernen Mehr Infos - @string/home_play + @string/home_play + Damit dieser Anbieter korrekt funktioniert, ist möglicherweise ein VPN erforderlich Dieser Anbieter bietet Torrents an, ein VPN wird dringend empfohlen + Metadaten werden nicht von der Website bereitgestellt, das Laden des Videos schlägt fehl, wenn sie auf der Website nicht vorhanden sind. + Beschreibung Keine Handlung gefunden Keine Beschreibung gefunden + Logcat anzeigen 🐈 + Bild-in-Bild Setzt die Wiedergabe in einem kleinen Fenster über anderen Anwendungen fort - Player-Skalierung anpassen + Player Skalierung anpassen Entfernt schwarze Ränder Untertitel - Player-Untertiteleinstellungen - Chromecast-Untertitel - Chromecast-Untertiteleinstellungen + Player Untertiteleinstellungen + Chromecast Untertitel + Chromecast Untertiteleinstellungen + Eigengravy-Modus Fügt eine Geschwindigkeitsoption im Player hinzu Wischen zum vor- und zurückspulen Nach links oder rechts wischen, um die Zeit im Videoplayer zu steuern Wischen, um Einstellungen zu ändern - Links oder rechts nach oben oder unten wischen, um die Helligkeit oder Lautstärke zu ändern + Nach links oder rechts wischen, um die Helligkeit oder Lautstärke zu ändern + Nächste Episode automatisch abspielen Nächste Episode wird gestartet, sobald die aktuelle Episode endet + Doppeltippen zum vor- und zurückspulen Doppeltippen zum Pausieren - Zeit für vor- und zurückspulen im Player (Sekunden) - Zweimal auf die rechte oder linke Seite tippen, um vor- oder zurückzuspulen - Doppelt in die Mitte tippen, um zu pausieren + Zeit für vor- und zurückspulen im Player + Zweimal auf die rechte oder linke Seite tippen, um vor- oder zurückzuspulen + + In die Mitte tippen, um zu pausieren Systemhelligkeit verwenden - Systemhelligkeit anstelle eines dunklen Overlay im Player verwenden + Systemhelligkeit anstelle eines dunklen + Overlay im Player verwenden + + Episodenfortschritt aktualisieren - Automatisches synchronisieren des aktuellen Episodenfortschritts + Automatisches Synchronisieren des aktuellen Episodenfortschritts + Daten aus Sicherung wiederherstellen + Daten sichern Sicherungsdatei geladen - Speicherberechtigungen fehlen. Bitte erneut versuchen. + Wiederherstellung der Daten aus der Datei %s fehlgeschlagen + Daten erfolgreich gesichert + Speicherberechtigungen fehlen, bitte erneut versuchen + Fehler beim Sichern von %s + Suche Konten Updates und Datensicherung + Info Erweiterte Suche Liefert die Suchergebnisse getrennt nach Anbietern Sendet Daten nur bei Abstürzen Sendet keine Daten - Füller-Episoden für Animes anzeigen - Trailer anzeigen + Füller-Episoden für Animes zeigen + Trailer zeigen Vorschaubilder von Kitsu anzeigen Ausgewählte Videoqualität bei Suchergebnissen ausblenden + Automatische Plugin-Updates App-Updates anzeigen - Automatisches Suchen nach neuen Updates nach dem Start + Automatisches Suchen nach neuen Updates beim Start Auf Vorabversionen updaten Suche nach Vorabversionen statt nur nach Vollversionen Github @@ -175,12 +197,18 @@ Discord beitreten Eine Benene an die Devs verteilen Verteilte Benenes - App-Sprache + + App Sprache + + Dieser Anbieter hat keine Chromecast-Unterstützung Keine Verlinkung gefunden Link in die Zwischenablage kopiert Episode abspielen Auf Standardwert zurücksetzen - Sorry, die Anwendung ist abgestürzt. Ein anonymer Fehlerbericht wird an die Entwickler gesendet + Sorry, die Anwendung ist abgestürzt. Ein anonymer Fehlerbericht wird an die + Entwickler gesendet + + Staffel Keine Staffel Episode @@ -188,18 +216,19 @@ %d-%d %d %s S - Datei löschen E Keine Episoden gefunden + + Datei löschen Löschen Pause Fortsetzen -30 +30 - Dadurch wird %s permanent gelöscht -\nBist du dir sicher\? - %dm -\nverbleibend + Dadurch wird %s permanent gelöscht \nBist du dir sicher? + %dm\nverbleibend + + Laufend Abgeschlossen Status @@ -208,14 +237,18 @@ Länge Website Zusammenfassung + In Warteschlange eingereiht Keine Untertitel Standard + Frei - Belegt + Benutzt App + + Filme - TV-Serien + TV Serien Trickfilme Anime Torrents @@ -225,83 +258,105 @@ Livestreams NSFW Andere + + Film Serie Trickfilm - Anime - OVA + @string/anime + @string/ova Torrent Dokumentation Asiatisches Drama Livestream NSFW Video + Quellenfehler - Remote-Fehler + Remotefehler Renderfehler - Unerwarteter Playerfehler - Downloadfehler, Speicherberechtigungen prüfen + Unerwarteter Player-Fehler + Download-Fehler, Speicherberechtigungen prüfen + Chromecast-Episode + Chromecastmirror + In App wiedergeben In %s wiedergeben In Browser wiedergeben Link kopieren - Auto-Download - Alternativer Download + Auto Download + Downloadmirror Links neu laden Untertitel herunterladen + Qualitätsanzeige - Dub-Label - Sub-Label + Dub Label + Sub Label Titel UI-Elemente auf Vorschaubild umschalten + Kein Update gefunden Auf Update prüfen + Sperren Skalieren Quelle Intro überspringen + Nicht mehr anzeigen Update ignorieren Update - Bevorzugte Videoqualität (WLAN) - Videoplayertitel max. Zeichen - Videoplayer Auflösung + Bevorzugte Auflösung + Videoplayer Titel max. Zeichen + Videoplayers Auflösung + Videopuffergröße Videopufferlänge Video-Cache in Speicher Video- und Bild-Cache leeren - Verursacht Abstürze, wenn zu hoch eingestellt. Nicht ändern, wenn wenig Arbeitsspeicher verfügbar ist, wie z.B. ein Android TV oder ein altes Telefon. - Kann auf Systemen mit geringem Speicherplatz, wie z. B. Android TV-Geräten, zu Problemen führen, wenn der Wert zu hoch eingestellt ist. + + Verursacht Abstürze, wenn zu hoch eingestellt. Nicht ändern, wenn wenig Arbeitsspeicher verfügbar ist, wie z.B. ein Android TV oder ein altes Telefon + Kann auf Systemen mit geringem Speicherplatz, wie z. B. Android TV-Geräten, zu Problemen führen, wenn der Wert zu hoch eingestellt ist + DNS über HTTPS Nützlich für die Umgehung von ISP-Sperren + Website klonen Website entfernen Einen Klon einer bestehenden Website mit einer anderen URL hinzufügen + Downloadpfad + Nginx-Server-URL + Dubbed/Subbed Anime anzeigen (Synchronisiert/Untertitelt) + An Bildschirm anpassen Strecken Vergrößern + Haftungsausschluss - Allgemein + General Zufalls-Button - Zeigt einen Zufallsbutton auf der Startseite an, mit welchem eine Serie oder ein Film von der Website zufällig ausgewählt wird - Anbieter-Sprachen - App-Layout + Zeige Zufallsgenerator Schaltfläche auf der Startseite + Anbieter Sprachen + App Layout Bevorzugte Medien - NSFW bei unterstützten Anbietern aktivieren Untertitel-Kodierung - Anbieter Layout + Auto - TV-Layout - Telefon-Layout - Emulator-Layout + TV Layout + Handy Layout + Emulator Layout + Grundfarbe App-Thema - Vorschaubildtitel Platzierung + Vorschaubild Titel Platzierung Titel unter Vorschaubild platzieren + + + passwort123 MeinCoolerUsername hello@world.com @@ -321,10 +376,12 @@ Sync Bewertung %d / 10 - /\?\? + /?? /%d Authentifiziert %s Die Authentifizierung bei %s ist fehlgeschlagen + + Keine Normal Alle @@ -340,18 +397,29 @@ Verwenden, wenn die Untertitel %dms zu früh angezeigt werden Verwenden, wenn die Untertitel %dms zu spät angezeigt werden Keine Untertitelverzögerung + + Vogel Quax zwickt Johnys Pferd Bim + Empfohlen %s geladen Aus Datei laden - Aus Internet laden + Vom Internet laden Heruntergeladene Datei Hauptdarsteller Nebendarsteller Hintergrunddarsteller - Quelle + + Anbieter Zufällig - Demnächst… + + Demnächst... + Cam Cam Cam @@ -368,6 +436,7 @@ HDR SDR Web + Vorschaubild Player Auflösung und Titel @@ -378,19 +447,19 @@ Ungültige URL Fehler Softsubs aus Untertiteln entfernen - Entfernt Schrott aus Untertiteln (z.B. Werbung) + Entferne nutzloses aus Untertiteln (z.B. Werbung) Nach bevorzugter Mediensprache filtern Extras Trailer Link zum Stream Referent - Weiter + Nächste Videos in diesen Sprachen ansehen Vorherige Einrichtung überspringen Aussehen der App passend zu dem des Geräts ändern Absturzmeldung - Was möchtest du anschauen\? + Was möchtest du anschauen? Fertig Erweiterungen Repository hinzufügen @@ -400,133 +469,32 @@ Plugin gelöscht Fehler beim Laden von %s 18+ - Herunterladen begonnen %d %s… - %d %s heruntergeladen + Herunterladen begonnen %d %s + Downloaded %d %s erfolgreich Alle %s bereits heruntergeladen - Batch-Download + Batch Download Plugin Plugins Dadurch werden auch alle Repository-Plugins gelöscht Repository löschen - Lade eine Liste der Websiten herunter, welche du verwenden möchtest - Heruntergeladen: %d - Deaktiviert: %d + Liste der Websites herunterladen, welche du verwenden möchtest + Downloaded: %d + Heruntergeladen: %d Nicht heruntergeladen: %d - CloudStream hat standardmäßig keine Websites installiert. Websites müssen aus Repositories installiert werden. -\n -\nAufgrund eines hirnlosen DMCA-Takedowns durch Sky UK Limited 🤮 können wir die Repository-Site nicht in der App verlinken. -\n -\nTrete unserem Discord bei oder suche online. + Repository hinzufügen um Anbieter-Erweiterungen herunterzuladen Community-Repositories anzeigen Öffentliche Liste Alle Untertitel in Großbuchstaben - Alle Plugins aus diesem Repository herunterladen\? + + Alle Plugins aus diesem Repository herunterladen? %s (Deaktiviert) - Spuren + Abgesicherter Modus aktiviert + Ein nicht behebbarer Absturz ist aufgetreten, und wir haben automatisch alle Erweiterungen deaktiviert, damit du die Erweiterung finden und entfernen kannst, die das Problem verursacht. + Absturzinfo ansehen + Bei Neustart anwenden Audiospuren Videospuren - Bei Neustart anwenden - Abgesicherter Modus aktiviert - Alle Erweiterungen wurden aufgrund eines Absturzes deaktiviert, damit Sie diejenige finden können, die Probleme verursacht. - Absturzinfo ansehen - Bewertung: %s - Beschreibung - Version - Status - Größe - Autoren - Unterstützt - Sprache - Zuerst muss die Erweiterung installiert werden - HLS-Playlist - Bevorzugter Videoplayer - Interner Player - VLC - MPV - Web Video Cast - Browser - App nicht gefunden - Alle Sprachen - Überspringen %s - Opening - Ending - Zusammenfassung - Vermischte Endings - %dd %dh %dm - %dh %dm - %dm - Trailer abspielen - Automatisches Herunterladen von Plugins - Automatische Installation aller noch nicht installierten Plugins aus hinzugefügten Repositories. - Einrichtungsvorgang wiederholen - APK-Installer - Einige Telefone unterstützen den neuen Package-Installer nicht. Benutze die Legacy-Option, wenn sich die Updates nicht installieren lassen. - %s %d%s - Links - App-Updates - Sicherung - Erweiterungen - Wartung - Cache - Gesten - Player-Funktionen - Untertitel - Layout - Voreinstellungen - Aussehen - Funktionen - %d Plugins aktualisiert - Als gesehen markieren - Sind Sie sicher, dass Sie die App beenden möchten\? - Ja - Nein - App-Update wird heruntergeladen… - App-Update wird installiert… - Konnte die neue Version der App nicht installieren - Legacy - PackageInstaller - Aktualisierung gestartet - Die Anwendung wird beim Beenden aktualisiert - Das Plugin wurde heruntergeladen - Von geschaut entfernen - Bibliothek - Browser - Sortieren nach - Sortieren - Bewertung (gut bis schlecht) - Bewertung (schlecht bis gut) - Aktualisiert (neu bis alt) - Aktualisiert (alt bis neu) - Alphabetisch (A bis Z) - Alphabetisch (Z bis A) - Bibliothek auswählen - Öffnen mit - Sieht aus, als wäre deine Bibliothek leer :( -\nMelde dich mit einem Bibliothekskonto an oder füge Titel zu deiner lokalen Bibliothek hinzu - Diese Liste scheint leer zu sein. Versuche, zu einer anderen Liste zu wechseln. - Datei für abgesicherten Modus gefunden! -\nBeim Start werden keine Erweiterungen geladen, bis die Datei entfernt wird. - Player ausgeblendet - Betrag zum vor- und zurückspulen - Der Betrag, welcher verwendet wird, wenn der Player eingeblendet ist - Der Betrag, welcher verwendet wird, wenn der Player ausgeblendet ist - Android-TV - Player eingeblendet - Betrag zum vor- und zurückspulen - Fehlgeschlagen - Erfolgreich - Anbieter-Test - Stopp - Log - Start - Neustarten - Bevorzugte Videoqualität (mobile Daten) - Umgehung der GitHub Sperre mit jsdelivr. Kann zu einigen Tagen Verzögerung bei Updates führen. - %s abonniert - %s deabonniert - Episode %d erschienen! - raw.githubusercontent.com Proxy - GitHub kann nicht erreicht werden, der jsdelivr-Proxy wird aktiviert. - Abonnierte Serien werden aktualisiert - Rückgängig - Abonniert - ISP-Umgehungen + Spuren + Aktivieren der NSFW auf unterstützten Anbietern + Anbieter diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index f07ce43c..11b74938 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -1,4 +1,3 @@ - CloudStream @@ -13,43 +12,48 @@ Περισσότερες Επιλογές Πίσω Επόμενο Επεισόδιο - Αφίσα + Πόστερ Κατηγορίες Κοινοποίηση Άνοιγμα στον περιηγητή Παράλειψη φόρτωσης Φόρτωση… + Παρακολούθηση Σε αναμονή Ολοκληρώθηκε Διακόπηκε Για παρακολούθηση Τίποτα - Αναπαραγωγή ταινίας + + Αναπαραγωγή Ταινίας Μετάδοση Torrent Πηγές Υπότιτλοι Προσπάθεια επανασύνδεσης… Πίσω - Αφίσα επεισοδίου - Αναπαραγωγή επεισοδίου + Πόστερ + Αναπαραγωγή Επεισοδίου Λήψη Σφάλμα φόρτωσης συνδέσμων Εσωτερικός χώρος αποθήκευσης + Dub Sub + Διαγραφή Αρχείου - Αναπαραγωγή αρχείου - Συνέχιση λήψης - Παύση λήψης - Λυπόμαστε, η εφαρμογή κατέρρευσε. Μια ανώνυμη αναφορά σφαλμάτων θα σταλεί στους προγραμματιστές + Αναπαραγωγή Αρχείου + Συνέχιση Λήψης + Παύση Λήψης + + Λυπούμαστε, η εφαρμογή κατέρρευσε. Μια ανώνυμη αναφορά σφαλμάτων θα σταλεί στους προγραμματιστές Απενεργοποιήστε την αυτόματη αναφορά σφαλμάτων - Εμφάνιση Logcat 🐈 - Περαιτέρω πληροφορίες + Εμφάνιση logcat 🐈 + Παραπάνω πληροφορίες Απόκρυψη - Κύρια αφίσα + Κύριο Πόστερ Αναπαραγωγή Πληροφορίες Next Random @@ -57,7 +61,7 @@ Φιλτράρισμα Σελιδοδεικτών Σελιδοδείκτες Αφαίρεση - Αναπαραγωγή επεισοδίου + Αναπαραγωγή Επεισοδίου Υποβολή Ακύρωση Ταχύτητα αναπαραγωγής @@ -75,126 +79,146 @@ Αναζήτηση βάσει τύπων %d μπανάνες δόθηκαν στους προγραμματιστές Καμία μπανάνα δεν δόθηκε + Αυτόματη επιλογή γλώσσας Λήψη γλωσσών Κρατήστε πατημένο για επαναφορά στις προεπιλεγμένες τιμές - Συνέχεια παρακολούθησης + Συνέχεια Παρακολούθησης + Αφαίρεση - Περαιτέρω πληροφορίες + Επιπλέον Πληροφορίες + Η χρήση ενός VPN ίσως χρειαστεί για την ομαλή λειτουργία του τρέχοντος παρόχου - Ο πάροχος αυτός πρόκειται για torrent, η χρήση ενός VPN συνιστάται + Πρόκειται για torrent, η χρήση ενός VPN συνιστάται Περιγραφή Δεν βρέθηκε περιγραφή Δεν βρέθηκε περιγραφή - Εικόνα-εντός-Εικόνας + + Εικόνα-σε-Εικόνα Συνεχίζει την αναπαραγωγή σε ένα μίνι παράθυρο πάνω από άλλες εφαρμογές Αλλαγή μεγέθους παραθύρου Αφαίρεση μαύρων περιγραμμάτων Υπότιτλοι Ρυθμίσεις υποτίτλων του προγράμματος αναπαραγωγής - Υπότιτλοι για Chromecast - Ρυθμίσεις υποτίτλων για Chromecast - Eigengravy Mode + Chromecast Υπότιτλοι + Ρυθμίσεις Chromecast υποτίτλων + Eigengrau Mode Προσθέτει την επιλογή ταχύτητας στο πρόγραμμα αναπαραγωγής Σύρετε για αναζήτηση - Σύρετε αριστερά ή δεξιά για να ελέγξετε τον χρόνο στην μπάρα του προγράμματος αναπαραγωγής + Σύρετε αριστερά ή δεξιά για να ελέγξετε τον χρόνο στην κάτω μπάρα Σύρετε για να αλλάξετε ρυθμίσεις Σύρετε αριστερά ή δεξιά για να αλλάξετε τη φωτεινότητα ή την ένταση Διπλό πάτημα για αναζήτηση - Διπλό πάτημα στα αριστερά ή δεξιά για αναζήτηση προς τα μπροστά ή πίσω + Διπλό πάτημα στα αριστερά ή δεξιά για αναζήτηση μπροστά ή πίσω Αναζήτηση Πληροφορίες Προχωρημένη Αναζήτηση Δίνει τα αποτελέσματα αναζήτησης ταξινομημένα ανά πάροχο - Αποστέλλει δεδομένα μόνο για καταρρεύσεις + Αποστέλλει δεδομένα μόνο για σφάλματα Δεν στέλνει δεδομένα Εμφάνιση ενημερώσεων Αυτόματη αναζήτηση νέων ενημερώσεων Ενημέρωση σε προ-εκδόσεις (beta) - Αναζητήστε ενημερώσεις προ-εκδόσεων (beta) αντί για σταθερές εκδόσεις (stable) - GitHub - Εφαρμογή βιβλίων από τους ίδιους προγραμματιστές - Εφαρμογή άνιμε από τους ίδιους προγραμματιστές - Βρείτε μας στο Discord - Δώστε μία μπανάνα στους προγραμματιστές - Μπανάνα δόθηκε + Αναζητήστε ενημερώσεις προ-εκδόσεων (beta) αντί για σταθερές εκδόσεις + Github + Ελαφριά novel εφαρμογή από τους ίδιους προγραμματιστές + Anime εφαρμογή από τους ίδιους προγραμματιστές + Εγγραφείτε στο Discord + Δώστε μπανάνα στους προγραμματιστές + Μπανάνα δόθηκε δώθηκε + Ταχύτητα (%.2fx) Βαθμολογία: %.1f - Νέα διαθέσιμη ενημέρωση! -\n%s -> %s + Νέα ενημέρωση διαθέσιμη!\n%s -> %s + Πάτημα στη μέση για παύση Χρήση φωτεινότητας συστήματος - Χρήση φωτεινότητας συστήματος στο ενσωματωμένο πρόγραμμα αναπαραγωγής, αντί εφαρμογής προεπιλεγμένου σκούρου επικαλύμματος + Χρήση φωτεινότητας συστήματος στο ενσωματωμένο πρόγραμμα αναπαραγωγής αντί να εφαρμοστεί το προεπιλεγμένο σκούρο επικάλυμμα + + Ενημέρωση προόδου παρακολούθησης Αυτόματος συγχρονισμός της προόδου του τρέχοντος επεισοδίου + Επαναφορά δεδομένων από αντίγραφο ασφαλείας + Αντίγραφα ασφαλείας Τα αντίγραφα ασφαλείας φορτώθηκαν - Η επαναφορά αντιγράφων ασφαλείας απέτυχε από το αρχείο %s - Τα δεδομένα αποθηκεύτηκαν - Δεν έχει δοθεί άδεια για πρόσβαση στον αποθηκευτικό χώρο. Παρακαλώ προσπαθήστε ξανά. + Η επαναφορά αντιγράφων ασφαλαείας απέτυχε από το αρχείο %s + Επιτυχής αποθήκευση δεδομένων + Δεν έχει δοθεί άδεια για πρόσβαση στον αποθηκευτικό χώρο, προσπαθήστε ξανά Σφάλμα δημιουργίας αντιγράφων ασφαλείας %s + Λογαριασμοί Ενημερώσεις και αντίγραφα ασφαλείας - Εμφάνιση filler επεισοδίου για άνιμε - Εμφάνιση trailer - Εμφάνιση αφισών από Kitsu + + Εμφάνιση filler επεισοδίου για anime + Εμφάνιση trailers + Εμφάνιση posters από kitsu Απόκρυψη επιλεγμένης ποιότητας βίντεο στα αποτελέσματα αναζήτησης - Γλώσσα εφαρμογής - Αυτός ο πάροχος δεν έχει υποστήριξη για Chromecast + + App Language + + Αυτός ο πάροχος δεν έχει υποστήριξη Chromecast Δεν βρέθηκαν διαθέσιμοι σύνδεσμοι Ο σύνδεσμος αντιγράφηκε στο πρόχειρο - Κύκλος + + Season %s %d%s - Κανένας κύκλος - Επεισόδιο - Επεισόδια + No Season + Episode + Episodes %d-%d %d %s - Σ + S E - Δεν βρέθηκαν επεισόδια + No Episodes found + Διαγραφή αρχείου Διαγραφή - Παύση + Πάυση Συνέχιση - Αυτό θα διαγράψει μόνιμα το %s -\nΕίστε σίγουροι πως θέλετε να προχωρήσετε; - %dm -\nαπομένουν + Αυτό θα διαγράψει μόνιμα το %s\nΕπιβεβαίωση; + %dm\nαπομένουν + Σε εξέλιξη Κατάσταση Έτος Διάρκεια Ιστότοπος Περίληψη + προστέθηκε στην ουρά Δεν υπάρχουν διαθέσιμοι υπότιτλοι Προεπιλεγμένοι υπότιτλοι - Ελεύθερος + + Ελέυθερος Σε χρήση Εφαρμογή + Ταινίες - Τηλεοπτικές σειρές + Τηλεοπτικές Σειρές Κινούμενα σχέδια Torrents Ντοκιμαντέρ - Ασιατικά Δράματα - Ζωντανές Μεταδόσεις + Ασιατικά Δράμα + Ζωντανές ροές Άλλα + Ταινία Σειρά - Κινούμενα Σχέδια + Cartoon Ντοκιμαντέρ Ασιατικό Δράμα - Ζωντανή Μετάδοση + Ζωντανή ροή Άλλο + Σφάλμα πηγής Απομακρυσμένο σφάλμα Σφάλμα απόδοσης - Απρόσμενο σφάλμα αναπαραγωγής + Μη αναμενόμενο σφάλμα αναπαραγωγής Σφάλμα λήψης, επιβεβαιώστε ότι η άδεια αποθήκευσης είναι ενεργοποιημένη - Chromecast επεισόδιο + + Chromecast επεισόδο Αναπαραγωγή εντός της εφαρμογής Αναπαραγωγή σε %s Αναπαραγωγή στον περιηγητή @@ -203,40 +227,52 @@ Λήψη mirror Επαναφόρτωση συνδέσμων Λήψη υποτίτλων + Ποιότητα Dub Sub Τίτλος - Εναλλαγή γραφικών στοιχείων στην αφίσα + Εναλλαγή των στοιχείων UI στο poster + Κλείδωμα Αλλαγή μεγέθους Πηγή Παράλειψη OP + Να μην εμφανιστεί ξανά Παράλειψη της τρέχουσας ενημέρωσης Ενημέρωση - Προτιμώμενη ποιότητας παρακολούθησης - Μέγιστος αριθμός χαρακτήρων τίτλου - Ανάλυση προγράμματος αναπαραγωγής βίντεο - Μέγεθος buffer για βίντεο - Μήκος buffer για βίντεο - Μέγεθος προσωρινής μνήμης βίντεο στον δίσκο + Προτίμηση ποιότητας παρακολούθησης + Μέγιστοι χαρακτήρες για τίτλο + Ανάλυση αναπαραγωγής βίντεο + + Μέγεθος buffer βίντεο + Μήκος buffer βίντεο + Προσωρινή μνήμη βίντεο στο δίσκο Εκκαθάριση προσωρινής μνήμης βίντεο και εικόνων - Προκαλεί καταρρεύσεις εάν οριστεί πολύ ψηλά. Μην το αλλάξετε εάν έχετε χαμηλή ποσότητα μνήμης RAM, όπως σε Android TV ή παλιά συσκευή. - Προκαλεί προβλήματα σε συστήματα με χαμηλό αποθηκευτικό χώρο, όπως σε συσκευές Android TV. - Χρήσιμο για παράκαμψη μπλοκαρισμάτων από ISP + + Θα προκαλέσει τυχαία σφάλματα εάν οριστεί πολύ ψηλά. Μην το αλλάξετε εάν έχετε χαμηλή ποσότητα μνήμης ram, όπως σε Android TV ή παλιό τηλέφωνο + Μπορεί να προκαλέσει προβλήματα σε συστήματα με χαμηλό αποθηκευτικό χώρο, όπως σε συσκευές Android TV, εάν τον ρυθμίσετε πολύ ψηλά + + Χρήσιμο για παράκαμψη μπλοκ ISP + Αντίγραφο ιστοτόπου Αφαίρεση ιστοτόπου Προσθήκη αντιγράφου ενός υπάρχοντος ιστοτόπου, με έναν διαφορετικό σύνδεσμο + Διαδρομή λήψης - Εμφάνιση Dubbed/Subbed Άνιμε + + Εμφάνιση Dubbed/Subbed Anime + Προσαρμογή στην οθόνη Τέντωμα Μεγέθυνση + Αποποίηση ευθυνών + Γενικά - Κουμπί τυχαίας δράσης - Εμφάνιση κουμπιού τυχαίας δράσης στην Αρχική Οθόνη + Τυχαίο κουμπί + Εμφάνιση τυχαίου κουμπιού στην Αρχική οθόνη Γλώσσες παρόχων Διάταξη εφαρμογής Προτιμώμενα μέσα @@ -244,15 +280,19 @@ Κωδικοποίηση υποτίτλων Πάροχοι Διάταξη + Αυτόματο Διάταξη TV Διάταξη τηλεφώνου Διάταξη emulator + Πρωτεύον χρώμα Θέμα εφαρμογής Τοποθεσία τίτλου Poster Τοποθετήστε τον τίτλο κάτω από το poster + Κωδικός γλώσσας (el) + Λογαριασμός Αποσύνδεση Σύνδεση @@ -264,7 +304,8 @@ Συγχρονισμός Βαθμολογήθηκε Πιστοποιήθηκε %s - Αποτυχία σύνδεσης στο %s + Αποτυχία πιστοποίησης σε %s + Τίποτα Κανονικά Όλα @@ -280,6 +321,7 @@ Χρησιμοποιήστε αυτό αν οι υπότιτλοι εμφανίζονται %dms πολύ νωρίς Χρησιμοποιήστε αυτό αν οι υπότιτλοι εμφανίζονται %dms πολύ αργά Καμία καθυστέρηση υποτίτλων + Συνιστώμενο Φόρτωση %s Φόρτωση από αρχείο @@ -288,15 +330,19 @@ Κύριο Υποστηρίζεται Φόντο + Πηγή Τυχαίο + Έρχεται σύντομα… + + Εικόνα Poster Πρόγραμμα αναπαραγωγής Ανάλυση και τίτλος Τίτλος Ανάλυση - Μη έγκυρο ID + Μη έγκυρο id Μη έγκυρα δεδομένα Μη έγκυρος σύνδεσμος Σφάλμα @@ -310,48 +356,47 @@ Επόμενο Παρακολούθηση βίντεο σε αυτή την γλώσσα Προηγούμενο - Παράλειψη διαμόρφωσης της εφαρμογής + Παράλειψη διαμόρφωσης Αλλαγή της εμφάνισης της συσκευής για να ταιριάζει με την συσκευή σας Αναφορά κατάρρευσης Τι θα θέλατε να δείτε Έγινε - Extensions + Πρόσθετα Προσθήκη αποθετηρίου Όνομα αποθετηρίου Σύνδεσμος αποθετηρίου Το πρόσθετο φορτώθηκε Το πρόσθετο διαγράφηκε - Αποτυχία φόρτωσης του %s - Ξεκίνησε η λήψη %d %s… - Κατέβηκε το %d %s επιτυχώς + Απέτυχε να φορτωθεί το %s + Ξεκίνησε η λήψη %d %s + Κατέβηκε το %d %s επιτυχώς Όλα τα %s έχουν ήδη κατέβει Μαζική λήψη Πρόσθετο Πρόσθετα Αυτό θα διαγράψει όλα τα πρόσθετα του αποθετηρίου Διαγραφή αποθετηρίου - Κατεβάστε την λίστα των ιστότοπων που θέλετε να χρησιμοποιήσετε + Λήψη της λίστας των ιστοσελίδων που θέλετε να δείτε Κατέβηκε: %d Απενεργοποιήθηκε: %d Δεν κατέβηκε: %d Ενημερώθηκαν %d πρόσθετα - Το CloudStream δεν έχει προεγκατεστημένους ιστότοπους. Πρέπει να εγκαταστήσετε ιστότοπους μέσω ορισμένων αποθετηρίων. -\n -\nΛόγω ενός χαζού DMCA takedown από μέρους των Sky UK Limited 🤮 δεν μπορούμε να προσθέσουμε απευθείας σύνδεσμο προς τα προαναφερόμενα αποθετήρια εντός της εφαρμογής. -\n -\nΒρείτε μας στο Discord ή ψάξτε στο διαδίκτυο. + Προσθήκη ενός αποθετηρίου για να εγκαταστήσετε πρόσθετα ιστοσελίδας Προβολή αποθετηρίων κοινότητας Δημόσια λίστα Κεφαλοποίηση υποτίτλων + Λήψη όλων των προσθέτων από αυτό το αποθετήριο; %s (Απενεργοποιήθηκε) Κομμάτια Ηχητικά κομμάτια Κομμάτια βίντεο Εφαρμογή στην επανεκκίνηση + Η ασφαλής λειτουργία ενεργοποιήθηκε - Όλα τα extensions απενεργοποιήθηκαν , ώστε να μπορέσετε να διαπιστώσετε ποιο από αυτά προκάλεσε τη κατάρρευση. + Ένα μη αντιστρέψιμο σφάλμα συνέβη και απενεργοποιήσαμε όλα τα πρόσθετα, ώστε να μπορέσετε να διαπιστώσετε ποιο πρόσθετο προκάλεσε αυτή τη κατάρρευση. Προβολή πληροφορίας κατάρρευσης + Βαθμολογία: %s Περιγραφή Έκδοση @@ -361,7 +406,9 @@ Υποστηρίζονται Γλώσσα Εγκατάσταση προσθέτου πρώτα + HLS Playlist + Προτεινόμενο πρόγραμμα αναπαραγωγής Ενσωματωμένο πρόγραμμα αναπαραγωγής VLC @@ -369,144 +416,6 @@ Web Video Cast Περιηγητής Η εφαρμογή δεν βρέθηκε - %s Επ %d - Το επεισόδιο %d θα κυκλοφορήσει σε - Φίλτρο - %d λεπτά - Ηθοποιοί: %s - %dμ %dω %dλ - %dω %dλ - %dλ - Αναζήτηση %s… - Αναπαραγωγή ζωντανής μετάδοσης - Ληφθέν - Η λήψη ακυρώθηκε - Η λήψη απέτυχε - Η λήψη ξεκίνησε - Η λήψη παύθηκε - Κατεβαίνει - Κλείσιμο - Εκκαθάριση - Γλώσσα υποτίτλων - @string/home_play - Δεν έχουν παρασχεθεί μεταδεδομένα από τον ιστότοπο, η φόρτωση του βίντεο θα αποτύχει αν δεν υπάρχει στον ιστότοπο. - Διπλό πάτημα για παύση - Μέγεθος αναζήτησης στο πρόγραμμα αναπαραγωγής - Αυτόματη ενημέρωση plugin - Αυτόματη λήψη plugin - DNS μέσω HTTPS - παράδειγμα.com - HQ - TS - TC - Blu-ray - WP - DVD - 4K - SDR - Web - HDR - Ανάμεικτοι τίτλοι αρχής - Εύσημα - Εισαγωγή - +30 - Ολοκληρώθηκε - NSFW - NSFW - Chromecast mirror - Σύνδεσμος NGINX σέρβερ - ΟΚουλΙστότοποςΜου - /\?\? - /%d - %d / 10 - Εκκαθάριση ιστορικού - Ιστορικό - Εμφάνιση pop-up για παράλειψη τίτλων αρχής και τέλους - Υπερβολικά μεγάλο μέγεθος κειμένου. Αδύνατη η αποθήκευση στο πρόχειρο. - Ναι - Όχι - Λήψη ενημέρωσης εφαρμογής… - Εγκατάσταση ενημέρωσης εφαρμογής… - Αδύνατη η εγκατάσταση της νέας έκδοσης της εφαρμογής - Ξαναβλέπει - Μετάδοση - Η λήψη ολοκληρώθηκε - Αυτόματη αναπαραγωγή επόμενου επεισοδίου - Αυτόματη εγκατάσταση όλων των μη-εγκατεστημένων plugin από τους προστιθεμένους παρόχους. - Άνιμε - Δεν βρέθηκε ενημέρωση - Έλεγχος για ενημέρωση - κωδικός123 - ΤοΚουλΨευδώνυμοΜου - γειασου@κόσμε.com - Η γρήγορη, καφέ αλεπού πηδάει πάνω από τον τεμπέλη σκύλο / The quick brown fox jumps over the lazy dog - Cam - Cam - Cam - HD - SD - UHD - 18+ - Τίτλοι αρχής - Τίτλοι τέλους - Ανακεφαλαίωση - Ανάμεικτοι τίτλοι τέλους - -30 - Κριτική - @string/ova - Ενημερώσεις εφαρμογής - Αντίγραφο ασφαλείας - Extensions - Δράσεις - Προσωρινή μνήμη - Χαρακτηριστικά προγράμματος αναπαραγωγής - Υπότιτλοι - Δομή - Προεπιλεγμένα - %s %s - Μέγεθος γραμματοσειράς - @string/anime - Σύνδεσμοι - Εμφάνιση - Χαρακτηριστικά - 127.0.0.1 - Όλες οι γλώσσες - Παράλειψη %s - PackageInstaller - Legacy - Αναπαραγωγή τρέιλερ - Ορισμός κατάστασης θέασης - Αντιγραφή - Αποθήκευση - Εισάγετε γραμματοσειρές τοποθετώντας \'τες στο %s - Αναπαραγωγή του επόμενου επεισοδίου με το τέλος του τρέχοντος - Εγκαταστάτης APK - Ορισμένες συσκευές δεν υποστηρίζουν τον νέο εγκαταστάτη πακέτων. Δοκιμάστε την επιλογή Legacy εάν δεν εγκαθίστανται οι ενημερώσεις. - OVA - Torrent - Χειρονομίες - Σημείωσε το ως πλήρως παρακολουθημένο - Είστε σίγουροι πως θέλετε να βγείτε; - Επανάληψη διαδικασίας διαμόρφωσης της εφαρμογής - Το πρόσθετο κατέβει - Ενημέρωση ξεκίνησε - Η εφαρμογή θα ενημερωθεί κατά την έξοδο - Αλφαβητικά (Ω προς Α) - Ταξινόμηση - Κριτική (Χαμηλή προς Υψηλή) - Ενημερωμένο (Καινούριο προς παλιό) - Ενημερωμένο (Παλιό προς Καινούργιο) - Βιβλιοθήκη - Κριτική (Υψηλή προς χαμηλή) - Ταξινόμηση με βάση - Αλφαβητικά (Α προς Ω) - Διάλεξε βιβλιοθήκη - Φαίνεται πως η λίστα είναι άδεια, δοκίμασε να μεταβείς σε μία άλλη - Αφαίρεση από παρακολουθημένα - Περιηγητής - Άνοιγμα με - Φαίνεται πως η βιβλιοθήκη σου είναι άδεια :( -\nΣυνδέσου σε έναν λογαριασμό που έχει βιβλιοθήκη, ή πρόσθεσε σειρές στην τοπική βιβλιοθήκη σου - Βρέθηκε αρχείο Ασφαλούς Λειτουργίας! -\nΔεν πρόκειται να φορτωθούν extensions κατά το ξεκίνημα μέχρι να διαγραφεί το αρχείο. + + diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml deleted file mode 100644 index 5eac8686..00000000 --- a/app/src/main/res/values-eo/strings.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - Reen - Hejmo - Elŝutoj - Reen - Fontoj - Efektivigi - Konservi - Tipara grando - Forigi - Epizodoj - E - +30 - Jaro - %d / 10 - /\?\? - /%d - Lingvo - Grando - Aŭtoroj - Priskribo - Versio - Stato - Nuligi - Forviŝi - Jes - Ne - Historio - Serĉi - Afiŝo - Spekti filmon - Spekti - Informoj - Kopii - Fermi - Tiparo - Elŝuti lingvojn - Aŭtomate elekti lingvon - Teksta koloro - Forigi - Informoj - S - Stato - Fonta eraro - Saluti - konto - Fenestra koloro - Agordoj - Ĝenroj - Ĉiuj lingvoj - Serĉi - Kontoj - GitHub - Sezono - Epizodo - Azia dramo - Aziaj dramoj - Serio - Televidaj serioj - −30 - Filmo - Filmoj - Aliaj - Blu-radia - Titolo - Nenio - Adiaŭi - Titolo - Fonto - Lingvokodo (eo) - pasvorto123 - MiaSalutNomo - saluton@mondo.com - 127.0.0.1 - MiaRetejo - ekzemplo.com - Rapido (%.2fx) - Serĉi… - Elŝuti - diff --git a/app/src/main/res/values-es/strings-es.xml b/app/src/main/res/values-es/strings-es.xml new file mode 100644 index 00000000..172d079c --- /dev/null +++ b/app/src/main/res/values-es/strings-es.xml @@ -0,0 +1,464 @@ + + + + %s Ep %d + Reparto: %s + El episodio %d se publicará en + %dd %dh %dm + %dh %dm + %dm + + + + Poster + @string/result_poster_img_des + Episode Poster + Main Poster + Next Random + Go back + Change Provider + Preview Background + + + Velocidad (%.2fx) + Calificación: %.1f + ¡Nueva actualización encontrada!\n%s -> %s + Rellenar + %d minutos + + CloudStream + Inicio + Búsqueda + Descargas + Ajustes + + Buscar… + Buscar en %s… + + Sin datos + Más opciones + Siguiente episodio + Géneros + Compartir + Abrir en el navegador + Omitir carga + Cargando… + + Viendo + En espera + Completada + Caída + Planeo ver + Ninguno + Volviendo a ver + + Reproducir película + Transmitir torrent + Fuentes + Subtítulos + Reintentar la conexión… + Regresar + Reproducir episodio + + + Descargar + Descargado + Descargando + Descarga pausada + Descarga iniciada + Descarga fallida + Descarga cancelada + Descarga realizada + Transmitir + + Error al cargar enlaces + Almacenamiento interno + + Doblado + Subtitulado + + Eliminar archivo + Reproducir archivo + Reanudar descarga + Pausar descarga + + Desactivar el informe automático de errores + Más información + Ocultar + Reproducir + Información + Filtrar marcadores + Marcadores + Eliminar + Establecer estado de vigilancia + Aplicar + Cancelar + Copiar + Cerrar + Limpiar + Guardar + + Velocidad del reproductor + + Ajustes de subtítulos + Color del texto + Color del contorno + Color del fondo + Color de la ventana + Tipo de borde + Elevación de subtítulos + Fuente + Tamaño de la fuente + + Buscar usando proveedores + Buscar usando tipos + + %d bananas otorgadas a los desarrolladores + No se han otorgado bananas + + Selección automática de idioma + Idiomas a descargar + Idioma de los subtítulos + Mantenga pulsado para restablecer a los valores predeterminados + Importar fuentes colocándolas en %s + Seguir viendo + + Eliminar + Más información + @string/home_play + + Es posible que se necesite una VPN para que este proveedor funcione correctamente + Este proveedor es un torrent, se recomienda una VPN + El sitio no proporciona los metadatos, la carga del video fallará si no existe en el sitio. + + Descripción + No se encontró ninguna trama + No se encontró ninguna descripción + + Mostrar logcat 🐈 + + Imagen en imagen + Continúa la reproducción en un reproductor en miniatura encima de otras aplicaciones + Botón de cambio de tamaño del reproductor + Elimina los bordes negros + Subtítulos + Ajustes de subtítulos del reproductor + Subtítulos de Chromecast + Ajustes de subtítulos de Chromecast + + Modo Eigengravy + Añade una opción de velocidad en el reproductor + Deslizar para desplazarse + Deslice el dedo hacia la izquierda o hacia la derecha para controlar el tiempo en el reproductor de vídeo + Deslizar para cambiar los ajustes + Deslice el dedo hacia la izquierda o hacia la derecha para cambiar el brillo o el volumen + Tocar dos veces para desplazarse + Tocar dos veces para pausar + Cantidad de desplazamiento del reproductor + Toque dos veces en el lado derecho o izquierdo para desplazarse hacia adelante o hacia atrás + Tocar en el medio para pausar + Usar el brillo del sistema + Usa el brillo del sistema en el reproductor en lugar de una superposición oscura + + Actualizar el progreso visto + Sincroniza automáticamente el progreso de tu episodio actual + + Restaurar datos desde una copia de seguridad + + Copia de seguridad de los datos + Archivo de copia de seguridad cargado + No se pudieron restaurar los datos del archivo %s + Datos almacenados con éxito + Faltan los permisos de almacenamiento, inténtelo de nuevo + Error al hacer una copia de seguridad de %s + + Búsqueda + Cuentas + Actualizaciones y copia de seguridad + + Información + Búsqueda avanzada + Te da los resultados de la búsqueda separados por proveedor + Solo envía datos sobre fallas + No envía datos + Mostrar episodio de relleno para anime + Mostrar tráilers + Mostrar pósters de kitsu + + Mostrar actualizaciones de la aplicación + Busca automáticamente nuevas actualizaciones al abrir la app + Actualizar a versiones preliminares + Busca actualizaciones preliminares en lugar de solo versiones completas + Github + Aplicación de novela ligera de los mismos desarrolladores + Aplicación de anime de los mismos desarrolladores + Únete al Discord + Dar una banana a los desarrolladores + Banana otorgada + + Idioma de la aplicación + + Este proveedor no es compatible con Chromecast + No se encontraron enlaces + Enlace copiado al portapapeles + Reproducir episodio + Restablecido al valor predeterminado + Lo sentimos, la aplicación se bloqueó. Se enviará un informe de error anónimo a los desarrolladores + + Temporada + Sin temporada + Episodio + Episodios + T + E + No se encontraron episodios + + Eliminar archivo + Eliminar + Pausar + Reanudar + -30 + +30 + Esto eliminará permanentemente %s\n¿Está seguro? + %dm\nrestante + + + En curso + Completada + Estado + Año + Calificación + Duración + Sitio + Sinopsis + + Puesto en cola + Sin subtítulos + Predeterminados + + Libre + Usado + Aplicación + + + Películas + Series de TV + Dibujos animados + Anime + Torrents + Documentales + OVA + Dramas asiáticos + + + Película + Serie + Dibujo animado + @string/anime + @string/ova + Torrent + Documental + Drama asiático + + Error de fuente + Error remoto + Error del renderizador + Error inesperado del reproductor + Error de descarga, verifique los permisos de almacenamiento + + Episodio Chromecast + Espejo Chromecast + Reproducir en la app + Reproducir en %s + Reproducir en el navegador + Copiar enlace + Descarga automática + Descargar espejo + Recargar enlaces + Descargar subtítulos + + Etiqueta de calidad + Etiqueta de doblaje + Etiqueta de subtitulado + Título + Alternar elementos de la interfaz de usuario en el póster + + No se encontró ninguna actualización + Buscar actualizaciones + + Bloquear + Redimensionar + Fuente + Omitir OP + + No volver a mostrar + Omitir esta actualización + Actualizar + Calidad de vista preferida + Caracteres máximos del título en el reproductor + Resolución del reproductor de vídeo + + Tamaño del búfer de vídeo + Duración del búfer de vídeo + Caché de vídeo en disco + Limpiar caché de imágenes y vídeos + + Provocará bloqueos aleatorios si se establece demasiado alto. No lo cambie si tiene poca memoria RAM, como un Android TV o un teléfono antiguo + Puede causar problemas en sistemas con poco espacio de almacenamiento, como dispositivos Android TV, si lo configura demasiado alto + + DNS sobre HTTPS + Útil para eludir los bloqueos del ISP + + Clonar sitio + Eliminar sitio + Añade un clon de un sitio existente, con una URL diferente + + Ruta de descarga + + URL del servidor Nginx + + Mostrar anime doblado/subtitulado + + Ajustar a la pantalla + Estirar + Zoom + + Descargo de responsabilidad + 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. + + In case of copyright infringement, please directly contact the responsible parties or the streaming websites. + + The app is purely for educational and personal use. + + CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. + CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or + manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, + user-friendly interface. + + It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. + + General + Botón aleatorio + Muestra un botón aleatorio en la página de inicio + Idiomas del proveedor + Diseño de la aplicación + Medios preferidos + Codificación de subtítulos + Diseño + + Automático + Diseño de televisión + Diseño de teléfono + Diseño de emulador + + Color primario + Tema de la aplicación + Póster en la ubicación del título + Coloca el título debajo del póster + + + + contraseña123 + MiNombreDeUsuarioGenial + hola@mundo.com + 127.0.0.1 + MiSitioGenial + ejemplo.com + Código de idioma (es) + + + %s %s + cuenta + Cerrar sesión + Iniciar sesión + Cambiar cuenta + Añadir cuenta + Añadir seguimiento + Añadido %s + Sincronizar + Calificación + %d / 10 + /?? + /%d + %s autenticado + No se pudo autenticar a %s + + + Ninguno + Normal + Todo + Máximo + Mínimo + Contorno + Deprimido + Sombra + Elevado + Sincronizar subtítulos + 1000ms + Retraso de subtítulos + Use esto si los subtítulos se muestran %dms demasiado pronto + Use esto si los subtítulos se muestran %dms demasiado tarde + Sin retraso de subtítulos + + + El rápido zorro marrón salta sobre el perro perezoso + + Recomendado + %s cargado + Cargar desde archivo + Cargar desde Internet + Archivo descargado + Principal + Secundario + Extra + + Fuente + Aleatorio + + Próximamente… + + Cam + Cam + Cam + HQ + HD + TS + TC + BlueRay + WP + DVD + 4K + SD + UHD + HDR + SDR + Web + + Imagen del póster + Reproductor + Resolución y título + Título + Resolución + Identificación no válida + Datos no válidos + Error + Eliminar subtítulos cerrados de subtítulos + Eliminar exceso de peso de subtítulos + Extras + Tráiler + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml deleted file mode 100644 index 06c20aa5..00000000 --- a/app/src/main/res/values-es/strings.xml +++ /dev/null @@ -1,532 +0,0 @@ - - - Extensiones - Descargue la lista de sitios que quiera utilizar - Descargado:%d - Descargado - Descargado %d %s - Borrar repositorio - El episodio %d se lanzará en - %dh %dm - %dm - Poster - Extensiones - Archivo descargado - Todas las extensiones se desactivaron debido a un fallo para ayudarlo a encontrar la que está causando problemas. - Todo %s ha sido descargado - No descargado:%d - %s (Desactivado) - URL del repositorio - Desactivado:%d - Agregar repositorio - Nombre del repositorio - Esto también eliminará todos los plugins del repositorio - Ocultar la calidad de video en los resultados de búsqueda - Diseño - Diseño - Calidad de visualización preferida (WiFi) - Reproductor de video preferido - Diseño para emulador - Diseño de la aplicación - Diseño de TV - Diseño para teléfonos - Subtítulos - Codificación de subtítulos - Cambia el aspecto de la aplicación para que se adapte a tu dispositivo - Configurar Subtítulos - Configuración de subtítulos del reproductor - Sombra - Subtítulos de Chomecast - Descargar subtítulos - Subtítulos - Deprimido - Le gustaba cenar un exquisito sándwich de jamón con zumo de piña y vodka fría - Cargar desde archivo - Cargar desde Internet - Reproducir automáticamente episodio siguiente - Configuración de subtítulos de Chromecast - Predeterminado - Contorno - Sin Subtítulos - Elevado - Use esto si los subtítulos se muestran %d ms muy pronto - Use esto si los subtítulos se muestran %d ms tarde - Desliza el dedo de lado a lado para controlar la posición en un video - Filtrar por idioma de medios preferido - Eliminar Closed Captions (CC) de los subtítulos - Cantidad de búsquedas del reproductor (segundos) - Use el brillo del sistema en el reproductor de la app en lugar de una superposición oscura - Resolución del reproductor de video - MPV - Reproductor - VLC - Web Video Cast - Navegador Web - Iniciar el siguiente episodio cuando el actual termine - Omitir Intro - Apertura - Omitir esta actualización - Omitir %s - Final - Apertura mixta - Resumen - Créditos - Final mixto - Poster del Episodio - Siguiente episodio - Más opciones - Actualizar progreso de lo visto - Duplicar en Chromecast - No se encontraron Episodios - Reproducir en Navegador - Reproducir en %s - Copiar enlace - Descarga automática - Descargar desde servidor alternativo - Recargar enlaces - /\?\? - /%d - Esto eliminará %s permanentemente -\nEstá seguro\? - Está seguro que quiere salir\? - Continuar Descarga - Código de idioma (es_LA) - Poster Principal - Idioma de la aplicación - Ver videos en estos idiomas - Cartel - Siguiente al azar - Todos los Idiomas - Volver - Cambiar proveedor - Vista previa del fondo - Nota:%.1f - Nueva actualización encontrada! -\n%s -> %s - Descargar - Pausar Descarga - Fuente - Color de Fondo - Tamaño Fuente - Velocidad (%.2fx) - Omitir carga - %s Ep %d - %dd %dh %dm - Elenco %s - Relleno - %d min - CloudStream - Reproducir con Cloudstream - Inicio - Buscar - Descargas - Configuración - Buscar… - Buscar en %s… - Sin datos - Géneros - Compartir - Abrir en Navegador - Cargando… - Ocultar - Aplicar - Subtítulos - Velocidad del reproductor - Reproducir Episodio - Mirando - En espera - Completado - Descartado - Planeando ver - Ninguno - Volviendo a mirar - Reproducir película - Reproducir Trailer - Reproducir transmisión en vivo (livestream) - Transmitir Torrent - Fuentes - Reintentar conexión… - Volver - Descargando - Descarga pausada - Descarga iniciada - Descarga fallida - Descarga Cancelada - Descarga Finalizada - Transmitir - Error cargando enlaces - Almacenamiento Interno - Doblado - Subtitulado - Borrar Archivo - Reproducir Archivo - Más información - Reproducir - Info - Filtrar Marcadores - Marcadores - Remover - Seleccionar estado de visualización - Cancelar - Copiar - Cerrar - Limpiar - Guardar - Color de Texto - Color de Contorno - Color de Ventana - Tipo de Borde - Elevación de Subtítulo - Buscar usando proveedores - Continúa la reproducción en un reproductor miniatura encima de otras aplicaciones - Botón de cambio de tamaño del reproductor - Eliminar bordes negros - Agrega la opción de velocidad en el reproductor - Seleccionar idioma automáticamente - Descargar Idiomas - Idioma del subtítulo - Imagen en Imagen (PIP) - Buscar por tipos - %d bananas otorgadas a los desarrolladores - No se otorgaron bananas - Mantenga pulsado para restablecer los valores predeterminados - Importar fuentes colocándolas en %s - Continuar Viendo - Remover - Más info - @string/home_play - Una VPN puede ser necesaria para que este proveedor funcione correctamente - Este proveedor es un torrent, se recomienda una VPN - El sitio no proporciona los metadatos, la carga del video fallará si no existe en el sitio. - Descripción - Trama no encontrada - Descripción no encontrada - Mostrar Logcat 🐈 - Modo Eigengravy - Deslice para avanzar/retroceder - Deslice para cambiar la configuración - Deslice hacia arriba o hacia abajo en el lado izquierdo o derecho para cambiar el brillo o el volumen - Toca dos veces para buscar - Tocar dos veces para pausar - Toque dos veces en el lado derecho o izquierdo para buscar hacia adelante o hacia atrás - Toque dos veces en el medio para hacer una pausa - Usar brillo del sistema - Restaurar datos desde el backup - Hacer copia de los datos (backup) - Archivo de backup cargado - Busque automáticamente nuevas actualizaciones después de iniciar la aplicación. - Rehacer el proceso de configuración inicial - Mostrar episodio de relleno para Anime - Reproducir Episodio - Episodio - Episodios - %d-%d - %d %s - E - Falló la restauración de los datos desde el archivo %s - Datos guardados - Faltan permisos de almacenamiento. Por favor intente de nuevo. - Error de backup de %s - Buscar - Cuentas - Actualizaciones y copias de seguridad - Info - Búsqueda Avanzada - Da los resultados de la búsqueda separados por proveedor - Solo envía datos si la App termina / falla inesperadamente (crash) - No enviar datos - Mostrar Trailers (avances) - Mostrar pósters de Kitsu - Actualizar a las versiones preliminares - Buscar actualizaciones preliminares (beta) en lugar de solo versiones completas (stable releases) - Github - App de novela ligera de los mismos desarrolladores - App de Anime de los mismos desarrolladores - Únete a Discord - Dale una banana a los desarrolladores - Banana otorgada - El proveedor no tiene soporte para Chromecast - Enlaces no encontrados - Enlaces copiados al portapapeles - Reiniciar a valores predefinidos - Lo sentimos, la aplicación se bloqueó. Se enviará un informe de error anónimo a los desarrolladores - Temporada - %s %d%s - Ninguna Temporada - T - Borrar Archivo - Borrar - Error inesperado del reproductor - Episodio en Chromecast - Reproducir en la aplicación - Pausar - Continuar - -30 - +30 - %dm -\nfaltante - En curso - Completado - Estado - Máxima cantidad de caracteres en el título del reproductor - Actualizaciones de la aplicación - Auto - Acciones - Activa NSFW (contenido adulto) en proveedores soportados - Proveedores - Predeterminados - Medios preferidos - Gestos - Idiomas del proveedor - %s Cargado - Eliminar excedente (bloat) de los subtítulos - Omitir configuración - Qué quieres ver - Poner en MAYÚSCULAS todos los subtítulos - Se aplican los cambios al reiniciar la App - Reproductor interno - Idioma - Legacy (método antiguo) - Instalador de paquetes - CloudStream no tiene sitios instalados de forma predeterminada. Necesita instalarlos desde repositorios. -\n -\nDebido a que dieron de baja el proyecto de manera arbitraria por infracción a la lay de derechos de autor (DMCA) de parte de Sky UK Limited 🤮, no podemos vincular el sitio del repositorio en la aplicación. -\n -\nÚnete a nuestro Discord o busca en línea. - Descarga todos los plugins desde este repositorio\? - Mostrar actualizaciones de la aplicación - Instalador de APK - Algunos dispositivos no soportan el nuevo instalador de paquetes. Pruebe la opción antigua (legacy) si las actualizaciones no se instalan. - Desactivar reporte automático de bugs - Sincronizar automáticamente el progreso de su episodio actual - Actualización automática de plugins - Características del reproductor - Descarga automática de plugins - Instala automáticamente todos los plugins aún no instalados de los repositorios agregados. - Aspecto - Características - Botón de Al azar - Muestra un botón de reproducción \"al azar\" en la página de inicio para poelículas y series - cuenta - Cerrar sesión - Cambiar cuenta - Añadir cuenta - Sincronizar - Calificación - %s autenticado - No se pudo autenticar a %s - Recomendado - Imagen del póster - Tráiler - Próximamente… - Al azar - Principal - Datos no válidos - Añadir seguimiento - Fuente - Normal - Añadido %s - Iniciar sesión - Año - Calificación - Duración - Sitio - Sinopsis - Puesto en cola - Libre - Usado - Aplicación - Películas - Series de TV - Dibujos animados - Título - Alternar elementos de la interfaz de usuario en el póster - No se encontró ninguna actualización - General - Color primario - Tema de la aplicación - hola@mundo.com - %s %s - Todo - 1000ms - Sin retraso de subtítulos - Secundario - Extra - Resolución y título - Error - Extras - Anime - Torrents - Documentales - OVA - Dramas asiáticos - Película - Serie - Dibujo animado - Anime - OVA - Torrent - Documental - Drama asiático - Error de fuente - Error remoto - Error del renderizador - Error de descarga, verifique los permisos de almacenamiento - Etiqueta de calidad - Etiqueta de doblaje - Etiqueta de subtitulado - Buscar actualizaciones - Mostrar anime doblado/subtitulado - Bloquear - Redimensionar - Fuente - No volver a mostrar - Actualizar - Tamaño del búfer de vídeo - Duración del búfer de vídeo - Caché de vídeo en disco - Limpiar caché de imágenes y vídeos - Provocará bloqueos aleatorios si se establece demasiado alto. No lo cambie si tiene poca memoria RAM, como un Android TV o un teléfono antiguo. - Puede causar problemas en sistemas con poco espacio de almacenamiento, como dispositivos Android TV, si lo configura demasiado alto. - DNS sobre HTTPS - Útil para eludir los bloqueos del ISP - Clonar sitio - Eliminar sitio - Añade un clon de un sitio existente, con una URL diferente - Ruta de descarga - URL del servidor Nginx - Ajustar a la pantalla - Estirar - Zoom - Descargo de responsabilidad - Ubicación del título en el póster - Coloca el título debajo del póster - contraseña123 - MiNombreDeUsuarioGenial - 127.0.0.1 - MiSitioGenial - ejemplo.com - %d / 10 - Ninguno - Máximo - Mínimo - Sincronizar subtítulos - Retraso de subtítulos - Cam - Cam - Cam - HQ - HD - TS - TC - BlueRay - WP - DVD - 4K - SD - UHD - HDR - SDR - Web - Título - Resolución - Identificación no válida - Transmisiones en vivo - NSFW - Otros - Transmisión en vivo - NSFW - Video - Enlaces - Backup - Caché - Crear cuenta - URL Inválida - Enlace a la transmisión - Referente - Siguiente - Anterior - Hecho - Plugin Cargado - 18+ - Iniciada la descarga %d %s… - Descarga por lotes - plugin - plugins - Actualizados %d plugins - Ver repositorios de la comunidad - Lista pública - Pistas - Pistas de audio - Pistas de video - Modo seguro ON - Ver info de fallos - Puntaje:%s - Versión - Estado - Tamaño - Autores - Soportado - Instale las extensiones primero - Lista de reproducción HLS - Limpiar historial - Historial - Marcar como visto - Demasiado texto. No se puede guardar en el portapapeles. - No - Descargando actualización de la aplicación… - Instalando actualización de la aplicación… - No se pudo instalar la nueva versión de la aplicación - App no encontrada - Reporte de fallos - Plugin Borrado - No puede cargar %s - Descripción - Intro - Muestrar botones emergentes para saltar apertura/finalización del video - Si - La aplicación se actualizará al salir - Actualización iniciada - Complemento descargado - Quitar de visto - Ordenar por - Ordenar - Valoración (más a menos) - Valoración (menos a más) - Actualizado (nuevo a viejo) - Actualizado (viejo a nuevo) - Alfabéticamente (A a Z) - Navegador - Biblioteca - Parece que esta lista está vacía, intenta cambiar a otra - Alfabéticamente (Z a A) - Seleccionar biblioteca - Abrir con - Parece que tu biblioteca está vacía :( -\nInicia sesión en una cuenta de biblioteca o añade series desde tu biblioteca local - ¡Se encontró un archivo en modo seguro! -\nNo cargar ninguna extensión al inicio hasta que se elimine el archivo. - Reproductor visible - buscar cantidad - Reproductor oculto - buscar cantidad - Android TV - Tiempo de búsqueda usado (en segundos) cuando el reproductor está visible - Tiempo de búsqueda usado (en segundos) cuando el reproductor está oculto - Parar - Falló - Registro - Empezar - Aprobado - Prueba del proveedor - Reiniciar - Suscrito - Suscrito a %s - Darse de baja de %s - Actualizando los programas suscritos - ¡Episodio %d publicado! - Proxy raw.githubusercontent.com - No se ha podido acceder a GitHub, activando el proxy jsdelivr. - Con jsdelivr, se puede omitir el bloqueo de GitHub. Puede retrasar las actualizaciones unos días. - Revertir - ISP Bypasses - Calidad de visualización preferida (Datos móviles) - diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml deleted file mode 100644 index e4c23628..00000000 --- a/app/src/main/res/values-fa/strings.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - حذف - مکث - در صف - بارگیری - در حال بارگیری - اتمام بارگیری - پخش آنلاین - بارگیری ناموفق بود - فیلتر کردن علاقه مندی ها - جست‌وجو - اندازه‌کردن - پر کردن - بزرگ‌نمایی - همه - قطعه‌ها - وضعیت - درحال بارگیری… - عنوان منبع - خروج - ورود - هیچ‌کدام - عنوان - تاریخچه - پوستر - پوستر - پوستر قسمت - %dروز %dساعت %dدقیقه - %s قسمت %d - بازیگران: %s - قسمت %d پخش خواهد شد - %dساعت %dدقیقه - %dدقیقه - پوستر اصلی - diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index b96ff0cd..c98173ce 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1,4 +1,3 @@ - CloudStream @@ -7,22 +6,22 @@ Téléchargements Paramètres Rechercher… - Affiche + Miniature Aucune Donnée Plus d\'options Retour - Épisode suivant - Affiche + Episode suivant + Miniature Genres Partager - Ouvrir dans le navigateur + Ouvrir dans le naviguateur Passer le chargement Chargement… En visionnage - En pause + En pose Terminé Abandonné - À regarder + A regarder Aucun Lire Streamer le Torrent @@ -30,19 +29,19 @@ Sous-titres Réessayer la connection… Retour - Affiche de l\'épisode - Lire l\'Épisode + Miniature de l\'Episode + Lire l\'Episode Télécharger Téléchargé Téléchargement Téléchargement en pause Téléchargement commencé - Échec du téléchargement - Téléchargement annulé - Téléchargement terminé + Echec du Téléchargement + Téléchargement Annulé + Téléchargement Terminé Erreur lors du téléchargement des liens - Stockage interne + Stockage Interne Dub Sub Supprimer le fichier @@ -52,10 +51,10 @@ Désactiver le rapport de bug automatique Plus d\'informations Cacher - Affiche principale + Poster principal Lecture - Infos - Aléatoire suivant + Info + Suivant Aléatoire Changer le fournisseur Filtrer les marques-pages Marque-pages @@ -63,31 +62,84 @@ Appliquer Annuler Vitesse de lecture + Paramètres de sous-titres + Couleur du texte + Couleur de la bordure exterieur + Couleur de fond + Couleur de la fenètre + Type de bordure + Elevation des sous-titres Aperçu de l\'arrière-plan + Police + Rechercher en utilisant les fournisseurs + Rechercher en utilisant les types + %d Benenes données au dev + Aucune Benenes donnée + Séléction automatique de la langue + Télécharger les langues + Maintenir pour réinitialliser les valeurs par défault + Continer à regarder + Supprimer + Plus d\'info + Un VPN peut être requit pour que ce fournisseur fonctionne + Ce fournisseur est un torrent, un VPN est recommendé + Description + Aucune description trouvée + Aucune description trouvée + Lecteur en mode Picture-in-Picture + Continuer la lecture dans une fenêtre miniature en superposition sur d\'autres applis + Boutton de redimentionnement du lecteur + Supprimer les bordures noires + Sous-titres + Paramètres des sous-titres du lecteur + Vitesse de lecture + Ajouter l\'option de vitesse sur le lecteur + Balayer pour avancer rapidement + Balayer vers la gauche ou la droite pour controler le temps du lecteur vidéo + Balayer pour changer les paramètres + Balayer sur le coté droit ou gauche pour changer le niveau de luminosité ou de volume + Taper deux fois pour rechercher + + Taper deux fois pour mettre en pause + + Taper deux fois sur le coté droit ou gauche pour avancer ou reculer + Rechercher + Informations + Recherche Avancée + Donne les résultats séparé par les fournisseur + N\'envoyer les données que lors d\'un crash + N\'envoyer aucune données + Afficher les mises-à-jour de l\'application + Chercher des mises-à-jour automatiquement au démarage + Mettre à jour vers une version béta + Rechercher pour une mise à jour vers une version béta au lieu des version complètes seulement + Github + L\'application Light Novel par les mêmes devs + Application d\'animés par les mêmes devs + Rejoindre le serveur Discord Donner une benene aux devs benenes données Language de l\'application Ce fournisseur ne supporte pas le Chromecast Aucun lien trouvé - Lien copié dans le presse-papier + Lien copié dans le presse-papiers Lecture de l\'episode Réinitialiser aux valeurs par défault - Désolé, l\'application à crashé. Un rapport de bug anonyme va être envoyé aux développeurs + Désolé, l\'application à crashé. Un rapport de bug anonyme va être envoyé aux devloppeurs Saison Pas de Saison - Épisode - Épisodes + Episode + Episodes S E Supprimer le Fichier Supprimer Pause Reprendre - Cela va supprimer définitivement %s -\nÊtes-vous sûr \? + Cela va supprimer définitivement %s\nÊtes vous sûr ? En cours Terminé - Statut + Status Année Note Durée @@ -107,9 +159,9 @@ Erreur de la source Erreur distante Erreur d\'affichage - Erreur inattendue du lecteur + Erreur innatendue du lecteur Erreur du téléchargement, vérifier l\'autorisation du stockage - Épisode Chromecast + Episode Chromecast Miroir Chromecast Lecture dans l\'application Lecture dans %s @@ -127,34 +179,59 @@ Ne pas afficher à nouveau Mettre à jour Utile pour contourner les bloquages des FAI + Emplacement de téléchargement + Nouvelle mise à jour trouvée ! \n%s -> %s Épisode spécial - Qualité de visionnage préférée (WiFi) + Qualité de visionnage préférée Taille de la mémoire cache Étendre Non-responsabilité Couleur principale Thème de l\'application Vitesse (%.2fx) + Utiliser la luminosité du système Général DNS avec HTTPS Afficher les animés en Anglais (Dub) / sous-titrés Disposition en mode téléphone %s Ep %d Note : %.1f + Taille de la police + Utiliser la luminosité du système dans le lecteur de l\'application au lieu d\'un écran noir + Afficher les épisodes spéciaux pour les animés Zoom Adapter à l\'écran Disposition de l\'application Disposition TV Language des fournisseurs - Médias préférées + Médias préfères Auto - Distribution : %s + Acteurs: %s %d min - Rechercher sur %s… - À re-regarder + Rechercher sur %s... + A re-regarder + Copier + Coller + Effacer + Enregister + Importer des polices en les plaçants dans %s + Les metadonnées ne sont pas fournies par le site, le chargement de la vidéo va échoué si elle n\'existe pas sur le site. + Afficher les logs 🐈 + Sous-titres Chromecast + Paramètres des sous-titres Chromecast + Taper au milieu pour mettre en pause + Mettre à jour la progression de visionnage + Synchroniser automatiquement votre progression de l\'épisode actuel + Restaurer les données sauvegardées + Sauvegarder les données + Fichier de sauvegarde chargé + Echec de la restauration des données depuis le fichier + Restauration des données réussie + Permission d\'accès au stockage manquante + Erreur pendant la sauvegarde %s Aucun épisode trouvé Documentaires OVA @@ -170,14 +247,14 @@ Longueur de la vidéo tampon Longueur de la vidéo cache Effacer les vidéos et les images cache - Causera des crashs si la valeur choisie est trop élevée sur un appareil avec peu de ram comme une télévision android. - Cause des problèmes si la valeur choisie est trop élevée sur un appareil avec peu de stockage comme une télévision android. + Causera des crashs aléatoire si mit trop haut. Ne le changez pas si vous avez un appareil avec peu de ram comme une télévision android ou un vieux téléphone. + Causera des problèmes si mit trop haut et si vous avez un appareil avec peu de stockage comme une télévision android ou un vieux téléphone. Bouton aléatoire - Afficher le bouton aléatoire + Afficher le boutton aléatoire Disposition émulateur Position du titre du poster Mettre le titre sous le poster - compte + Compte Se déconnecter Se connecter Changer de compte @@ -186,7 +263,7 @@ %s ajouté Synchroniser Noté - Impossible de se connecté à %s + Impossible de s\'authentifier avec %s Aucun Normal Tous @@ -195,15 +272,15 @@ Contour Déprimé Ombre - Élévation + Elevation Synchroniser les sous-titres - 1000 ms + 1000ms Délai des sous-titres - Utilisez cela si les sous-titres sont affichés %d ms trop tôt - Utilisez cela si les sous-titres sont affichés %d ms trop tard + Utilisez si les sous-titres sont affichés %dms trop tôt + Utilisez si les sous-titres sont affichés %dms trop tard Aucun délai de sous-titre - Portez ce vieux whisky au juge blond qui fume - Recommandé + Le rapide rendard brun saute au-dessus du chien paresseux + Recommendé %s chargé Charger depuis le fichier Fichier téléchargé @@ -212,316 +289,8 @@ Arrière plan Source Aléatoire - Bientôt disponible… + A venir ... Image de l\'affiche - %s Connecté + Connecté %s Définir le statut de visionage - Copier - Fermer - Vider - Enregistrer - Paramètres des sous-titres - Couleur du texte - Couleur des contours - Couleur d\'arrière-plan - Couleur de la fenêtre - Type de bordure - Elevation des sous-titres - Police - Taille de la police - Recherche par fournisseur - Recherche par types - %d Donner une banane aux devs - Aucun Bananes donné - Sélection automatique de la langue - Télécharger les langues - Langue des sous-titres - Maintenir pour rétablir les valeurs par défaut - Importez des polices en les plaçant dans %s - Continuer à regarder - Retirer - Plus d\'informations - @string/home_play - Un VPN peut être nécessaire pour que ce fournisseur fonctionne correctement - Ce fournisseur est un torrent, un VPN est recommandé - Les métadonnées ne sont pas fournies par le site, le chargement de la vidéo échouera si elles n\'existent pas sur le site. - Description - Aucune trace trouvée - Aucune description trouvée - Afficher le Logcat 🐈 - Picture-in-picture - Poursuite de la lecture dans un lecteur miniature au-dessus d\'autres applications - Bouton de redimensionnement du lecteur - Supprimer les bordures noires - Sous-titres - Paramètres des sous-titres du lecteur - Sous-titres Chromecast - Paramètres des sous-titres Chromecast - Mode Eigengravy - Ajout d\'une option de vitesse dans le lecteur - Balayez pour chercher - Balayez vers la gauche ou la droite pour contrôler le temps dans le lecteur vidéo - Balayez pour modifier les paramètres - Glissez sur le côté gauche ou droit pour modifier la luminosité ou le volume - Lecture automatique du prochain épisode - Démarrer l\'épisode suivant lorsque l\'épisode en cours se termine - Double tape pour chercher - Double tape pour mettre en pause - Player seek amount - Tapez deux fois sur le côté droit ou gauche pour aller en avant ou en arrière - Tapez au milieu pour mettre en pause - Utiliser la luminosité du système - Utiliser la luminosité du système dans le lecteur d\'applications au lieu du sombre - Mise à jour de la progression de la veille - Synchronisation automatique de la progression de votre épisode en cours - Restaurer des données à partir d\'une sauvegarde - Sauvegarde des données - Fichier de sauvegarde chargé - Échec de la restauration des données du fichier %s - Données stockées - Permissions de stockage manquantes. Veuillez réessayer. - Erreur de sauvegarde %s - Recherche - Comptes - Mises à jour et sauvegarde - Info - Recherche avancée - Vous donne les résultats de la recherche séparés par fournisseur - Envoi de données uniquement en cas d\'accident - N\'envoie aucune donnée - Afficher les épisodes spéciaux pour les animés - Montrer les bandes-annonces - Montrer les affiches provenant de Kitsu - Masque la qualité vidéo sélectionnée dans les résultats de recherche - Mises à jour automatiques des plugins - Afficher les mises à jour de l\'application - Recherche automatique de nouvelles mises à jour au démarrage - Mettre à jour vers une version bêta - Recherche pour une mise à jour vers une version bêta au lieu des version complètes seulement - Github - Application Light Novel par les mêmes devs - Anime app by the same devs - Rejoignez le Discord - %d h %d min - %d min - Lire avec CloudStream - Lire en direct - Fin - Historique - hello@world.com - Toutes les langues - Ignorer %s - Ouverture - Récap - Crédits - Intro - Effacer l\'historique - Oui - %d j %d h %d min - Stream - Êtes-vous sûr·e de vouloir quitter \? - Non - Téléchargement de la mise à jour… - L\'épisode %d sera publié dans - Étiquette de qualité - Résolution du lecteur vidéo - Cloner le site - Supprimer le site - Ajoute un clone à un site déjà existant, avec une URL différente - URL du serveur NGINX - MonSiteTresCool - ID invalide - Installer automatiquement les plugins qui sont dans les repository mais qui n\'ont pas encore été installés. - %dm -\nrestant - En direct - Autres - En direct - Vidéo - Étiquette Dub (doublage anglais) - Étiquette sous titre - Titre - Active le nsfw sur les fournisseurs ayant cette option - motdepasse123 - MonNomDutilisateur - Code de la langue (fr) - Créer un compte - Charger depuis internet - Résolution et titre - Lecteur - URL invalide - Erreur - Fermer les - Télécharger automatiquement les plugins - Activer ou désactiver les éléments du poster - Ignorer cette mise-à-jour - Données invalides - Encodage des sous titres - Fournisseurs - Disposition - exemple.com - Titre - Résolution - TS - TC - WP - DVD - Suivant - Blu-ray - %s %d%s - %d-%d - %d %s - NSFW - 127.0.0.1 - %d / 10 - /\?\? - /%d - SD - UHD - HDR - SDR - Cam - HQ - HD - 4K - Web - -30 - @string/anime - @string/ova - NSFW - %s %s - Filtrez par langue préférée - Extras - Lien vers le stream - Référenceur - Installeur APK - +30 - Cam - Supprimer les éléments superflus des sous-titres - Cam - Trailer - Regardez les vidéos dans ces langues - Modifier le style de l\'application pour être assortie à votre appareil - Tous les sous-titres en majuscules - Voir les repository de la communauté - Liste publique - Marquer comme regardé - Url du repository - Finir - Ajouter le repository - Ce que vous voulez voir - Extensions - Impossible de charger %s - 18+ - Plugin Chargé - Plugin Supprimé - Téléchargement par lots - Début du téléchargement %d %s… - Téléchargé %d %s - Tous les %s déjà téléchargés - Télécharger la liste de sites que vous voulez utiliser - Téléchargé : %d - Pistes vidéo - Appliqué au redémarrage - Toutes les extensions ont été désactivé à cause d\'un crash pour vous aider à trouver l\'extension causant le problème. - Mode sans échec activé - Taille - Version - Note : %s - Description - Status - Installer l\'extension d\'abord - Playlist HLS - Lecteur vidéo préféré - VLC - Fin mitigée - Introduction mitigée - Installation de la mise a jour de l\'application… - Impossible d\'installer la nouvelle version de l\'application - Navigateur Web - Certains téléphones ne supporte pas le nouvel installateur d\'application. Essayez l\'option de l\'ancien installateur si les mises-à-jour ne s\'installe pas. - Précédent - Ignorer la configuration - Rapport de crash - Nom du repository - plugin - Supprimer le repository - Désactivé : %d - Non téléchargé : %d - %d plugins mis-à-jour - Télécharger tous les plugins de ce repository \? - %s (Désactivé) - Pistes - Pistes audio - Afficher les infos du crash - Auteurs - Supporté - Lecteur interne - Application non trouvée - Trop de texte. Impossible de sauvegarder dans le presse papier. - MPV - Installateur de paquet - plugins - Cela supprimera également tous les plugins du repository - CloudStream n\'a pas de sites installés par défaut. Vous devez installer les sites à partir de dépôts. -\n -\nEn raison d\'un retrait DMCA irréfléchi de Sky UK Limited 🤮, nous ne pouvons pas lier le site du dépôt dans l\'application. -\n -\nRejoignez notre Discord ou cherchez en ligne. - Langage - Afficher les popups skip pour les intro / fins - Ancienne méthode d\'installation - Web Video Cast - Liens - Gestes - Fonctionnalités du lecteur - Sous-titres - Disposition - Valeurs par défaut - Apparence - Fonctionnalités - Lire la bande-annonce - Mises à jour de l\'application - Sauvegarde - Extensions - Actions - Cache - Refaire le processus d\'installation - Mise à jour commencée - L\'application sera mise à jour dès la fin de la session - Plugin Téléchargé - Retirer de la vue - Bibliothèque - Navigateur - Trier - Note (basse à haute) - Note (haut à bas) - Alphabétique (A à Z) - On dirait que votre bibliothèque est vide :( -\nConnectez-vous à un compte ou ajoutez des séries à votre bibliothèque locale - Il semble que cette liste soit vide, essayez d\'en choisir une autre - Android TV - Trié par - Alphabétique (Z à A) - Sélectionnez la bibliothèque - Ouvrir avec - Mis à jour (Nouveau vers ancien) - Mis à jour (ancien vers nouveau) - Fichier du mode sans échec trouvé ! -\nAucune extension ne sera chargée au démarrage avant que le fichier ne soit enlevé. - Arrêter - Revenir à - Enregistrer - Qualité de visionnage préférée (données mobiles) - Abonné à %s - Démarrer - Test des fournisseurs - Réussi - Désabonné de %s - Redémarrer - Abonné - raw.githubusercontent.com Proxy - Contournements de FAI - L\'épisode %d est sorti ! - Échouer diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 833b76f4..a6ad0af7 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -1,25 +1,27 @@ - रफ्तार (%.2fx) - नया अपडेट आया है! -\n%s -> %s + नया अपडेट आया है!\n%s -> %s + होम खोजें डाउनलोड सेटिंग + डेटा उपलब्ध नही है अन्य ऑप्शन अगला एपिसोड शैलियां शेयर ब्राउज़र में खोलें + देखा जा रहा है होल्ड पर पूरा देखा अधूरा छोड़ा देखने की योजना + मूवी चलाये टोरेंट चलाये सूत्र @@ -27,6 +29,7 @@ वापिस जाए एपिसोड चलाये + डाउनलोड डाउनलोड किया गया डाउनलोड हो रहा @@ -35,12 +38,15 @@ डाउनलोड नही हो पाया डाउनलोड रद्द डाउनलोड पूरा हुआ + लिंक में गड़बड़ है अंदरूनी स्टोरेज + फ़ाइल डिलीट फ़ाइल चलाये डाउनलोड फिर शुरू करे डाउनलोड रोके + स्वचालित त्रुटि रिपोर्ट रोकें और जानकारी छिपाये @@ -52,26 +58,33 @@ लागू करे रद्द करे प्लेयर की रफ्तार + सूत्रों से छांटे प्रकार से छांटे + %d केले दिए गए कोई केले नही दिए गए + रिसेट करने ले किये दबाये रखे देखना जारी रखें + हटाएँ अधिक जानकारी + इस सूत्र को इस्तेमाल करने के लिए एक VPN की ज़रूरत पड़ सकती है यह सूत्र एक torrent है, एक VPN इस्तेमाल करने की सिफारिश की जाती है वर्णन कोई विषय नही मिला कोई वर्णन नही मिला + आपकी वीडियो एक छोटे से डब्बे में चलाता है काले बॉर्डर को हटाता है प्लेयर की subtitle सेटिंग्स प्लेयर में वीडियो की रफ्तार धिमी या तेज़ करता है दाएं या बाएं तरफ स्वाइप करने से वीडियो को आगे पीछे करता है दाएं तरफ या बाएं तरफ स्वाइप करने से रोशिनी और आवाज़ को ऊपर नीचे करता है - दो बार दाएं या बाएं तरफ दबाने से वीडियो को आगे या पीछे करा जा सकता है + दो बार दाएं या बाएं तरफ दबाने से वीडियो को आगे या पीछे करा जा सकता है + खोजें जानकारी नतीजों को सूत्रों के हिसाब से बांटकर दिखता है @@ -84,18 +97,23 @@ Discord से जुड़िये इस प्रोग्राम के निर्माता को केला चढ़ाये केले दे दिए गए + एप्प की भाषा + यह सूत्र क्रोमकास्ट का समर्थन नही करता कोई लिंक नही मिले लिंक को क्लिपबोर्ड पे कॉपी करदिया गया चलाये - असुविधा के लिए खेद है, यह एप्प क्रैश हो गया है । एक गुमनाम रिपोर्ट निर्माताओं को भेज दी गयी है । + असुविधा के लिए खेद है, यह एप्प क्रैश हो गया है । एक गुमनाम रिपोर्ट निर्माताओं को भेज दी गयी है । + + + फ़ाइल डिलीट करें डिलीट रोके वापिस चलाये - एज इस चीज़ को हमेशा के लिए नष्ट कर देगा %s -\nक्या आपका निर्णय निश्चित है \? + एज इस चीज़ को हमेशा के लिए नष्ट कर देगा %s\nक्या आपका निर्णय निश्चित है ? + अभी चालू है खत्म हो गया है स्तिथि @@ -104,14 +122,17 @@ अवधि सूत्र Synopsis + खाली इस्तेमाल में एप्प + मूवी टीवी सीरियल कार्टून अनिमे टोरेंट + क्रोमकास्ट एपिसोड कक्रोमकास्ट मिरर एप्प मैं चलाये @@ -121,30 +142,29 @@ डाउनलोड करे मिरर डाउनलोड लिंक फिरसे लोड करें + कोई अपडेट नही मिला अपडेट के लिए खोजे + ताला आकार सूत्र OP स्किप करें + फिरसे ना दिखाए अपडेट पसंदीदा देखने की क्वालिटी ISP ब्लॉक से छुटकारा पाएं सूत्र की भाषाएं ऐप का लेआउट - पसंदीदा मीडिया - डाउनलोड करने का मार्ग + पसंदीदा मीडिया + + डाउनलोड करने का मार्ग Dubbed या Subbed Anime दिखाये + टीवी लेआउट फ़ोन लेआउट + मुख्य रंग ऐप का रंग - देखा हुआ चिह्नित करें - इतिहास - भाग %d जारी होगा - %dd %dh %dm - %dh %dm - %dm - विज्ञापन diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index b4931377..2a7bff1c 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -1,4 +1,3 @@ - @@ -18,30 +17,34 @@ %dd %dh %dm %dh %dm %dm + Poster - Poster + @string/result_poster_img_des Episode Poster Main Poster Next Random Go back Change Provider Preview Background + Brzina (%.2fx) Ocijenjeno: %.1f - Pronađeno novo ažuriranje! -\n%s -> %s + Pronađeno novo ažuriranje!\n%s -> %s Umetak %d min + CloudStream Otvori s CloudStream-om Početna stranica Pretraži Preuzimanja Postavke - Pretraži… + + Pretraži... Pretraži %s… + Nema podataka Više postavki Sljedeća epizoda @@ -50,6 +53,7 @@ Otvori u pregledniku Preskoči učitavanje Učitavanje... + Gledam Na čekanju Dovršeno @@ -57,6 +61,7 @@ Planiram pogledati Ništa Ponovno gledam + Pokreni Film Pokreni LiveStream Pokreni Torrent @@ -66,6 +71,7 @@ Idi natrag Pokreni Epizodu + Preuzmi Preuzeto Trenutno preuzimam @@ -75,14 +81,18 @@ Preuzimanje otkazano Preuzimanje dovršeno Stream + Pogreška pri učitavanju veza Unutarnja pohrana + Dub Sub + Izbriši datoteku Otvori datoteku Nastavi preuzimanje Pauziraj preuzimanje + Onemogući automatsko izvješćivanje o bugovima Više informacija Sakrij @@ -98,7 +108,9 @@ Zatvori Očisti Sačuvaj + Brzina playera + Postavke titlova Boja teksta Boja obruba @@ -108,26 +120,35 @@ Visina titlova Font Veličina fonta + Pretraži s uslugama Pretraži s tipovima + %d banana dano developerima Nisi dao ni jednu bananu + Automatski odabir jezika Preuzmi jezike Jezik titlova Držite za vraćanje na zadane postavke Uvezi fontove tako da ih postavite u %s - Nastavite s gledanjem + Nastavi gledati + Makni Više informacija - @string/home_play + @string/home_play + Za ispravan rad ovog pružatelja usluga može biti potreban VPN Ovaj pružatelj usluga je torrent, preporučuje se VPN + Stranica ne daje metapodatke, učitavanje videozapisa neće uspjeti ako ne postoji na stranici. + Opis Plot nije pronađen Opis nije pronađen - Prikaži LogMačku 🐈 + + Pokaži logmačku 🐈 + Picture-in-picture Nastavlja reprodukciju u minijaturnom playeru povrh drugih aplikacija Gumb za promjenu veličine playera @@ -136,33 +157,44 @@ Postavke titlova playera Chromecast Titlovi Postavke Chromecast titlova + Eigengravy način Dodaje opciju brzine u playeru Prijeđi prstom za traženje - Prijeđite prstom ulijevo ili udesno kako biste kontrolirali player + Prijeđi prstom ulijevo ili udesno za kontrolu vremena u videoplayeru Klizni za promjenu postavki - Kliznite prstom ulijevo ili udesno za promjenu svjetline ili glasnoće + Prijeđi prstom ulijevo ili udesno za promjenu svjetline ili glasnoće + Automatski započni sljedeću epizodu Započne sljedeću epizodu kad trenutna završi + Dodirni dvaput za traženje Dodirni dvaput za pauziranje - Iznos preskakanja u playeru (Sekunde) - Dvaput dodirni desnu ili lijevu stranu ekrana za pomicanje naprijed ili natrag - Dodirnite dvaput u sredinu zaslona za pauziranje + Iznos traženja u playeru + Dvaput dodirni desnu ili lijevu stranu ekrana za pomicanje naprijed ili natrag + + Dodirni u sredini za pauziranje Koristi svijetlinu u sustavu - Koristi svjetlinu sustava u playeru aplikacija umjesto tamnog preklopa + Koristi svjetlinu sustava u playeru aplikacija umjesto tamnog + preklopa + + Ažuriraj napredak gledanja Automatski sinkroniziraj svoj trenutni napredak u epizodi + Vraćanje podataka iz sigurnosne kopije - Sigurnosno kopiranje podataka + + Sigurnosna kopija podataka Učitana datoteka sigurnosne kopije Vraćanje podataka iz datoteke nije uspjelo %s - Podaci pohranjeni - Nedostaju dozvole za pohranu, pokušaj ponovo. + Uspješno pohranjeni podaci + Nedostaju dozvole za pohranu, pokušaj ponovo Pogreška pri sigurnosnom kopiranju %s + Pretraži Računi Ažuriranja i sigurnosne kopije + Informacije Napredno pretraživanje Daje rezultate pretraživanja odvojene prema pružatelju usluga @@ -170,11 +202,12 @@ Ne šalje podatke Prikaži dodatnu epizodu za anime Prikaži trailere - Prikaži postere iz Kitsua + Prikaži postere iz kitsua Sakrij odabranu kvalitetu videozapisa u rezultatima pretraživanja + Automatsko ažuriranje dodataka Prikaži ažuriranja aplikacije - Automatski traži nova ažuriranja nakon pokretanja aplikacije + Automatski traži nova ažuriranja pri pokretanju aplikacije Ažuriranje na predizdanja Tražite ažuriranja prije izdanja umjesto samo potpunih izdanja Github @@ -183,32 +216,37 @@ Uđi u naš Discord Daj bananu developerima Dana banana + Jezik aplikacije + Ovaj pružatelj usluga nema podršku za Chromecast Nisu pronađene veze Veza je kopirana u međuspremnik Pokreni epizodu Vrati na zadanu vrijednost - Nažalost, aplikacija se srušila. Anonimno izvješće o bugu bit će poslano developerima + Nažalost, aplikacija se srušila. Anonimno izvješće o bugu bit će poslano developerima + + Sezona Nema sezone Epizoda - Epizoda + Epizode %d-%d %d %s S E Nisu pronađene epizode + Izbriši datoteku Izbriši Pauziraj Nastavi -30 +30 - Ovo će trajno izbrisati %s -\nJeste li sigurni\? - %dm -\npreostalo + Ovo će trajno izbrisati %s\nJeste li sigurni? + %dm\npreostalo + + U tijeku Završeno Status @@ -217,12 +255,15 @@ Trajanje Stranica Sinopsis + u redu čekanja - Bez titlova + Nema titlova Zadano + Slobodno Iskorišteno Aplikacija + Filmovi TV Serije @@ -235,23 +276,26 @@ Livestreamovi NSFW Ostali + Film Serija Crtić - Anime - OVA + @string/anime + @string/ova Torrent Dokumentarac Azijska drama Livestream NSFW Video + Greška u izvoru Pogreška remote-a Pogreška renderera Neočekivana pogreška playera Pogreška preuzimanja, provjeri dozvole za pohranu + Chromecast epizoda Chromecast mirror Pokreni u aplikaciji @@ -262,45 +306,73 @@ Preuzmi zrcalo Ponovno učitaj poveznice Preuzmi titlove + Oznaka kvalitete Oznaka sinkronizacije Oznaka titlova Naslov Uključi/isključi elemente korisničkog sučelja na posteru + Nije pronađeno ažuriranje Provjeri ažuriranja + Zaključaj Promijeni veličinu Izvor Preskoči OP + Ne prikazuj više Preskoči ovo ažuriranje Ažuriraj Preferirana kvaliteta streama Maksimalni broj znakova u naslovu video playera Rezolucija video playera + Veličina video međuspremnika Duljina video međuspremnika Video predmemorija na disku Očisti predmemoriju videa i slika - Izazvat će nasumična rušenja ako se postavi previsoko. Nemojte mijenjati ako imate malu količinu RAM-a kao što je Android TV ili stari telefon. - Može uzrokovati probleme na sustavima s malo prostora za pohranu kao što su Android TV uređaji ako postavite previsoko. + + Izazvat će nasumična rušenja ako se postavi previsoko. Nemojte mijenjati ako imate malu količinu RAM-a kao što je Android TV ili stari telefon + Može uzrokovati probleme na sustavima s malo prostora za pohranu kao što su Android TV uređaji ako postavite previsoko + DNS preko HTTPS-a Korisno za zaobilaženje blokada ISP-a + Kloniraj web stranicu Ukloni web stranicu - Dodajte klon postojeće web-lokacije s drugim url-om + Dodajt klon postojeće web-lokacije s drugim url-om + Put preuzimanja - NGINX server URL + + Nginx server url + Prikaži sinkronizirani anime ili s titlovima + Prilagodi zaslonu Rastegni Zoom - Obavijest - 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. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. + + Odricanje + 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. + + In case of copyright infringement, please directly contact the responsible parties or the streaming websites. + + The app is purely for educational and personal use. + + CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. + CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or + manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, + user-friendly interface. + + It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. + Općenito Random gumb - Prikazuje gumb na početnoj stranici koji može odabrati nasumični film ili TV seriju s početne stranice + Prikaži random gumb na početnoj stranici Jezici pružatelja usluga Izgled aplikacije Preferirani mediji @@ -308,14 +380,18 @@ Kodiranje titlova Pružatelji usluga Izgled + Auto TV izgled Izgled za telefone Izgled za emulatore + Primarna boja Tema aplikacije Mjesto naslova postera Stavi naslov ispod postera + + lozinka123 MojeCoolIme @@ -324,6 +400,7 @@ MojaCoolStranica primjer.com Šifra jezika (en) + %s %s račun Odjava @@ -336,10 +413,11 @@ Sinkroniziraj Ocijenjeno %d / 10 - /\?\? + /?? /%d - Ovjereno%s - Nije moguće prijaviti se na %s + Autentičnost %s + Provjera autentičnosti nije uspjela %s + Nijedan Normal @@ -351,11 +429,12 @@ Sjena Podignuto Sinkroniziraj titlove - 1000 ms + 1000ms Kašnjenje titlova - Koristi ovo ako su titlovi prikazani %d ms prerano - Koristite ovo ako se titlovi prikazuju %d ms prekasno + Koristi ovo ako su titlovi prikazani %dms prerano + Koristite ovo ako se titlovi prikazuju %dms prekasno Nema kašnjenja titlova + The quick brown fox jumps over the lazy dog + Preporučeno Učitano %s - Učitaj datoteku titlova + Učitaj iz datoteke Učitaj sa interneta Preuzeta datoteka Glavno Podupiranje Pozadina + Izvor Random + Dolazi uskoro… + Cam Cam Cam @@ -381,7 +464,7 @@ HD TS TC - Blu-Ray + BlueRay WP DVD 4K @@ -390,14 +473,15 @@ HDR SDR Web + Slika postera Player Rezolucija i naslov Naslov Rezolucija - ID je nevažeći + Nevažeći ID Nevažeći podaci - URL je nevažeći + Nevažeći URL Greška Ukloni titlove iz titlova Ukloni reklame iz titlova @@ -417,13 +501,13 @@ Ekstenzije Dodaj repository Ime repositorya - URL spremišta (repositorija) + URL Repositorija Dodatak je učitan Dodatak je izbrisan - Nije moguće učitati %s + Učitavanje nije uspjelo %s 18+ - Započelo preuzimanje %d %s… - Preuzeto %d %s + Započelo preuzimanje %d %s + Preuzeto %d %s uspješno Sve %s je već preuzeto Skupno preuzimanje plugin @@ -434,23 +518,22 @@ Preuzeto: %d Onemogućeno: %d Nije preuzeto: %d - CloudStream nema instalirane web stranice prema zadanim postavkama. Morate instalirati stranice iz repozitorija. -\n -\nZbog bezumnog uklanjanja DMCA od strane Sky UK Limited 🤮 ne možemo povezati web mjesto repozitorija u aplikaciji. -\n -\nPridružite se našem Discordu ili tražite online. + Dodaj spremište za instaliranje ekstenzija web mjesta Pregledajte repozitorije zajednice Javni popis Svi titlovi pisani velikim slovima - Preuzeti sve dodatke iz ovog repozitorija\? + + Preuzeti sve dodatke iz ovog repozitorija? %s (Onemogućeno) Zapis Audio zapis Video zapis - Primijeni na ponovnom pokretanju - Sigurnosni način rada omogućen - Sve su ekstenzije isključene zbog rušenja aplikacije kako biste lakše pronašli ono koje uzrokuje probleme. + Primijeni na restart + + Siguran način rada omogućen + Došlo je do nepopravljivog pada i automatski smo onemogućili sva proširenja, tako da možete pronaći i ukloniti proširenje koje uzrokuje probleme. Pogledajte podatke o padu + Ocjena: %s Opis Verzija @@ -459,100 +542,6 @@ Autori Podržano Jezik + HLS Playlista - Automatski instaliraj ekstenzije - Zasluge - Automatski instaliraj sve neinstalirane ekstenzije iz dodanih repozitorija. - Preferirani video player - Interni player - Prvo instalirajte ekstenziju - VLC - MPV - Web Video Cast - Aplikacija nije pronađena - Svi jezici - Previše teksta. Nije moguće spremiti u međuspremnik. - Označi kao gledano - Prikaži skočne prozore za preskakanje za početak/završetak - Da - Preuzimanje ažuriranja aplikacije… - Jeste li sigurni da želite izaći\? - Ne - Instaliranje ažuriranja aplikacije… - Nije moguće instalirati novu verziju aplikacije - Ažurirano %d ekstenzija - Mješoviti početak - Uvod - Linkovi - Pokreni Trailer - Ponovi postupak postavljanja - Neki telefoni ne podržavaju novi program za instaliranje paketa. Isprobaj naslijeđenu opciju ako se ažuriranja ne instaliraju. - Instalator APK-a - Ažuriranja aplikacije - Sigurnosna kopija - Ekstenzije - Radnje - Predmemorija - Geste - Značajke playera - Titlovi - Izgled - Zadane postavke - Izgled - Značajke - Web preglednik - Preskoči %s - Završetak - Zaključak - Mješoviti završetak - Obriši povijest - Povijest - Legacy - Otvaranje - PackageInstaller - %s %d%s - Aktualiziranje započeto - Program če se aktualizirati tijekom zatvaranja programa - Dodatak preuzet - Ukloni iz pogledanog - Preglednik - Biblioteka - Ocjena (Veća do manje) - Sortiraj prema - Sortiraj - Ocjena (Manja do veće) - Ažurirano (Od novog do starog) - Ažurirano (Od starog do novog) - Abecedno (A do Ž) - Abecedno (Ž do A) - Odaberite biblioteku - Otvori sa - Čini se da vam je biblioteka prazna :( -\nPrijavite se na račun biblioteke ili dodajte serije u svoju lokalnu biblioteku - Čini se da je ova lista prazna, pokušajte se prebaciti na drugu - Pronađena datoteka sigurnog načina rada! -\nNe učitavaju se ekstenzije pri pokretanju dok se datoteka ne ukloni. - Prikazan player- iznos preskakanja - Količina preskakanja koja se koristi kada je player vidljiv - Player skriven - Količina preskakanja - Količina preskakanja koja se koristi kada je player skriven - Android TV - Prošlo - Restart - Log - Početak - Neuspješno - Stop - Test pružatelja usluga - Ažuriram pretplaćene serije - Epizoda %d izbačena! - Pretplaćeno - Pretplaćen na %s - Otkazana pretplata sa %s - Vraćanje - ISP zaobilaznice - raw.githubusercontent.com Proxy - Neuspješno dohvaćanje GitHuba, omogućavanje jsdelivr proxyja. - Koristeći jsdelivr, GitHub blokiranje se može zaobići. Može odgoditi ažuriranja za nekoliko dana. - Preferirana kvaliteta gledanja (podatkovna mobilna mreža) diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml deleted file mode 100644 index 1389dff0..00000000 --- a/app/src/main/res/values-hu/strings.xml +++ /dev/null @@ -1,278 +0,0 @@ - - - Stáblista: %s - %dn %dó%dp - %dó%dp - %dp - Epizód Plakát - Fő Plakát - Vissza - Szolgáltató Váltás - Sebesség (%.2fx) - Értékelés: %.1f - Új frissítés található! -\n%s -> %s - %d perc - %sEp%d - CloudStream - Háttér Előnézet - Letöltés sikertelen - Letöltés - Keresés - Törlés - Szüneteltetés - sorba állítva - Igazítás - Kitöltés - Nagyítás - Összes - Zeneszámok - Következő véletlenszerű - Kijelentkezés - Bejelentkezés - Semmi - Cím - Betöltés… - Állapot - Forrás címe - Előzmények - Eigengravy mód - Felirat magassága - Letöltés elkezdve - Nem található cselekmény - Lejátszás folytatása - Filler - Lejátszás CloudStreamel - Kezdőlap - Keresés - Letöltések - Beállítások - Keresés… - Keresés %s… - Nincs adat - Több opció - Következő epizód - Műfajok - Megosztás - Megnyitás böngészőben - Betöltés kihagyása - Poster - @string/result_poster_img_des - Nézés - Befejezve - Később megnézés - Nincs - Újranézés - Film lejátszása - Előzetes lejátszása - Élőadás lejátszása - Torrent streamelése - Források - Feliratok - Vissza - Letöltés kész - Stream - Hiba a linkek betöltésekor - Belső tárhely - Dub - Sub - Fájl törlése - Fájl lejátszása - Letöltés folytatása - Letöltés szüneteltetése - Automatikus hibajelentés kikapcsolása - Több információ - Elrejtés - Lejátszás - Információ - Könyvjelző szűrése - Könyvjelzők - Eltávolítás - Megtekintés állapotának beállítása - Alkalmazás - Mégse - Másolás - Bezárás - Törlés - Mentés - Lejátszás sebessége - Felirat beállítások - Szövegszín - Körvonal szín - Háttér szín - Ablak szín - Edge típus - Betűtípus - Keresés típusok szerint - Keresés szolgáltatók szerint - %d Banán a fejlesztőknek - Nyelvek letöltése - Tartsa lenyomva az alapértelmezett érték visszaállításához - Betűtípusok importálása %s - Eltávolítás - Több információ - @string/home_play - VPN szükséges lehet ehhez a szolgáltató megfelelő működéséhez - Ez a szolgáltató torrent, VPN ajánlott - Leírás - Nem található leírás - Logcat megjelenítése 🐈 - Kép-a-képben - Folytatja a lejátszást egy miniatűr lejátszóban, más alkalmazások mellett - Lejátszó átméretezése gomb - Fekete szegélyek eltávolítása - Feliratok - Lejátszó feliratok beállításai - Chromecast Feliratok - Chromecast feliratok beállításai - Sebességbeállítást ad hozzá a lejátszóhoz - Epizód lejátszása - Letöltve - Letöltés szüneteltetve - Letöltés megszakítva - Letöltés - Betűtípus mérete - Felirat nyelve - A metaadatokat a webhely nem biztosítja, a videó betöltése sikertelen lesz, ha nem létezik a webhelyen. - Nyelv automatikus kiválasztása - Frissítések és biztonsági mentés - Speciális keresés - Filler epizódok mutatása animéknél - Előzetesek megjelenítése - Telepítési folyamat megismétlése - Anime applikáció ugyanazoktól a fejlesztőktől - Light novel applikáció ugyanazoktól a fejlesztőktől - Alkalmazás nyelve - Epizód - Epizódok - Év - Film - %d-%d - %d %s - Ep - Nem található epizód - Fájl törlése - %dp -\nhátra - Időtartam - Elérhető - Használatban - Alkalmazás - Befejezve - Értékelés - Rajzfilmek - Élőadások - Alapértelmezett - Filmek - TV sorozat - Anime - Torrentek - Ázsiai drámák - Dokumentumfilmek - OVA - Egyebek - Sorozat - @string/anime - Forráshiba - NSFW - Rajzfilm - @string/ova - Élőadás - NSFW - Videó - Torrent - Dokumentumfilm - Ázsiai dráma - Linkek újratöltése - Link másolás - Link letöltés - Automatikus letöltés - Adatok eltárolva - Hiba a biztonsági mentés során %s - Fiókok - Szolgáltatás szerinti keresés eredmények - Nem küld adatokat - Poszterek megjelenítése Kitsu-ról - Kiválasztott videóminőségek elrejtése keresési eredményekbe - Automatikus bővítményfrissítések - Bővítmények automatikus letöltése - Automatikusan telepíti az összes még nem telepített bővítményt a hozzáadott tárolókból. - Alkalmazás frissítések megjelenítése - Automatikusan keressen új frissítéseket indításkor - Frissítés az előzetes kiadásokhoz (prerelease) - Csak előzetesen kiadott frissítések (prerelease) keresése a teljes kiadások helyett - Github - Csatlakozz a Discordhoz - Adj Banánt a fejlesztőknek - Ez a szolgáltató nem támogatja a Chromecast-ot - Nem található link - A link vágólapra másolva - Epizód lejátszása - Alapértelmezett értékre visszaállítása - Az alkalmazás összeomlott. Egy névtelen hibabejelentés küldünk a fejlesztőknek - Évad - %s %d%s - Nincs évad - +30 - Ezzel véglegesen törli a %s -\nBiztosan törli\? - Folyamatban levő - Év - Webhely - Szinopszis - Nincsenek feliratok - Távoli hiba - Render hiba - Váratlan lejátszó hiba - Letöltés hiba, ellenőrízze a tárolási engedélyeket - Chromecast epizód - Chromecast link - Lejátszás az alkalmazásban - Lejátszás %s - Lejátszás böngészőben - Feliratok letöltése - Újracsatlakozás… - Swipe balra vagy jobbra a videólejátszóban az idő vezérléséhez - Csúsztassa ujját a beállítások módosításához - Csúsztassa az újját bal vagy jobb oldalon a fényerő vagy hangerő megváltoztatásához - Biztonsági mentés - 0 Banán a fejlesztőknek - Swipe to seek - Következő epizód automatikus lejátszása - Következő epizód lejátszása amikor az aktuális epizód véget ér - Dupla koppintás to seek - Dupla koppintás a szüneteltetéshez - Player seek amount - Koppintson kétszer a jobb vagy bal oldalra az előre vagy hátra ugráshoz - Koppintson középre a szüneteltetéshez - Rendszer fényerejének használata - Rendszer fényerejének használata az appban a sötét átfedés helyett - Előrehaladás frissítése - Automatikusan szinkronizálja az aktuális epizód előrehaladását - Adatok visszaállítása a biztonsági mentésből - Biztonsági mentés betöltve - Információ - Folytatás - -30 - Frissítés elkezdődött - Nem sikerült visszaállítani az adatok a fájlból %s - Tárolási engedélyek hiányoznak. Kérjük próbálja újra. - Csak összeomlásokról küld adatokat - APK Telepítő - Egyes telefonok nem támogatják az új csomag telepítőt. Ha a frissítések nem települnek, próbálja meg a régi opciót. - Banán adva - A felhasználói felület elemeinek bekapcsolása a poszteren - Nem található frissítés - Frissítés keresése - Zárolás - Átméretezés - Forrás - Bevezető intro átugrása - Ne mutasd újra - Az %d epizód ekkor jelenik meg: - Szüneteltetve - Elvetve - Minőségi jelzés - Szinkroncímke - Alcímke - diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 02234c49..31b84bfd 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -1,32 +1,36 @@ - + %s Ep %d Pemeran: %s + Poster - Poster + @string/result_poster_img_des Episode Poster Main Poster Next Random Go back Change Provider Preview Background + Kecepatan (%.2fx) Dinilai: %.1f - Update baru ditemukan! -\n%s -> %s + Update baru ditemukan!\n%s -> %s Filler %d mnt + CloudStream Beranda Cari Download Pengaturan + Cari… Cari %s… + Tidak Ada Data Opsi Lanjutan Episode selanjutnya @@ -35,13 +39,15 @@ Buka Di Browser Skip Loading Loading… + Sedang Menonton - Tertunda + Tertahan Selesai Dihentikan Rencana untuk Menonton Tidak Ada Menonton Ulang + Putar Movie Streaming Torrent Sumber @@ -50,6 +56,7 @@ Kembali Putar Episode + Download Terdownload Sedang Didownload @@ -58,14 +65,18 @@ Download Gagal Download Dibatalkan Download Selesai + Error Memuat Tautan Penyimpanan Internal + Dub Sub + Hapus File Putar File Lanjutkan Download Jeda Download + Matikan pelaporan bug otomatis Lebih banyak info Sembunyikan @@ -81,7 +92,9 @@ Tutup Bersihkan Simpan + Kecepatan Pemutar + Pengaturan Subtitle Warna Teks Warna Garis Luar @@ -91,25 +104,34 @@ Ketinggian Subtitle Font Ukuran Font + Cari menggunakan provider Cari menggunakan jenis + %d Piseng yang diberikan kepada developer Tidak ada piseng yang diberikan + Pilih Bahasa Otomatis Download Bahasa Tahan untuk mengatur ulang ke default Impor font dengan meletakkan mereka di %s Lanjutkan Menonton + Hapus Info lebih lanjut - @string/home_play + @string/home_play + Sebuah VPN mungkin diperlukan agar provider ini bisa bekerja dengan benar Provider ini adalah sebuah torrent, VPN direkomendasikan + Metadata tidak disediakan oleh situs, loading video akan gagal jika tidak ada di situs. + Deskripsi Tidak Ada Plot Yang Ditemukan Tidak Ada Deskripsi Yang Ditemukan - Tampilkan Logcat 🐈 + + Tampilkan logcat 🐈 + Picture-in-picture Melanjutkan pemutaran di pemutar mini di atas aplikasi lain Tombol mengubah ukuran pemutar @@ -118,31 +140,39 @@ Pengaturan subtitle pemutar Subtitle Chromecast Pengaturan subtitle Chromecast + Mode Eigengravy Menambahkan opsi kecepatan di pemutar Geser untuk mengubah waktu - Geser dari sisi ke sisi untuk mengontrol posisi dalam video + Geser ke kiri atau kanan untuk mengontrol waktu di pemutar video Geser untuk mengubah pengaturan - Geser ke atas atau ke bawah di sisi kiri atau kanan untuk mengubah kecerahan atau volume + Geser ke sisi kiri atau kanan untuk mengubah pencerahan atau volume Tekan dua kali untuk mengubah waktu Tekan dua kali untuk menjeda - Jumlah pengubah waktu pemutar (Detik) - Tekan dua kali di sisi kanan atau kiri untuk mengubah waktu ke depan atau ke belakang - Tekan dua kali di tengah untuk menjeda + Jumlah pengubah waktu pemutar + Tekan dua kali di sisi kanan atau kiri untuk mengubah waktu ke depan atau ke belakang + + Tekan di tengah untuk menjeda Gunakan pencerahan sistem - Gunakan pencerahan sistem di pemutar aplikasi dari pada hamparan gelap + Gunakan pencerahan sistem di pemutar aplikasi dari pada hamparan gelap + + Update progres tontonan Sinkronisasikan progres episode saat ini secara otomatis + Pulihkan data dari cadangan + Cadangkan data File cadangan termuat Gagal untuk memulihkan data dari file %s - Data tersimpan - Izin penyimpanan tidak ditemukan, mohon coba lagi. + Data berhasil disimpan + Izin penyimpanan tidak ditemukan, mohon coba lagi Error saat mencadang %s + Cari Kredit dan akun Update dan cadangan + Info Pencarian Lanjutan Memberikan hasil pencarian yang dipisahkan berdasarkan provider @@ -150,7 +180,7 @@ Tidak mengirim data Tampilkan episode filler untuk anime Tampilkan update aplikasi - Secara otomatis mencari update terbaru setelah aplikasi dibuka. + Secara otomatis mencari update terbaru saat aplikasi dibuka Update ke prarilis Hanya mencari update prarilis daripada rilis penuh Github @@ -159,13 +189,17 @@ Bergabung dengan Discord Berikan sebuah piseng kepada developer Piseng yang diberikan + Bahasa Aplikasi + Provider ini tidak memiliki dukungan Chromecast Tidak Ada Tautan Yang Ditemukan Tautan disalin ke papan klip Putar Episode Ulang ke pengaturan default - Maaf, aplikasi ngecrash. Laporan bug anonim akan dikirim ke developer + Maaf, aplikasi ngecrash. Laporan bug anonim akan dikirim ke developer + + Season Tidak Ada Season Episode @@ -173,16 +207,17 @@ S E Episode Tidak Ditemukan + Hapus File Hapus Jeda Lanjutkan -30 +30 - Ini akan secara permanen menghapus %s -\nApakah anda yakin\? - %dm -\ntersisa + Ini akan secara permanen menghapus %s\nApakah anda yakin? + %dm\ntersisa + + Masih Berlanjut Tamat Status @@ -191,12 +226,15 @@ Durasi Situs Sinopsis + antri Tidak Ada Subtitle Default + Bebas Terpakai Aplikasi + Movie Seri TV @@ -206,20 +244,23 @@ Film Dokumenter OVA Drama Asia + Movie Seri Kartun - Anime - OVA + @string/anime + @string/ova Torrent Film Dokumenter Drama Asia + Error sumber Error jarak jauh Error perender Error pemutar yang tidak terduga Error mendownload, cek izin penyimpanan + Episode Chromecast Mirror Chromecast Putar di aplikasi @@ -230,54 +271,85 @@ Download mirror Muat ulang tautan Download subtitle + Label kualitas Label dub Label sub Judul Aktifkan atau nonaktifkan elemen UI pada poster + Tidak Ada Update Yang Ditemukan Cek Untuk Update + Kunci Ubah Ukuran Sumber Skip OP + Jangan tunjukkan lagi Skip Update ini Update - Kualitas tontonan yang lebih diinginkan (WIFI) + Kualitas tontonan yang lebih diinginkan Karakter maksimal judul pemutar video Resolusi pemutar video + Ukuran buffer video Panjang buffer video Cache video di disk Membersihkan cache video dan gambar - Dapat menyebabkan crash jika disetel terlalu tinggi. Jangan diubah jika perangkat anda memiliki jumlah RAM yang rendah, seperti TV Android atau ponsel lama. - Dapat bermasalah pada sistem dengan ruang penyimpanan rendah, seperti Android TV, jika diatur terlalu tinggi. + + Akan menyebabkan crash acak jika disetel terlalu tinggi. Jangan diubah jika anda memiliki jumlah ram yang rendah seperti TV Android atau ponsel lama + Dapat menyebabkan masalah pada sistem dengan ruang penyimpanan yang rendah seperti perangkat Android TV jika anda menyetelnya terlalu tinggi + DNS melalui HTTPS Berguna untuk melewati blok ISP + Jalur download - NGINX server URL + + Url server Nginx + Tampilkan Anime Dub/Sub + Paskan dengan layar Regang Zoom + Peringatan - 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. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. + 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. + + In case of copyright infringement, please directly contact the responsible parties or the streaming websites. + + The app is purely for educational and personal use. + + CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. + CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or + manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, + user-friendly interface. + + It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. + Umum Tombol Acak - Tampilkan tombol di halaman utama yang dapat memilih seri film atau TV acak dari halaman utama + Tampilkan tombol acak di Beranda Bahasa provider Tata Letak Aplikasi Media yang lebih diinginkan Antarmuka pengguna + Otomatis Tata letak TV Tata letak HP Tata letak emulator + Warna primer Tema aplikasi Lokasi judul poster Meletakkan judul di bawah poster + + %s %s akun - Keluar - Masuk + Logout + Login Ganti akun Tambah akun Tambah pelacak @@ -298,10 +370,11 @@ Sinkronisasi Skor %d / 10 - /\?\? + /?? /%d - %s terautentikasi - Gagal masuk ke %s + %s Diautentikasikan + Gagal untuk mengautentikasi ke %s + Tidak Ada Normal @@ -313,11 +386,12 @@ Bayangan Menonjol Sinkronisasi sub - 1000 ms + 1000ms Penundaan subtitle - Gunakan ini jika subtitle ditampilkan %d ms terlalu cepat - Gunakan ini jika subtitle ditampilkan %d ms terlalu lambat + Gunakan ini jika subtitle ditampilkan %dms terlalu cepat + Gunakan ini jika subtitle ditampilkan %dms terlalu lambat Tidak ada penundaan subtitle + The quick brown fox jumps over the lazy dog + Direkomendasikan Memuat %s Muat dari file @@ -332,9 +407,12 @@ Utama Pendukung Latar Belakang + Sumber Acak + Segera hadir… + Cam Cam Cam @@ -342,7 +420,7 @@ HD TS TC - Blu-ray + BlueRay WP DVD 4K @@ -351,206 +429,10 @@ HDR SDR Web + Gambar Poster Pemutar Resolusi dan judul Judul Resolusi - %dh %dm - %dm - Episode %d akan dirilis tanggal - %dd %dh %dm - Pembaruan otomatis plugin - Unduh plugins secara otomatis - Tampilkan poster dari Kitsu - Pasang otomatis semua plugins yang belum terpasang, dari repositori yang telah ditambahkan. - Siaran langsung - 17+ - password123 - Penyedia - Cache - Fitur Pemutar - Bahasa - Tampilan - Bawaan - Tampilan - Fitur - Tampilkan konten 17+ - Putar Cuplikan - Putar Siaran - Siaran - Bahasa Subtitel - Putar otomatis episode selanjutnya - Putar episode selanjutnya, setelah ini berakhir - Tampilkan cuplikan - Ulang proses pemasangan - %d-%d - %d %s - 17+ - Lainnya - Video - Duplikasi Website - Duplikasi website yang telah ada, dengan alamat berbeda - Tautan - Perbarui Aplikasi - Cadangkan - Fitur Tambahan - Putar di CloudStream - Sembunyikan kualitas video terpilih di pencarian - %s %d%s - Siaran langsung - Hapus Website - UsernameKeren - contoh@email.com - 127.0.0.1 - Websiteku - contoh.com - Ekstra - Apa yang ingin anda lihat - Plugin terhapus - %d plugin diperbarui - Lihat Repositori dari Group - List Umum - CloudStream tidak memiliki sumber video secara bawaan. Kamu harus menginstall dari repositori. -\n -\nKarena banyak laporan dari banyak pihak berwajib, kami tidak dapat memberikannya secara langsung. -\n -\nGabung dengan group Discord atau cari di internet. - Alamat Repositori - Buat Akun - Error - Urutkan berdasarkan bahasa terpilih - Cuplikan - Plugin terpasang - Plugin %s gagal diambil - 18+ - Nama Repositori - plugin - Semua %s telah terunduh - Unduh bersamaan - plugins - Tidak aktif: %d - Ganti subtitle jadi huruf besar semua - Terunduh: %d - Tidak terunduh: %d - Unduh semua plugin dari repositori ini\? - Semua Umur - %s (Tidak aktif) - Trek - Terapkan saat dimuat ulang - Keterangan - Versi - Status - Lihat log crash - Peringkat: %s - Pembuat - Bahasa - Pemutar video utama - Pemutar Bawaan - VLC - MPV - Terunduh %d %s - Memulai mengunduh %d %s… - Semua fitur tambahkan dimatikan karena crash, untuk memudahkanmu mencari penyebab crash. - Kode bahasa (en) - Ambil dari internet - Putar video di bahasa ini - Tambah Repositori - Pilih ini untuk menghapus semua repositori plugin - Lewati pengaturan - Alamat salah - Alamat streaming - Selanjutnya - Sebelumnya - Ubah tampilan aplikasi - Laporkan Crash - Selesai - Fitur Tambahan - Hapus Repositori - Unduh semua situs yang ingin digunakan - Ukuran - ID salah - Riwayat - Telah ditonton - Data salah - Hapus teks tertutup dari subtitel - Hapus karakter sampah dari subtitel - Audio Trek - Video Trek - Dukungan - Daftar putar HLS - Penginstal APK - Teknik menampilkan subtitel - Gerakan - Beberapa perangkat tidak mendukung penginstal paket mode baru. Coba mode lama jika pembaruan tidak dapat diinstal. - Aksi - Referer - Ya - Pasang dulu fitur tambahan - Semua Bahasa - Lewati %s - Pembuka - Sesi Akhir - Sinopsis - Sesi akhir ganda - Sesi Kredit - Sesi Intro - Terlalu banyak karakter, tidak dapat menyimpan di clipboard. - Yakin ingin keluar\? - Tidak - Memasang pembaruan… - Tidak dapat memasang versi terbaru - Web browser - Aplikasi tidak ditemukan - Web Video Cast - Hapus Riwayat - Tampilkan popup untuk skip sesi pembuka/akhir - Mengunduh pembaruan… - Versi lama - Sesi pembuka ganda - Penginstal Paket - Plugin Terunduh - Aplikasi akan diperbaharui pada saat keluar - Pembaharuan Dimulai - Hapus dari tontonan - Browser - Pilih pustaka - Yahh daftar pustaka kamu kosong :( -\nMasuk ke akun pustaka atau tambah perlihatkan ke lokal pustaka kamu - Pustaka - Urutkan berdasar - Urutkan - Peringkat (Rendah ke Tinggi) - Update (Lama ke Terbaru) - Peringkat (Tinggi ke Rendah) - Update (Terbaru ke Lama) - Abjad (A ke Z) - Abjad (Z ke A) - Buka dengan - Yahh daftar ini kosong, coba ganti ke yang lain - Mode aman file ditemukan! -\nTidak memuat ekstensi pada startup sampai berkas dihapus. - Sembunyikan Pemutaran - Geser - Pemutar terlihat - Geser - Geser untuk menghilangkan - Geser untuk menghilangkan - Android TV - Log - Berhasil - Tes provider - Berhenti - Mulai - Mulai lagi - Gagal - Memperbarui acara langganan - Berlangganan - Berlangganan ke %s - Berhenti berlangganan di %s - Episode %d telah rilis! - raw.githubusercontent.com Proksi - Gagal mencapai GitHub, mengaktifkan proksi jsdelivr. - Mengunakan jsdelivers, bisa melewati pemblokiran GitHub. Mungkin dapat menyebabkan pembaruan tertunda dalam beberapa hari. - Bypass ISP - Pulihkan - Nonton dengan kualitas yang di inginkan (Data Seluler) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index eca60da1..86206213 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1,37 +1,40 @@ - %s Ep %d Cast: %s L\'episodio %d uscirà in - %d g %d o %d min - %d o %d min - %d min + %dg %do %dm + %do %dm + %dm + Poster - Poster - Poster episodio - Poster principale - Prossimo casuale - Torna indietro - Cambia provider - Anteprima sfondo + @string/result_poster_img_des + Episode Poster + Main Poster + Next Random + Go back + Change Provider + Preview Background + Velocità (%.2fx) Valutato: %.1f - Nuovo aggiornamento trovato! -\n%s -> %s + Nuovo aggiornamento trovato!\n%s -> %s Filler %d min + Riproduci con CloudStream Home Cerca Scaricati Impostazioni + Cerca… Cerca in %s… + Nessun dato Più impostazioni Prossimo episodio @@ -40,6 +43,7 @@ Apri nel browser Salta caricamento Caricamento… + Guardando In attesa Completato @@ -47,6 +51,7 @@ Da guardare Nessuno Riguardando + Riproduci film Riproduci Livestream Stream Torrent @@ -55,6 +60,7 @@ Riprova connesisone… Indietro Riproduci episodio + Scarica Scaricato Download in corso @@ -64,14 +70,18 @@ Download cancellato Download completato Stream + Errore durante il caricamento dei link Archiviazione interna - Doppiato - Sottotitolato + + Dub + Sub + Elimina file Riproduci file Riprendi download Ferma download + Disabilita segnalazione automatica di bug Più info Nascondi @@ -87,7 +97,9 @@ Chiudi Cancella Salva + Velocità video + Impostazioni sottotitoli Colore testo Colore contorno @@ -97,26 +109,35 @@ Elevazione sottotitolo Carattere Dimensione + Cerca per provider Cerca per tipo + %d banane date agli sviluppatori Nessuna banana data + Selezione automatica lingua Lingue da scaricare Lingue sottotitoli Tenere premuto per ripristinare le impostazioni predefinite Importa font mettendoli in %s Continua a guardare + Rimuovi Più info @string/home_play + Potrebbe essere necessaria una VPN per far funzionare correttamente questo provider Questo provider è un torrent, si raccomanda una VPN + I metadati non sono forniti dal sito, il caricamento del video fallirà se non esiste sul sito. + Descrizione Nessuna trama trovata Nessuna descrizione trovata + Mostra Logcat 🐈 + Picture-in-Picture Continua la riproduzione in un player in miniatura sopra altre applicazioni Pulsante di ridimensionamento del video @@ -125,14 +146,13 @@ Impostazioni sottotitoli Sottotitoli Chromecast Impostazioni sottotitoli Chromecast + Modalità Eigengravy Aggiungi opzione velocità nel player Scorri per mandare avanti/indietro Scorri a sinistra o a destra per controllare il tempo del video Scorri per cambiare le impostazioni Passa il dito sul lato sinistro o destro per cambiare la luminosità o il volume - Riproduci automaticamente l\'episodio successivo - Avvia l\'episodio successivo al termine di quello in corso Doppio tocco per andare avanti/indietro Doppio tocco per mettere in pausa Tocca due volte il lato destro o sinistro dello schermo per mandare avanti o indietro il video @@ -140,18 +160,23 @@ Player seek Utilizzare la luminosità del sistema Utilizzare la luminosità del sistema al posto di una sovrapposizione scura + Sincronizza automaticamente gli episodi guardati + Ripristinare i dati da backup + Backup data File di backup caricato Impossibile ripristinare i dati dal file %s - Dati salvati - Permessi di archiviazione mancanti. Per favore riprova. + Dati salvati con successo + Permessi di archiviazione mancanti, riprova Errore nel backup %s + Cerca Accounts Aggiornamenti e Backup + Info Ricerca avanzata Dividi i risultati della ricerca per provider @@ -160,8 +185,7 @@ Mostra tag [filler] per anime Mostra trailer Mostra poster da Kitsu - Nascondi la qualità video selezionata dai risultati di ricerca - Aggiorna automaticamente i plugin + Mostra gli aggiornamenti dell\'app Cerca automaticamente nuovi aggiornamenti all\'avvio Aggiorna alle prerelease @@ -172,33 +196,33 @@ Entra in Discord Dai una banana agli sviluppatori Dai una banana + Lingua app + Questo provider non ha il supporto per Chromecast Nessun link trovato Link copiato negli appunti Riproduci episodio Ripristina il valore predefinito Spiacente, l\'applicazione è andata in crash. Una segnalazione anonima di bug sarà inviata agli sviluppatori + Stagione - %s %d%s Nessuna stagione Episodio Episodi - %d-%d - %d %s S E Nessun episodio trovato + Elimina file Elimina Pausa Riprendi -30 +30 - Stai per eliminare permanentemente %s -\nSei sicuro\? - %dm -\nrimanenti + Stai per eliminare permanentemente %s\nSei sicuro? + %dm\nrimanenti + In corso Completato Stato @@ -207,12 +231,15 @@ Durata Sito Sinossi + In coda Nessun sottotiolo Default + Libero Usato App + Film Serie TV @@ -223,8 +250,7 @@ Documentari Drama Asiatici Livestreams - NSFW - Altri + Film Serie TV @@ -235,100 +261,116 @@ Documentario Drama Asiatico Livestream - NSFW - Altro + Source error - Errore remoto - Errore del renderer + Remote error + Renderer error Errore inaspettato nel player video Errore download, controlla i permessi di archiviazione + Chromecast - Mirror Chromecast + Chromecast mirror Riproduci in app Riproduci in %s Riproduci nel browser Copia link Download - Mirror download + Download mirror Aggiorna link Download sottotitoli + Etichetta Qualità Etichetta Dub Etichetta Sub Titolo Etichette sui Poster + Nessun aggiornamento trovato Controlla aggiornamenti + Blocca Ridimensiona Sorgente Salta intro + Non mostrare di nuovo Salta questo aggiornamento Aggiorna - Qualità di visualizzazione preferita (WiFi) + Risoluzione preferita Limita i caratteri del titolo nel player Risoluzione video player + Dimensione cache video Lunghezza buffer video Dimensione cache video su disco Cancella cache immagini e video - Causa arresti anomali se impostato troppo alto sui dispositivi con poca memoria, come Android TV. - Se impostato troppo alto può causare problemi su sistemi con poca archiviazione interna, come i dispositivi Android TV. + + Se impostato troppo alto può causare problemi su sistemi con poca RAM. Come i dispositivi Android TV o vecchi telefoni + Se impostato troppo alto può causare problemi su sistemi con poca Archiviazione interna. Come i dispositivi Android TV + DNS over HTTPS Utile per bypassare i blocchi ISP + Clona un sito Rimuovi sito - Aggiungi un clone di un sito esistente, con un URL differente + Aggiungi un clone di un sito esistente, con un url differente + Posizione Download - URL server NGINX + + URL Nginx server + Mostra anime doppiati/sottotitolati + Adatta allo schermo Allunga Zoom - Avvertenza + + Disclaimer Generale Random Mostra pulsante Random nella homepage Lingua provider Layout app Media preferito - Abilita NSFW sui provider supportati Encoding Sottotitoli - Provider Interfaccia utente + Auto Layout TV Layout Telefono Layout Emulatore + Colore primario Tema Posizione Titolo sui Poster Titolo sotto il poster + password123 - IlMioUsername + MyCoolUsername hello@world.com 127.0.0.1 - IlMioSito + MyCoolSite example.com Codice lingua (it) + %s %s account - Disconnetti - Accedi + Logout + Login Cambia account Aggiungi account - Crea un account - Aggiungi tracciamento + Crea account + Aggiungi tracking Aggiunto %s - Sincronizza + Sync Valutato %d / 10 - /\?\? + /?? /%d %s autenticato Impossibile autenticarsi a %s + Nessuno Normale @@ -340,10 +382,10 @@ Ombra Rilievo Sincronizza sottotitoli - 1000 ms + 1000ms Ritardo sottotitoli - Usa se i sottotitoli sono mostrati %d ms troppo presto - Usa se i sottotitoli vengono mostrati %d ms troppo tardi + Usa se i sottotitoli sono mostrati %dms troppo presto + Usa se i sottotitoli vengono mostrati %dms troppo tardi Nessun ritardo dei sottotitoli Ma che bel gufo spenzola da quei travi + Consigliato Caricato %s Carica da file @@ -360,9 +403,12 @@ Protagonista Supporto Secondario + Sorgente - Casuale - In Arrivo… + Random + + In Arrivo + Cam Cam Cam @@ -370,7 +416,7 @@ HD TS TC - Blu-ray + BlueRay WP DVD 4K @@ -379,21 +425,23 @@ HDR SDR Web + Poster - Lettore + Player Titolo e Risoluzione Titolo Risoluzione ID non valido Dati non validi - URL non valido + Url non valido Errore - Filtra in base alla lingua preferita - Extra + Trailer Link allo stream + + Referer Prossimo Guarda video in queste lingue @@ -406,13 +454,13 @@ Estensioni Aggiungi repository Nome repository - URL repository + Url repository Plugin caricato Plugin eliminato - Impossibile caricare %s + Caricamento non riuscito %s 18+ - Download iniziato %d %s… - Scaricato %d %s + Download iniziato %d %s + Download %d %s completato Tutti %s già scaricati Download in blocco plugin @@ -423,133 +471,23 @@ Scaricato: %d Disabilitato: %d Non scaricato: %d - Aggiornati %d plugin - CloudStream non ha siti installati per impostazione predefinita. È necessario installare i siti dai repository. -\n -\nA causa di una rimozione DMCA senza cervello da Sky UK Limited 🤮 non possiamo collegare il sito repository nell\'app. -\n -\nUnisciti al nostro Discord o cerca online. + Aggiungi una repository per installare l\'estensione dei siti Vedi le repository della community Lista pubblica Tutti i sottotitoli in maiuscolo - Scaricare tutti i plugin da questa repository\? + + Scaricare tutti i plugin da questa repository? %s (Disabilitato) - Tracce + Tracks Traccia audio Traccia video Applica al riavvio - Safe mode attiva - Tutte le estensioni sono state disabilitate a causa di un arresto anomalo per aiutarti a trovare l\'estensione che causa il problema. + + Safe Mode abilitata + Si è verificato un crash irrecuperabile e abbiamo disabilitato automaticamente tutte le estensioni, in modo che possiate trovare e rimuovere l\'estensione che causa il problema. Vedi informazioni del crash - Voto: %s - Descrizione - Versione - Stato - Dimensione - Autori - Supportati - Lingua - Prima installa l\'estensione - Playlist HLS - Video player preferito - Player interno - VLC - MPV - Cast Web Video - Web browser - App non trovata - Tutte le lingue - Salta %s - - - Riassunto - - - Crediti - - Cancella cronologia - Cronologia - Mostra popup per salta sigla iniziale/finale - Testo troppo lungo. Impossibile salvare negli appunti. - CloudStream - Scarica automaticamente i plugins - Impossibile installare la nuova versione dell\'app - Aggiorna il progresso di avanzamento - Opening misto - Installando l\'aggiornamento dell\'app… - Rimuovi le didascalie chiuse dai sottotitoli - Rimuovi il bloat dai sottotitoli - Opening - Ending - Ending misto - Intro - Segna come visto - Sei sicuro di voler uscire\? - - No - Scaricando l\'aggiornamento dell\'app… - Installa automaticamente tutti i plugin non ancora installati dai repository aggiunti. - ProgrammaInstallazionePacchetti - Alcuni telefoni non supportano il nuovo programma di installazione dei pacchetti. Prova l\'opzione legacy se gli aggiornamenti non vengono installati. - Installer APK - Legacy - Riproduci trailer - Ripeti il processo di configurazione - Link - Aggiornamenti app - Backup - Estensioni - Azioni - Cache - Gesti - Funzionalità lettore - Sottotitoli - Disposizione - Predefiniti - Aspetto - Funzionalità - L\'app verrà aggiornata all\'uscita - Aggiornamento avviato - Plugin scaricato - Rimuovi dai già visti - Browser - Ordina per - Punteggio (Decrescente) - Punteggio (Crescente) - Aggiornato (Da nuovo a vecchio) - Aggiornato (Da vecchio a nuovo) - Alfabetico (A - Z) - Alfabetico (Z - A) - Sembra che la tua libreria sia vuota :( -\nAccedi a un account di libreria o aggiungi degli show alla tua libreria locale - Seleziona libreria - Apri con - Libreria - Ordina - Sembra che questa lista sia vuota, prova a passare a un\'altra - File \"safe mode\" trovato! -\nAll\'avvio non sarà caricata alcuna estensione finchè il file non verrà rimosso. - Quantità di ricerca usata quando il player è nascosto - TV Android - Quantità di ricerca usata quando il player è visibile - Player visibile - Quantità di ricerca - Player nascosto - Quantità di ricerca - Registro - Avvia - Test del provider - Riavvia - Ferma - Superato - Fallito - Proxy raw.githubusercontent.com - Disiscritto da %s - Iscritto - Iscritto a %s - Impossibile contattare GitHub, abilitazione proxy jsdelivr avviata. - Bypassa il blocco di GitHub utilizzando jsdelivr, potrebbe causare un ritardo di alcuni giorni. - Baypass ISP - Ripristina - Aggiornando shows a cui sei iscritto - L\'episodio %d è stato rilasciato! - Qualità di visualizzazione preferita (Dati mobili) + HLS Playlist + + diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml deleted file mode 100644 index b24f0c60..00000000 --- a/app/src/main/res/values-iw/strings.xml +++ /dev/null @@ -1,509 +0,0 @@ - - - הרקע של ההצגה לפני - צוות שחקנים: %s - לחזור - לשנות ספק - מהירות (%.2fx) - דירוג: %.1f - נמצא עדכון חדש! -\n%s -> %s - סינון - %d דקות - קלאודסטרים - בית - חיפוש - הורדות - הגדרות - חיפוש… - חיפוש %s… - ללא נתונים - יותר אפשרויות - ז\'אנרים - שיתוף - לפתוח בדפדפן - דלג על טעינה - פרק %d יצא בעוד - %dימים %dשעות %dחודשים - %dשעות %dחודשים - %dחודשים - פוסטר - הפוסטר של הפרק - פוסטר ראשי - ארקאי הבא - הושלם - בתכנון לצפייה - צופה מחדש - הפעל סרט - הפעל טריילר - הפעל שידור חי - מקורות - נסיון חדש לחיבור… - הפסיק - שדר טורנט - חזרה - נגן פרק - הורד - מוריד - ההורדה מושהית - ההורדה התחילה - ההורדה נכשלה - ההורדה בוטלה - נגן טריילר - להזרים טורנט - שגיאה בטעינת קישורים - אחסון פנימי - עם כתוביות - מחיקת קובץ - נגן קובץ - המשך הורדה - השהה את ההורדה - השבת את דיווח הבאגים האוטומטי - נגן - מידע - סנן סימניות - סימניות - הסר - הגדר מצב צפייה - ליישם - בטל - העתק - לסגור - נקה - לשמור - %s פרק %d - נגן עם קלאודסטרים - פרק הבא - טוען… - צופה - כתוביות - בהמתנה - ללא - להוריד - מדובב - יותר מידע - להתחבא - מהירות הנגן - הגדרות הכתוביות - צבע טקסט - צבע חיצון - סוג קצה - גובה הכתוביות - גודל גופן - חפש באמצעות הספקים - חפש באמצעות סוגים - בחירת שפה אוטומטית - הורדת שפות - שפת הכתוביות - ייבא גופנים בהצבתם ב %s - להמשיך לצפות - להסיר - עוד מידע - הספק הזה הוא טורנט, שימוש ב-VPN הוא מומלץ - תיאור - עלילה לא נמצאה - התיאור לא נמצאה - מצב תמונה בתמונה - כפתור שינוי גודל הנגן - הסר את הגבולות השחורים - %d בנינים שניתנו למפתחים - הגדרות כתוביות הנגן - הגדרות כתוביות כרומקאסט - גופן - החזק כדי לאפס לברירת מחדל - צבע הרקע - ייתכן שיהיה צורך לשימוש ב-VPN כדי שספק זה יפעל באופן נכון - כתוביות כרומקאסט - ממשיך ניגון בנגן מינימלי מעל ישומים אחרים - כתוביות - @string/home_play - היסטוריה - מורשת - לא - כן - סימון כנצפה - סטטוס - התנתק - התחברות - כותרת - אין - הכל - כותרת - העדכון התחיל - מידע - התקן אוטומטית את כל התוספים שטרם הותקנו ממאגרים שנוספו. - חפש אוטומטית עדכונים חדשים בפתיחת האפליקציה - הצג עדכונים לאפליקציה - בצע מחדש את תהליך ההגדרה - עדכן למהדורות מוקדמות - חפש עדכוני מהדרות מוקדמות במקום מהדורות מלאות בלבד - פרקים - %d-%d - פרק - %d %s - עו - פר - לא נמצאו פרקים - מחק קובץ - מחק - עצור - המשך - -30 - +30 - %dדקות -\nנותרו - ‬פעולה זאת תמחק לצמיתות את %s -\nהאם אתם בטוחים\? - מתמשך - משך זמן - דירוג - שנה - ללא כתוביות - ברירת מחדל - חינם - משומש - סדרת טלוויזיה - סדרות/סרטים מצוירים - @string/anime - @string/ova - דרמה אסייתית - כרומקאסט את הפרק - כרומקאסט את המראה - נגן בדפדפן - תווית כתוביות - החלף רכיבי ממשק משתמש בפוסטר - דלג על הפתיח - אל תראה שוב - דלג על עדכון זה - עדכון - איכות צפייה מועדפת - נגן וידאו כותרת מקסימום תווים - רזולוציית נגן וידאו - הוסף מעקב - צור חשבון - הבא - ספריה - מטא-דאטה לא מסופק על ידי האתר, טעינת הסרטון תיכשל אם הוא לא קיים באתר. - החלק על הצד השמאלי או הימני כדי לשנות את הבהירות או עוצמת הקול - שחזור הנתונים מהקובץ נכשל %s - עדכונים וגיבויים - נותן לך את תוצאות החיפוש מופרדות לפי ספק - הצג פרק פילר (לא הכרחי לעלילה) לאנימה - הורדה אוטומטית של תוספים - עדכוני תוספים אוטומטיים - כמות בנינים שניתנו - עונה - מצטערים, האפליקציה קרסה. דוח באג אנונימי יישלח למפתחים - טורנט - NSFW - שגיאת מעבד - הורד מראה - הורד כתוביות - הצג כפתור אקראי בדף הבית - תווית דיבוב - גורם לקריסות אם מוגדר גבוה מדי במכשירים עם זיכרון נמוך, כגון Android TV. - מקור - ספקים - הסר אתר - נתיב הורדה - נראה - אוטומטי - פריסת טלוויזיה - %s %s - סנן לפי שפת מדיה מועדפת - נושא אפליקציה - מיקום כותרת פוסטר - מה אתם רוצים לראות - איכות גבוהה - HD - SDR - תוסף טעון - DVD - עדכוני אפליקציה - 4K - WP - דלג על ההגדרה - תכונות נגן - עדכן התקדמות צפייה - DNS מעל HTTPS - לחץ באמצע כדי לעצור - החלק שמאלה או ימינה כדי לשלוט על זמן בנגן הסרטונים - נגן אוטומטית את הפרק הבא - התחל את הפרק הבא כאשר הפרק הנוכחי נגמר - לחץ פעמיים על צד ימין או שמאל כדי להציץ קדימה או אחורה - השתמש בבהירות המערכת - גבה נתונים - סנכרן את הכתוביות - עיכוב כתוביות - אין עיכוב בכתוביות - מומלץ - נטען %s - השתמש בזה אם הכתוביות מוצגות %d מילישניות מוקדם יותר - אקראי - רזולוציה - איכות מצלמה - כתובת אתר לא מזוהה - קישור לשידור - המפנה - TC - SD - UHD - כתובת אתר למאגר - מדיה מועדפת - /%d - נגן את הפרק - לא נמצאו קישורים - קוד שפה (אנגלית) - שגיאה - שפות ספק - קישורים - פריסת אפליקציה - חיפוש מתקדם - 18+ - פוסטר - איכות מצלמה - החלק כדי להציץ - גיבוי - כמות הצצת הנגן - סרטון - גיטהאב - מצלמה - החלק לשינוי ההגדרות - ‪דפדפן - צבע חלון - הצג לוג - הוסף אפשרות מהירות בנגן - לחץ פעמיים כדי להציץ - לחץ פעמיים כדי לעצור - התשתמש בבהירות המערכת בנגן האפליקציה במקום שכבת-על כהה - סנכרן אוטומטית את התקדמות הפרק הנוכחית שלך - שחזר נתונים מגיבוי - קובץ הגיבוי נטען - נתונים מאוחסנים - חסרות הרשאות אחסון. אנא נסה שוב. - שגיאה בגיבוי %s - חיפוש - חשבונות - לא ניתנו בנינים - מצב איגנגראבי - שולח נתונים רק בקריסות - לא שולח נתונים - הצג טריילרים - הצג פוסטרים מKitsu - הסתר את איכות הסרטון שנבחרה בתוצאות החיפוש - מתקין APK - חלק מהטלפונים אינם תומכים במתקין החבילות החדש. נסו את האפשרות מדור קודם אם העדכונים לא מתקינים. - אפליקצית רומנים קלים על ידי אותו מפתחים - אפליקצית אנימה על ידי אותו מפתחים - הצטרפות לדיסקורד - שפת האפליקציה - לספק זה אין תמיכה בכרומקאסט - הקישור הועתק ללוח - אפס לערך ברירת המחדל - אין עונה - הושלם - אתר - תקציר - בתור - לתת בניני למפתחים - %s %d%s - אפליקציה - סרטים - אנימה - סרטים תיעודיים - אנימציית וידאו מקורית - דרמות אסייתיות - שידורי חי - אחר - סרט - סדרה - סדרה/סרט מצויר - טורנט - דוקומנטרי - שידור חי - NSFW - שגיאת מקור - שגיאה מרחוק - שגיאת נגן לא צפויה - שגיאת הורדה, בדוק הרשאות אחסון - נגן באפליקציה - נגן ב %s - העתק קישור - הורדה אוטומטית - טען מחדש קישורים - תווית איכות - לא נמצא עדכון - בדוק אם יש עדכון - נעל - שינוי גודל - גודל מאגר וידאו - אורך מאגר וידאו - מטמון וידאו בכונן - נקה מטמון וידאו ותמונה - גורם לקריסות אם מוגדר גבוה מדי במכשירים עם מקום נמוך, כגון Android TV. - שימושי עבור עקיפת מחסומי ספק שירותי אינטרנט - אתר העתק - הוסף העתק של אתר קיים, עם כתובת אתר אחרת - NGINX שרת כתובת אתר - הצג אנימות מדובבות/מתורגמות - התאם למסך - מתוח - זום - כתב ויתור - הרחבות - פעולות - מטמון - מחוות - כתוביות - פריסה - ברירות מחדל - תכונות - כללי - כפתור אקראי - הרשה NSFW בספקים נתמכים - קידוד כתוביות - פריסה - פריסת טלפון - פריסת אמולטור - צבע ראשוני - שים את הכותרת מתחת לפוסטר - סיסמה123 - שםהמשתמשהמגניבשלי - hello@world.com - 127.0.0.1 - האתרהמגניבשלי - דוגמה.com - חשבון - החלף חשבון - הוסף חשבון - נוסף %s - סנכרן - מדורג - %d / 10 - /\?\? - %s מאומת - לא ניתן להתחבר ב %s - רגיל - מקסימום - מינימום - מתאר - מדוכא - צל - הורם - 1000 מילישניות - השתמש בזה אם הכתוביות מוצגות %d מילישניות מאוחר יותר - השועל החום המהיר קופץ מעל הכלב העצלן - טען מקובץ - טען מהאינטרנט - קובץ שהורד - ראשי - משנה - רקע - מקור - בקרוב… - TS - Blu-ray - HDR - Web - תמונת פוסטר - נגן - רזולוציה וכותרת - לא מזוהה ID - נתונים לא מזוהים - הסר כיתובים סגורים מהכתוביות - הסר נפיחות מכתוביות - תוספות - טריילר - צפה בסרטונים בשפות אלה - קודם - שנה את מראה האפליקציה כך שיתאים למכשירך - דיווח על קריסה - סוים - הרחבות - הוסף מאגר - שם מאגר - תוסף הורד - התוסף נמחק - לא יכול לטעון %s - התחיל להוריד %d %s… - הוריד %d %s - הצג חלונות קופצים של דילוג בשביל פתיח/שיר סיום - מחק מאגר - פתיח מעורב - כל %s כבר הורד - מחברים - שפה - MPV - קרדיטים - מיין - בחר ספרייה - נראה שהספרייה שלכם ריקה :( -\nהתחברו לחשבון ספריה או הוסף סדרות לספרייה המקומית שלך - קובץ מצב בטוח נמצא! -\nלא טוען שום תוספות בהפעלה עד להסרת הקובץ. - לא ניתן להתקין את הגרסה החדשה של האפליקציה - הורדת אצווה - תוסף - הורד את כל התוספים ממאגר זה\? - רצועות שמע - מסלולים - Web Video Cast - דפדפן אינטרנט - כל התוספים נכבו עקב התרסקות כדי לעזור לך למצוא את האחד הגורם לצרות. - מוריד החבילות - עודכן %d תוספים - תוספים - פעולה זו תמחק גם את כל תוספי המאגר - הורד את רשימת האתרים שבהם ברצונך להשתמש - הורד: %d - מוגבל: %d - לא מורד: %d - לקלאודסטרים אין אתרים מותקנים כברירת מחדל. עליכם להתקין את האתרים ממאגרים. -\n -\nבגלל הסרת DMCA על ידי Sky UK LImited 🤮 אנחנו לא יכולים לקשר את אתר המאגרים באפליקציה. -\n -\nתצטרפו לדיסקורד שלנו או תחפשו באינטרנט. - הצג מאגרים קהילתיים - רשימה ציבורית - לשים את הכתוביות באותיות רישיות - %s (מוגבל) - רצועות וידאו - החל על הפעלה מחדש - מצב בטוח מופעל - הצג מידע על ההתרסקות - דירוג: %s - תיאור - גרסה - מצב - גודל - תומך - התקן תחילה את התוסף - פלייליסט HLS - נגן וידאו מועדף - נגן פנימי - VLC - האפליקציה לא נמצאה - כל השפות - דלג %s - פתיח - שיר סיום - סיכום - שיר סיום מעורב - מבוא - נקה היסטוריה - יותר מדי טקסט. לא ניתן לשמור ללוח. - הסר מצפייה - האם אתם בטוחים שאתם רוצים לצאת\? - מוריד עדכון אפליקציה… - מתקין עדכון אפליקציה… - האפליקציה תעודכן עם היציאה - מיין לפי - דירוג (גבוה לנמוך) - דירוג (נמוך לגבוה) - מעודכן (חדש לישן) - מעודכן (ישן לחדש) - אלפביתי (א \'עד ת\') - אלפביתי (ת\' עד א\') - פתח עם - נראה שהרשימה הזו ריקה, נסו לעבור לרשימה אחרת - diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml deleted file mode 100644 index 20641b20..00000000 --- a/app/src/main/res/values-ja/strings.xml +++ /dev/null @@ -1,185 +0,0 @@ - - - %d分 - ダウンロード - 検索 - 設定 - シェア - 映画 - ホーム - ライブラリ - 再生 - %d日 %d時間%d分 - %d時間%d分 - 検索… - ダウンロード - 情報 - シーズン - 予告編 - シリーズ - エピソード - 再生速度 (%.2fx) - 次のエピソード - 適用 - アカウント - カートゥーン - TVシリーズ - トレント - ドキュメンタリー - OVA - アジアドラマ - ライブ配信 - 映画 - その他 - カートゥーン - トレント - ドキュメンタリー - アジアドラマ - ライブ配信 - NSFW - キャンセル - アニメ - ロック - ソース - NSFW - 履歴を削除 - 視聴中コンテンツ - 全般 - 動画 - プレーヤー - 懐う - 予告編を再生 - エピソード - 視聴 - ジャンル - 映画を再生 - 字幕 - CloudStream - CloudStreamで再生 - ブラウザ - 完成 - 放置 - 保留 - ローディング… - ブラウザで開く - シーズン - 残り -\n%d分 - 再生エピソード - ダウンロード済 - バックアップ - ソース - 履歴 - ポスター - なし - コピー - 閉じる - 保存 - 消去 - %sエピ%d - 出演者:%s - ポスター - エピソードポスター - 主要ポスター - 次のランダム - 戻り - 視聴率 %.1f - 新しいアップデートを発見! -\n%s -> %s - %d分 - %sを検索… - ソース - ろくごうきじ - 接続を再試行… - 戻り - 削除 - 詳細情報 - 閉じる - アップデート・バックアップ - アプリ言語 - GitHub(ギットハブ) - -30 - +30 - 免責 - 拡張機能 - アプリ更新 - 提供者 - 字幕 - 特徴 - デフォルト - 自動 - 任意 - 拡張機能 - リンク - Android TV - ログイン - ログアウト - 最大 - 最小 - なし - - 18+ - - で開く - エピソード - 時間 - 概要 - サイト - 使用 - アプリ - 詳細情報 - 削除 - ピクチャーインピクチャー - 字幕 - 情報 - 一時停止 - 再生エピソード - 削除 - 開始 - 状態 - - 再開 - 失敗 - 合格 - 空き - 完成 - 進行中 - デフォルト - ウェブブラウザ - VLC - MPV - 言語 - 作成者 - サイズ - 状態 - バージョン - 視聴率 %s - 視聴率 - デフォルト - ダウンロード失敗 - ダウンロード開始 - ダウンロード完了 - ダウンロード終了 - ストリーム - アップデート開始 - シーズンなし - 字幕なし - アスペクト比 - ロードをスキップする - その他のオプション - データなし - ダウンロード中 - ブックマーク - 内部記憶装置 - ダウンロードが一時停止 - メタデータはこのサイトでは提供されません。メタデータがサイト上に存在しない場合、ビデオの読み込みに失敗します。 - 記述 - Logcat 🐈を表示 - ログ - 検索 - Discordに参加 - アップデート - アップデートを確認 - 作品名 - アプリのアップデートをインストール中… - diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml deleted file mode 100644 index 4b7b6869..00000000 --- a/app/src/main/res/values-kn/strings.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - %sಎಪಿ%d - ಕ್ಯಾಸ್ಟ್:%s - ಹಿಂದೆ ಹೋಗು - ಫಿಲ್ಲರ್ - ಹುಡುಕು - ಡೌನ್ಲೋಡ್ - ಫಾಂಟ್ - ಪೂರೈಕೆದಾರರನ್ನು ಬಳಸಿಕೊಂಡು ಹುಡುಕಿ - ಪ್ರಕಾರಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಹುಡುಕಿ - ಯಾವುದೇ ಬೆನೆನ್ಸ್ ನೀಡಿಲ್ಲ - ಸ್ವಯಂ-ಆಯ್ಕೆ ಭಾಷೆ - ಹೆಚ್ಚಿನ ಮಾಹಿತಿ - \@ಸ್ಟ್ರಿಂಗ್/ಹೋಮ್_ಪ್ಲೇ - ಈ ಪೂರೈಕೆದಾರರು ಸರಿಯಾಗಿ ಕೆಲಸ ಮಾಡಲು VPN ಬೇಕಾಗಬಹುದು - ಕಪ್ಪು ಗಡಿಗಳನ್ನು ತೆಗೆದುಹಾಕಿ - ಸಂಚಿಕೆ%d ಬಿಡುಗಡೆಯಾಗಲಿದೆ - %dh %dm - ಪೋಸ್ಟರ್ - ಪೋಸ್ಟರ್ - ಸಂಚಿಕೆ ಪೋಸ್ಟರ್ - ಮೇನ್ ಪೋಸ್ಟರ್ - ಅಪ್ಡೇಟ್ ಪ್ರಾರಂಭವಾಗಿದೆ - ಲೋಡಿಂಗ್ ಲಿಂಕ್ ಎರರ್ ಬಂದಿದೆ - ಇಂಟರ್ನಲ್ ಸ್ಟೋರೇಜ್ - ಡಬ್ - ಸಬ್ - ಸ್ವಯಂಚಾಲಿತ ದೋಷ ವರದಿಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ - ಹೈಡ್ - ಪ್ಲೇ - ಮಾಹಿತಿ - ಸೆಟ್ ವಾಚ್ ಸ್ಟೇಟಸ್ - ಅನ್ವಯಿಸು - ರದ್ದುಮಾಡು - ಸಬ್ ಟೈಟಲ್ಸ್ ಎಲೆವಷನ್ - ಫಾಂಟ್ ಸೈಜ್ - ಸಬ್ ಟೈಟಲ್ಸ್ ಭಾಷೆ - ತೆಗೆದುಹಾಕಿ - ಈ ಪೂರೈಕೆದಾರರು ಟೊರೆಂಟ್ ಆಗಿದೆ, VPN ಅನ್ನು ಶಿಫಾರಸು ಮಾಡಲಾಗಿದೆ - ಯಾವುದೇ ಪ್ಲಾಟ್ ಕಂಡುಬಂದಿಲ್ಲ - ಲಾಗ್‌ಕ್ಯಾಟ್ 🐈 ತೋರಿಸಿ - ಲಾಗ್ - ಚಿತ್ರದಲ್ಲಿ-ಚಿತ್ರದಲ್ಲಿ - ಪ್ಲೇಯರ್ ಮರುಗಾತ್ರಗೊಳಿಸಿ ಬಟನ್ - ಸಬ್ ಟೈಟಲ್ಸ್ - ಪ್ಲೇಯರ್ ಸಬ್ ಟೈಟಲ್ಸ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು - ಕ್ರೋಮ್ ಕ್ಯಾಸ್ಟ್ ಸಬ್ ಟೈಟಲ್ಸ್ ಸೆಟ್ಟಿಂಗ್ಸ್ - ಹಿಂದೆ ಹೋಗು - ಡೌನ್‌ಲೋಡ್ ವಿರಾಮಗೊಳಿಸಿ - ಬುಕ್‌ಮಾರ್ಕ್‌ - ಬ್ಯಾಕ್ ಗ್ರೌಂಡ್ ಕಲರ್ - %d ಡೇವ್‌ಗಳಿಗೆ ಬೆನೆನೆಸ್ ನೀಡಲಾಗಿದೆ - ಡೀಫಾಲ್ಟ್‌ಗೆ ಮರುಹೊಂದಿಸಲು ಹಿಡಿದುಕೊಳ್ಳಿ - ಸೈಟ್‌ನಿಂದ ಮೆಟಾಡೇಟಾವನ್ನು ಒದಗಿಸಲಾಗಿಲ್ಲ, ಅದು ಸೈಟ್‌ನಲ್ಲಿ ಅಸ್ತಿತ್ವದಲ್ಲಿಲ್ಲದಿದ್ದರೆ ವೀಡಿಯೊ ಲೋಡಿಂಗ್ ವಿಫಲಗೊಳ್ಳುತ್ತದೆ. - ಇತರ ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಮೇಲೆ ಚಿಕಣಿ ಪ್ಲೇಯರ್‌ನಲ್ಲಿ ಪ್ಲೇಬ್ಯಾಕ್ ಅನ್ನು ಮುಂದುವರಿಸುತ್ತದೆ - ಕ್ರೋಮ್ ಕ್ಯಾಸ್ಟ್ ಸಬ್ ಟೈಟಲ್ಸ್ - ರೇಟೆಡ್:%.1f - ತೆಗೆದುಹಾಕಿ - ಡೌನ್‌ಲೋಡ್ ಅನ್ನು ಪುನರಾರಂಭಿಸಿ - ಕ್ಲೋಸ್ - ಕ್ಲಿಯರ್ - ಸೇವ್ - ಸಬ್ ಟೈಟಲ್ಸ್ ಸೆಟ್ಟಿಂಗ್ಸ್ - ಫೈಲ್ ಪ್ಲೇ - ಟೆಕ್ಸ್ಟ್ ಕಲರ್ - ಔಟ್ ಲೈನ್ ಕಲರ್ - ವಿಂಡೋ ಕಲರ್ - ಎಡ್ಜ್ ಟೈಪ್ - ಪ್ರೊವೈಡರ್ ಬದಲಾಯಿಸಿ - %dಮಿನ - ವಿವರಣೆ - ಸ್ಪೀಡ್(%.2fx) - ಹೋಂ - ಸಬ್ ಟೈಟಲ್ಸ್ - ಸೆಟ್ಟಿಂಗ್ಸ್ - ಬುಕ್‌ಮಾರ್ಕ್‌ಗಳನ್ನು ಫಿಲ್ಟರ್ ಮಾಡಿ - ಹುಡುಕು… - ಚಲನಚಿತ್ರವನ್ನು ಪ್ಲೇ ಮಾಡಿ - ಪ್ರಿವ್ಯೂ ಹಿನ್ನೆಲೆ - ಮುಂದಿನ ಸಂಚಿಕೆ - ಕ್ಲೌಡ್ ಸ್ಟ್ರೀಮ್ - ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗುತ್ತಿದೆ - ಸ್ಟ್ರೀಮ್ - ಶೇರ್ - ಫೈಲ್ ಅಳಿಸಿ - ಹೆಚ್ಚಿನ ಮಾಹಿತಿ - ಹೊಸ ಅಪ್ಡೇಟ್ ಬಂದಿದೆ -\n%s-%s - ಲೋಡಿಂಗ್… - ಡೌನ್‌ಲೋಡ್ ಭಾಷೆಗಳನ್ನು ಮಾಡಿ - ಲೈವ್‌ಸ್ಟ್ರೀಮ್ ಪ್ಲೇ ಮಾಡಿ - ಕ್ಲೌಡ್ ಸ್ಟ್ರೀಮ್ ಇದರೊಂದಿಗೆ ಪ್ಲೇ ಮಾಡಿ - ವೀಕ್ಷಿಸಲು ಯೋಜನೆ - ಸಂಚಿಕೆಯನ್ನು ಪ್ಲೇ ಮಾಡಿ - ಕಂಟಿನ್ಯೂ ವಾಟಚಿಂಗ್ - ಯಾವುದೇ ವಿವರಣೆ ಕಂಡುಬಂದಿಲ್ಲ - ಸ್ಟ್ರೀಮ್ ಟೊರೆಂಟ್ - ಡೌನ್‌ಲೋಡ್ - ಕಾಪಿ - ನೋ ಡೇಟಾ - ಪ್ಲೇಯರ್ ಸ್ಪೀಡ್ - %d %dh %dm - ಹುಡುಕು %s… - ಹೆಚ್ಚಿನ ಆಯ್ಕೆ - ಫಾಂಟ್‌ಗಳನ್ನು ಇರಿಸುವ ಮೂಲಕ ಆಮದು ಮಾಡಿ %s - %dm - ಪ್ರಕಾರಗಳು - ಬ್ರೌಸರ್ ತೆರೆಯಿರಿ - ಆನ್-ಹೋಲ್ಡ್ - ನನ್ - ಸಂಪರ್ಕವನ್ನು ಮರುಪ್ರಯತ್ನಿಸಿ… - ಡೌನ್‌ಲೋಡ್ ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ - ಡೌನ್‌ಲೋಡ್ ವಿಫಲವಾಗಿದೆ - ಡೌನ್‌ಲೋಡ್ ಮುಗಿದಿದೆ - ಬ್ರೌಸರ್ - ಸ್ಕಿಪ್ ಲೋಡಿಂಗ್ - ವಾಚಿಂಗ್ - ಪೂರ್ಣಗೊಂಡಿದೆ - ಕೈಬಿಡಲಾಯಿತು - ಪುನಃ ವೀಕ್ಷಿಸುತ್ತಿದೆ - ಟ್ರೈಲರ್ ಪ್ಲೇ ಮಾಡಿ - ಮೂಲಗಳು - ಡೌನ್‌ಲೋಡ್ ಮಾಡಲಾಗಿದೆ - ಡೌನ್‌ಲೋಡ್ ಪ್ರಾರಂಭವಾಗಿದೆ - ಡೌನ್‌ಲೋಡ್ ರದ್ದುಗೊಳಿಸಲಾಗಿದೆ - ಮುಂದಿನ ರಾಂಡಮ್ - diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 811a09c5..85aee997 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -1,17 +1,17 @@ - - + Брзина (%.2fx) Оценето: %.1f - Пронајдена нова верзија на апликацијата! -\n%s -> %s + Пронајдена нова верзија на апликацијата!\n%s -> %s Филтер + CloudStream Дома Барај Превземања Поставки + Барај… Нема податоци Повеќе опции @@ -21,6 +21,7 @@ Отвори во прелистувач Прескокни вчитување Вчитување… + Моментални гледања Ставено на чекање Завршени @@ -28,6 +29,7 @@ Планирани за гледање Ништо одбрано Повторно гледање + Пушти филм Стримај торент Извори @@ -36,6 +38,7 @@ Оди назад Пушти епизода + Превземи Превземено Се превзема @@ -44,12 +47,15 @@ Превземањето е неуспешно Превземањето е откажано Превземањето е готово + Грешка при вчитување на врската Внатрешна меморија + Избриши датотека Пушти датотека Продолжи со преземање Паузирај со преземање + Оневозможи автоматско известување за грешки Повеќе информации Скриј @@ -61,6 +67,7 @@ Активирај Откажи Брзина на плеер + Поставки за преводи Боја на текстот Боја на надворешна линија од текстот @@ -70,22 +77,30 @@ Висина на титл Фонт Големина на фонт + Пребарувај користејќи провајдери Пребарувај користејќи типови + %d Крем банани дадени на девелоперите Нема крем банани дадени + Авто одбирање на јазик Превземи јазици Држете за да се ресетира на стандардно Продолжи со гледање + Избриши Повеќе информации + Можеби ќе биде потребен VPN за да работи правилно овој провајдер Овој провајдер е торент, се препорачува VPN + Метаподатоците не се обезбедени од страната, вчитувањето на видеото може да не успее. + Опис Не е пронајден заговор Не е пронајден опис + Слика-во-слика Продолжува со репродукција во минијатурен плеер над другите апликации Копче за промена на големината на плеерот @@ -102,6 +117,7 @@ Допрете двапати на десната или левата страна за да барате напред или назад Користете ја осветленоста на системот Користете ја осветленоста на системот во плеерот наместо темната прекривка + Барај Информации Напредно пребарување @@ -119,25 +135,29 @@ Придружете се на Discord Дајте им крем банана на развивачите Дадени крем банани + Јазик на апликацијата + Овој провајдер нема поддршка за Chromecast Не се пронајдени врски Врската е копирана во таблата со исечоци Пушти ја епизодата Ресетирање на стандардните вредности За жал, апликацијата падна. Ќе се испрати анонимен извештај за грешка до програмерите + Сезона Нема сезона Епизода Епизоди С Е + Избриши датотека Избриши Паузирај Продолжи - Ова трајно ќе го избрише %s -\nДали си сигурен\? + Ова трајно ќе го избрише %s\nДали си сигурен? + Во тек Завршени Статус @@ -146,22 +166,27 @@ Времетраење Извор Крат + во редица Нема преводи Стандардно + Слободен простор Искористен простор Апликациски простор + Филмови Серии Цртани Аниме Торент + Извор на проблемот Грешка Грешка Неочекувана грешка со плеерот Грешка при превземањето, проверете си ги дозволите за користење на простор + Епизода на Chromecast Огледало на Chromecastr Пушти во апликацијата @@ -171,32 +196,42 @@ Авто превземање Превземи Mirror Вчитај повторно врски + Не е пронајдено ажурирање Проверете ажурирања + Заклучи Променете ја големината Избор Прескокни ОП + Не прикажувај повторно Ажурирај Префериран квалитет на гледање DNS преку HTTPS Корисно за заобиколување на блоковите на интернет провајдерите + Патека на превземање + Прикажи синхронизирано/сублирано аниме + Прилагоди на екранот Истегни Зумирај + Disclaimer Генерално Јазици на провајдерите Распоред на апликацијата Претпочитани медиуми + Автоматски Изглед за гледање на телевизор Изглед за гледање на телефон + Примарна боја Тема на апликацијата + %s %s Корисничко име Одјавување @@ -213,5 +248,4 @@ Депресиран Сенка Подигнат - Историја diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index d430d7cc..78f45e26 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -1,11 +1,10 @@ - വേഗം (%.2fx) റേറ്റിംഗ്: %.1f - പുതിയ അപ്ഡേറ്റ് -\n%s -> %s + പുതിയ അപ്ഡേറ്റ്\n%s -> %s + CloudStream ഹോം തിരയുക @@ -18,11 +17,13 @@ തരം ഷെയർ ബ്രൗസറിൽ തുറക്കുക + കാണുന്നു കണ്ടു നിർത്തി കണ്ടു തീർന്നു കണ്ടു മതിയാക്കി കാണാൻ പോകുന്നു + സിനിമ പ്ലേ ചെയ്യുക ടോറൻറ് സ്ട്രീം ചെയ്യുക സ്രോതസുകൾ @@ -30,6 +31,7 @@ വീണ്ടും കണക്ട് ചെയ്യുക… പിന്നോട്ട് പോകുക എപ്പിസോഡ് പ്ലേയ് ചെയ്യുക + ഡൌൺലോഡ് ഡൌൺലോഡ് ചെയ്തവ ഡൌൺലോഡ് ചെയ്യുന്നു @@ -38,14 +40,18 @@ ഡൌൺലോഡ് മുടങ്ങി ഡൌൺലോഡ് റദ്ദാക്കി ഡൌൺലോഡ് പൂർത്തിയായി + ലിങ്കിൽ തകരാർ ആന്തരിക സ്റ്റോറേജ് + ഡബ് സബ് + ഫയൽ ഡിലീറ്റ് ചെയ്യുക ഫയൽ പ്ലേയ് ചെയ്യുക ഡൌൺലോഡ് തുടരുക ഡൌൺലോഡ് നിർത്തുക + ഓട്ടോമാറ്റിക് ബഗ് റിപ്പോർട്ടിംഗ് പ്രവർത്തനരഹിതമാക്കുക കൂടുതൽ വിവരം ഒളിക്കുക @@ -57,6 +63,7 @@ പ്രയോഗിക്കുക റദ്ദാക്കുക പ്ലേയർ വേഗത + + സ്രോതസ് അടിസ്ഥാനത്തിൽ തിരയുക തരം അടിസ്ഥാനത്തിൽ തിരയുക + %d പഴം കൊടുത്തു - പഴം കൊടുത്തിട്ടില്ല + പഴം കൊടുത്തിട്ടില്ല + റീസെറ് ചെയ്യാൻ അമർത്തിപ്പിടിക്കുക തുടർന്നു കാണുക + നീക്കം ചെയ്യുക കൂടുതൽ വിവരം - ഈ സ്രോതസ് പ്രവൃത്തിക്കാൻ VPN ഉപയോഗിക്കേണ്ടിവന്നേക്കാം + + ഈ സ്രോതസ് പ്രവൃത്തിക്കാൻ VPN ഉപയോഗിക്കേണ്ടിവന്നേക്കാം ഈ ടോറന്റ് സ്രോതസ് ഉപയോഗിക്കാൻ VPN ശുപാർശചെയ്യുന്നു വിവരണം വിവരണം ലഭ്യമല്ല വിവരണം ലഭ്യമല്ല + മറ്റ് ആപ്പുകളുടെ മുകളിൽ ഒരു മിനിയേച്ചർ പ്ലെയറിൽ പ്ലേബാക്ക് തുടരുക @@ -94,6 +107,7 @@ തെളിച്ചം അല്ലെങ്കിൽ വോളിയം മാറ്റാൻ ഇടത്തോട്ടോ വലത്തോട്ടോ സ്വൈപ്പുചെയ്യുക മുന്നോട്ട് അല്ലെങ്കിൽ പിന്നിലേക്ക് നീങ്ങാൻ വലത്തോട്ടോ ഇടത്തോട്ടോ രണ്ടുതവണ ടാപ്പുചെയ്യുക + തിരയുക വിവരം @@ -111,21 +125,24 @@ പഴം കൊടുക്കു പഴം കൊടുത്ത എണ്ണം ആപ്പിന്റെ ഭാഷ - ഈ സ്രോതസ് ക്രോംകാസ്റ് അനുവദിക്കുന്നില്ല + + ഈ സ്രോതസ് ക്രോംകാസ്റ് അനുവദിക്കുന്നില്ല ലിങ്കുകൾ ലഭ്യമല്ല ലിങ്ക് പകർത്തിയിരിക്കുന്നു എപ്പിസോഡ് പ്ലേയ് ചെയ്യുക ആപ്പ് നിശ്ചലമായിരിക്കുന്നതിന് ക്ഷമിക്കണം + സീസൺ സീസണില്ല എപ്പിസോഡ് എപ്പിസോഡുകൾ + ഫയൽ ഡിലീറ്റ് ചെയ്യുക ഡിലീറ്റ് നിർത്തുക തുടരുക - സ്ഥിരമായി %sനെ ഡിലീറ്റ് ചെയ്യുക -\nഉറപ്പാണോ\? + സ്ഥിരമായി %sനെ ഡിലീറ്റ് ചെയ്യുക\nഉറപ്പാണോ? + തുടരുന്നു പൂർത്തിയായി അവസ്ഥ @@ -134,22 +151,27 @@ ദൈര്ഘം സ്രോതസ് സംഗ്രഹം + + ഒഴിവ് ഉപയോഗത്തിൽ ആപ്പ് + സിനിമ ടീവീ സീരീസ് കാർട്ടൂൺസ് ആനിമേ ടോറൻറ് + + ആപ്പിൽ പ്ലേയ് ചെയ്യുക @@ -159,15 +181,16 @@ ഡൌൺലോഡ് ചെയ്യൂ മിറർ ഡൗണ്ലോഡ് ലിങ്ക്സ് വീണ്ടും ലോഡുചെയ്യുക + അപ്ഡേറ്റ് ലഭ്യമല്ല അപ്ഡേറ്റിനായി തിരയുക + പൂട്ടുക വലുപ്പം മാറ്റുക സ്രോതസ് OP ഒഴിവാക്കു - ഇനിയും കാണിക്കരുത് + + ഇനിയും കാണിക്കരുത് അപ്ഡേറ്റ് ഔചിത്യ വീഡിയോ ക്വാളിറ്റി - ചരിത്രം - കണ്ടതാണെന്ന് അടയാളപ്പെടുത്തുക diff --git a/app/src/main/res/values-qt/strings.xml b/app/src/main/res/values-mo/string.xml similarity index 73% rename from app/src/main/res/values-qt/strings.xml rename to app/src/main/res/values-mo/string.xml index eee28785..361aaf56 100644 --- a/app/src/main/res/values-qt/strings.xml +++ b/app/src/main/res/values-mo/string.xml @@ -1,15 +1,15 @@ - + aauugghhaauuh ooh oouuh - ouuhhuhooh ooh + ouuhhhooh ooh aaaghhoh aauugghh - haaooh ooo + haaooh aaaaaah oooohhoouuh - aaaaoouuuhahhh ahh + aaaaaoouuhahhh ahh oooohh aauuh ahhhaaaghh aaaaa ahaauugghh @@ -122,7 +122,7 @@ A ooha ohahaaahoooa ahahooo oooooah - aahhaaaaaahooo aauuh aaahhuaooo-ahahoooohh aoouuhoohoohooo-ahah %s + aahhaaaaaahooo aauuh aaahhuaooo-ahahoooohh aoouuhoohoohooo-ahah aaaghhaaaaa aauuhaauuh ahhhaaaaa @@ -145,7 +145,7 @@ aauugghhooo-ahah ohaaauugghh aoohaaahhu ahouuhhh ooo-ahahaauuh aaahhu - ooo-ahah ohaauuh %s + ooo-ahah ohaauuh ahoha ooo-ahahohoohah oooohh aauugghhahhaauugghh aaaghhoooohh aaahhu ahooo @@ -172,80 +172,28 @@ oouuh haa oohahaha hahha ooooohaha oohahaha hahha ooooohaha haaoou - u ahhu uuuh hau uaohuau + u ahhu uuuh hau uaohuau aahuuouhh ouh hhhah hhaohuhha a auoo ohauh - uhaauauau ahuuouaha + uhaauauau ahuuouaha auuuha h a ahuhaaaa - uaoh uhu uahaaaaoo - uauhah u aao u oah - h u ahahh aoou ha + uaoh uhu uahaaaaoo + uauhah u aao u oah + h u ahahh aoou ha haoooo aaoou uou ah oahuouooaouoa ouuhh o ouou uhauuuoaah h ou aouhouo aaooao hh - hhauhohhuu au aaohu - uhuoh o a ohahuhohoa hah + hhauhohhuu au aaohu + uhuoh o a ohahuhohoa hah ua hu ouo o aoau hah ah - ah huu oouhhau aoaoaaohoo ha - a ahu uoo uoahuo uo + ah huu oouhhau aoaoaaohoo ha + a ahu uoo uoahuo uo uo u ohouao uuoouhh hhuhuuh ouhoaao hau aouo - uha uh huo uooaah u + uha uh huo uooaah u u ooah uo ahauao huhuu hauu h a ou oh ouhuouhoaaha aaooohhouhhha hauauuu - aaaaaaa uuuuuu -\n%s -> %s - %s aaou %d - oouaaahh %s - aaaaaaugh ouh %d uuoogahaaah ooua-h-ha - %daaa %duuu %dhhhg - %daaaaaaauuhh %doouuahaaha - %dmmmm... - aaaaaaaaaaaaahh (%.2foouo) - aghaaaooo-ough %.1f - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaooooohhuhuhuhuhuhhuhuhhhaaagaha-agagaoooo - %d ooaaaugha - oooooh aaah aauuuggghauuh - aaoohhu %s aoouu - oooaaaauhgaaaa - aaaaaah-ooooooooouuagh - auugggguuuuuuhhh uuuu hhhhhhhhuhggghggg - ggaaaahhhhhhh gaauuuuuuuaaaau - aaauuuuggggguu - ooo aagg hhhh - ooo aagg hhhh - uuuuhhhoouuooog ooaaahhhh - uuu ugggg - ooo guggg ooh - auuuooohaaaaagh - uuuuuuuh aaaoo o - ooooooouuuua aa aaagh agh - AAAAUUUOH - aoughoooaaaa - oooouuuh - ahaough aaouuuuh-h - auughooo - ooooooa aauoh - aaaaagh oouoo aaaaaaa - aaaaaagh uuohuoh - aaaaaauo agghhhhhhaoouu - uuuuuuuuh - ouaaahh - ooough aaoough aooou %s aaaa - ouooooouuuu oooooo - aaaaaaaaaaahhhgh-aooohoooo - aau aooooghaao - aagh aaaaaaaaaaaa oooh, aaough, ooga oguuu aaaaaaaaaaa ooooooohghh a-a-aaauo - %dmmmmmm.. -\naaaaooughugh - aooohuohaaaa ooooagh - oooooogh-aaaaaogh - guuuaaaahhhhhhhaaa - woooaaahh ahahaaaauu 🦍 - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOOOGGAGHAGHAAA - aoaaaaaoooghhh - oooooh uuaagh - @string/home_play - + aaaaaaa uuuuuu\n%s -> %s + \ No newline at end of file diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml deleted file mode 100644 index c757504a..00000000 --- a/app/src/main/res/values-ms/strings.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 766bcdc7..7daca143 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -1,4 +1,3 @@ - @@ -8,30 +7,34 @@ %dd %dh %dm %dh %dm %dm + Poster - Poster + @string/result_poster_img_des Aflevering Poster Hoofdposter Volgende willekeurig Ga terug Wijzig provider Voorbeeld Achtergrond + Snelheid (%.2fx) Beoordeeld: %.1fAls - Nieuwe update gevonden! -\n%s -> %s + Nieuwe update gevonden!\n%s -> %s Filler %d min + CloudStream Afspelen met CloudStream Home Zoeken Downloads Instellingen + Zoeken… Zoeken %s… + Geen gegevens Meer Opties Volgende aflevering @@ -40,6 +43,7 @@ Openen in Browser Laden overslaan Laden… + Aan het kijken In de wacht Voltooid @@ -47,6 +51,7 @@ Plan om te kijken Geen Opnieuw kijken + Film afspelen Livestream afspelen Stream Torrent @@ -56,6 +61,7 @@ Ga terug Aflevering afspelen + Download Gedownload Downloaden @@ -65,14 +71,18 @@ Download Geannuleerd Download Gereed Stream + Fout bij laden links Interne opslag + Dub Sub + Bestand verwijderen Bestand afspelen Download hervatten Download pauzeren + Automatische bugrapportage uitschakelen Meer info Verberg @@ -88,7 +98,9 @@ Sluit Wissen Opslaan + Afspelen Snelheid + Ondertiteling Tekst kleur Omtrekkleur @@ -98,26 +110,35 @@ Ondertitelhoogte Lettertype Lettergrootte + Zoeken met providers Zoeken met types + %d Bananen gegeven aan ontwikkelaars Geen Bananen gegeven + Taal automatisch selecteren Talen downloaden Ondertiteltaal Houd ingedrukt om naar standaard instellingen gaan Importeer lettertypes door ze in %s Doorgaan met kijken + Verwijder Meer Info - @string/home_play + @string/home_play + Een VPN kan nodig zijn om deze provider correct te laten werken Deze provider is een torrent, een VPN wordt aanbevolen + Metadata wordt niet geleverd door de site, het laden van video\'s zal mislukken als deze niet op de site bestaat. + Beschrijving Geen plot gevonde Geen Beschrijving Gevonden - Toon Logcat 🐈 + + Toon logcat 🐈 + Beeld-in-beeld Blijft afspelen in een miniatuurspeler bovenop andere apps Afspeel knop wijzig @@ -126,31 +147,37 @@ Speler Ondertiteling instellingen Chromecast Ondertitels Chromecast ondertitels instellingen + Eigengravy Modus Voegt een snelheidsoptie toe in de speler Swipe to seek - Veeg naar links of rechts om de tijd in de videospeler te regelen + Veeg naar links of rechts om de tijd in de videoplayer te regelen Veeg om instellingen te wijzigen Veeg naar links of rechts om de helderheid of het volume te wijzigen Dubbeltik om te zien Dubbeltik om te pauzeren - Videospeler aantal zoeken + Speler zoeken bedrag Tik twee keer aan de rechter- of linkerkant om vooruit of achteruit te zoeken - Tik twee keer in het midden om te pauzeren + Tik in het midden om te pauzeren Systeemhelderheid gebruiken Gebruik systeemhelderheid in de app-speler in plaats van een donkere overlay + Kijkvoortgang bijwerken Automatisch synchroniseren van je huidige episode vooruitgang + Gegevens herstellen vanaf back-up + Back-up gegevens Geladen back-up bestand Kan gegevens uit bestand niet herstellen %s - De gegevens zijn opgeslagen - Opslagrechten ontbreken. Probeer het opnieuw. + Gegevens succesvol opgeslagen + Opslagrechten ontbreken, probeer het opnieuw Fout bij het maken van een back-up %s + Zoeken Accounts Updates en back-up + Info Geavanceerd zoeken Geeft u de zoekresultaten gescheiden door provider @@ -158,7 +185,8 @@ Verstuurt geen gegevens Toon filler episode voor anime Toon trailers - Toon posters van Kitsu + Toon posters van kitsu + App-updates tonen Automatisch zoeken naar nieuwe updates bij het opstarten Update naar pre-releases @@ -169,13 +197,16 @@ Word lid van ons Discord kanaal Geef een banaan aan de ontwikkelaars Gegeven banaan + App Taal + Deze provider heeft geen Chromecast-ondersteuning Geen links gevonden Link gekopieerd naar klembord Aflevering afspelen Reset naar standaardwaarde - Sorry, de applicatie is gecrasht. Er wordt een anoniem bugrapport naar de ontwikkelaars gestuurd + Sorry, de applicatie is gecrasht. Er wordt een anoniem bugrapport naar de ontwikkelaars gestuurd + Seizoen Geen seizoen Aflevering @@ -185,16 +216,16 @@ S E Geen afleveringen gevonden + Verwijder bestand Verwijder Pauze Hervatten -30 +30 - Dit wordt zeker permanent verwijderd %s -\nWeet u het zeker\? - %dm -\nremaining + Dit wordt zeker permanent verwijderd %s\nWeet u het zeker? + %dm\nremaining + Voortdurende Voltooid Status @@ -203,12 +234,15 @@ Duur Site Korte inhoud + wachtrij Geen ondertiteling Standaard + Vrij Gebruikt App + Films TV series @@ -219,6 +253,7 @@ OVA Aziatische drama\'s Livestreams + Film Serie @@ -229,11 +264,13 @@ Documentaire Aziatisch drama Livestream + Bronfout Externe fout Renderer fout Onverwachte speler fout Downloadfout, controleer opslagrechten + Chromecast aflevering Chromecast mirror Speel in app @@ -244,40 +281,53 @@ Download mirror Herlaad Linkss Ondertitels downloaden + Kwaliteitslabel Dub label Sub label Titel Schakel UI-elementen op poster + Geen update gevonden Controleren op updates + Slot Formaat wijzigen Bron OP overslaan + Niet meer weergeven Deze update overslaan Update Gewenste kijkwaliteit Maximaal aantal tekens voor titel van videospeler - Videospeler Resolutie + Videospeler Resolutie + Grootte videobuffer Lengte videobuffer Video cache op schijf Wis video en beeld cache + Zal willekeurige crashes veroorzaken als deze te hoog is ingesteld. Verander niet als je weinig RAM hebt, zoals een Android TV of een oude telefoon Kan problemen veroorzaken op systemen met weinig opslagruimte, zoals Android TV-apparaten als u deze te hoog instelt + DNS over HTTPS Handig om ISP-blokkades te omzeilen + Kloon site Site verwijderen - Voeg een kloon toe van een bestaande site, met een andere URL + Voeg een kloon toe van een bestaande site, met een andere url + Downloadpad + Nginx server url + Weergave Dubbed/Subbed Anime + Pas aan het scherm Uitgerekt Zoom + Disclaimer Algemeen Willekeurige knop @@ -287,14 +337,18 @@ Preferred media Subtitle encoding Layout + Auto TV layout Telefoon layout emulator layout + Primaire kleur App thema Locatie van de titel van de poster Zet de titel onder de poster + + wachtwoord123 MijnCoolGebruikersnaam @@ -303,6 +357,7 @@ MyCoolSite voorbeeld.com Taalcode (nl) + Geen normaal @@ -343,6 +399,7 @@ Gebruik dit als de ondertitels %dms te vroeg worden getoond Gebruik dit als ondertitels %dms te laat worden getoond Geen ondertitelvertragin + De snelle bruine vos springt over de luie hond + Aanbevolen Geladen %s Laden uit bestand @@ -358,9 +416,12 @@ Hoofd Ondersteuning Achtergrond + Bron Willekeurig + Binnenkort… + Cam Cam Cam @@ -368,7 +429,7 @@ HD TS TC - Blu-ray + BlueRay WP DVD 4K @@ -377,6 +438,7 @@ HDR SDR Web + Posterafbeelding Player Resolutie en titel @@ -398,15 +460,6 @@ Instelling overslaan Pas het uiterlijk van de app aan uw apparaat aan Crashrapportage - Wat wil je zien\? + Wat wil je zien? Klaar - Markeer als bekeken - Geschiedenis - Speel Trailer - Start de volgende episode wanneer deze afgelopen is - Volgende episode automatisch afspelen - De update is gestart - Bibliotheek - Browser - Logboek diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml deleted file mode 100644 index 43738665..00000000 --- a/app/src/main/res/values-nn/strings.xml +++ /dev/null @@ -1,186 +0,0 @@ - - - Fleire val - Heim - Søk - Nedlastingar - Innstillingar - Søk… - Søk i %s… - Ingen data tilgjengeleg - Neste episode - Episodeminiatyrbilete - Neste tilfeldig - Spel av med CloudStream - Fyllstoff - Forhandsvis bakgrunnsbilete - Vurdert: %.1f - Ny oppdatering tilgjengeleg! -\n%s -> %s - %d minutt - Miniatyrbilete - Episode %d vil bli sleppt om - %dd %dt %dm - %dt %dm - Rollebesetning: %s - Hovudminiatyrbilete - Endre leverandør - Opne i nettlesaren - Laster inn… - Hopp over buffering - Ser på - På vent - Fullført - Planlagt - Spel av film - Spel av trailer - Spel av direktesending - Kjelder - Undertekstar - Gå tilbake - Spel av episode - Nedlasting - Lasta ned - Nedlasting pågår - Nedlasting satt på vent - Nedlasting starta - Nedlasting feila - Nedlasting avbrote - Nedlasting ferdig - Straum - Feil ved innlasting av lenker - Internlagring - Dubb - Slett fil - Spel av fil - Sett nedlasting på vent - Deaktiver automatisk feilrapportering - Gøym - Spel(e) av - Informasjon - Filtrer bokmerker - Bokmerker - Fjern - Sett visingstatus - Bruk - Avbryt - Kopier - Tøm - Lagre - Avspelinghastigheit - Innstillingar for undertekstar - Tekstfarge - Konturfarge - Bakgrunnfarge - Vindaugefarge - Kanttype - Underteksthøgde - Skrifttype - Skriftstorleik - Søk basert på leverandør - Fjern - Meir informasjon - Undertekstar - Beskriving - Fant ingen handling - Fant ingen beskriving - Fjern dei svarte kantane - Spel av neste episode automatisk - Sikkerheitskopier data - Lasta inn sikkerheitkopifil - Søk - Kontoar - Oppdateringar og sikkerheitskopiering av data - Informasjon - Avansert søk - Vis trailerar - Vis miniatyrbilete frå Kitsu - Vis programoppdateringar - Github - Lysnovelleprogram av dei same utviklarane - Animeprogram av dei same utviklarane - Programspråk - Ingen lenker funnen - Kopiert lenke til utklipptavle - Spel av episode - Tilbakestill til standardverdi - Beklager, programmet har krasjet. Ein anonymisert feilrapport vil bli sendt til utviklarane - %s %d%s - Ingen sesong - Episode - S - E - Ingen episodar blei funnen - Slett fil - Slett - Pause - Gjenoppta - -30 - +30 - Dette vil slette %s permanent. -\nEr du sikker på dette\? - %dm -\ngjenstår - Pågåande - Fullført - År - Vurdering - Nettstad - Om - i kø - Ingen undertekstar - Standard - Brukt - Program - Filmar - TV-seriar - Tilgjengeleg - Teikneseriar - Anime - Dokumentarar - Andre - Film - Serie - Teiknefilm - Dokumentar - Asiatisk drama - Direktesending - Video - Kjeldefeil - Spel av i programmet - Spel av i %s - Spel av i nettlesaren - Kopier lenke - Automatisk nedlasting - Last inn lenker på nytt - Last ned undertekstar - Oppdater - Ignorer oppdatering - Ikkje vis igjen - Kjelde - Hopp over intro - Endre storleik - Lås - Føretrekt oppløysing - DNS over HTTPS - Gå tilbake - Sjangrar - Dele - Ingen - Ser om igjen - Oppdatering starta - Fortsett nedlasting - Meir informasjon - Lukk - Avspelinghastigheit (%.2fx) - CloudStream - %dm - Sjå etter nye oppdateringar automatisk ved oppstart - Bli med i Discord - Denne leverandøren støttar ikkje Chromecast - Sesong - Episodar - Varigheit - Direktesendingar - Programoppdateringar - diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index fddd4919..ebd6ee49 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -1,4 +1,3 @@ - Plakat @@ -9,18 +8,20 @@ Gå tilbake Bytt leverandør Forhåndsvisning Bakgrunn + Avspillingshastighet (%.2fx) Vurdert: %.1f - Ny oppdatering funnet! -\n%s -> %s + Ny oppdatering funnet!\n%s -> %s + CloudStream Hjem Søk Nedlastinger Innstillinger + Søk… - Ingen data tilgjengelig + Nei informasjon Flere valg Neste episode Sjangere @@ -28,12 +29,14 @@ Åpne i nettleseren Hopp over Laster inn… + Ser på På vent Fullført Falt Planlegg å se Ingen + Spill filmer Strøm Torrent Kilder @@ -42,6 +45,7 @@ Gå tilbake Spille Episode + nedlasting Lastet ned Nedlasting pågår @@ -50,14 +54,18 @@ Nedlasting mislyktes Nedlasting avbrutt Last ned Ferdig + feil ved lasting av lenker Intern lagring + Dubbet Subbed + Slett fil Spill av fil Fortsett nedlasting Stopp nedlastingen + Deaktiver automatisk feilrapportering Mer informasjon Gjemme seg @@ -69,6 +77,7 @@ Søke om Avbryt Spillerhastighet + Innstillinger for teksting Tekstfarge Konturfarge @@ -78,21 +87,27 @@ Undertekst høyde Skrift Skriftstørrelse + Søk med leverandører Søk med typer + %d Benenes gitt til utvikler Nei Benenes gitt + Velg språk automatisk Last ned språk Hold inne for å tilbakestille Fortsett å se + ta bort Mer informasjon + En VPN kan være nødvendig for at denne leverandøren skal fungere Denne leverandøren er en torrent, en VPN anbefales Beskrivelse - Fant ingen handling + Fant ikke tomt Ingen beskrivelse funnet + Bilde-i-bilde Fortsetter avspilling i en miniatyr spiller på toppen av andre apper Knapp for størrelse på spiller @@ -109,6 +124,7 @@ Trykk to ganger på høyre eller venstre for å søke fremover eller bakover Bruk systemets lysstyrke Bruk systemlysstyrke i appspilleren i stedet for et mørkt overlegg + Søk Informasjon Avansert søk @@ -126,7 +142,9 @@ Bli med Discord Gi en benene til devs Gitt benene + appspråk + Denne leverandøren har ingen Chromecast-støtte Ingen lenker funnet Linken er kopiert til utklippstavlen @@ -139,12 +157,13 @@ Episoder S E + Slett fil Slett Stopp Gjenoppta - Dette vil slette %s -\nEr du sikker\? + Dette vil slette %s\nEr du sikker? + Pågående Fullført Posisjon @@ -153,22 +172,27 @@ Varighet Nettstedet Om + I kø Ingen undertekster Misligholde + Tilgjengelig Brukt applikasjon + Filmer TV-serier Tegneserier Anime Torrent + Kildefeil Fjernfeil Gjengivelsesfeil Uventet spillerfeil Nedlastingsfeil, sjekk lagringstillatelser + Støpt Episode Støpt Speil Spill i appen @@ -178,18 +202,22 @@ Automatisk nedlasting Last ned speil Last inn lenker på nytt + Ingen oppdatering funnet Se etter oppdatering + Låse Endre størrelse Kilde Hoppe OP + Ikke vis igjen Oppdater Foretrukket klokkekvalitet DNS rundt HTTPS Nyttig til omgå Internettleverandør hinder Vis dubbet/subbed Anime + Tilpass til skjermen Tøye ut Zoome @@ -202,294 +230,22 @@ Primær Farge App Tema Foretrukket Videoinnhold + Disclaimer - 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. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. - Besetning: %s - %dm - Tøm - Kontoer - %s Ep %d - Spill med CloudStream - Kopier - Episode %d vil bli sluppet om - %dt %dm - %d min - Lukk - Undertekstspråk - %dd %dt %dm - Lagre - Vis Logcat 🐈 - Innstillinger for Chromecast-innstillinger - Blafringsmengde - Gjenopprett data fra sikkerhetskopi - -30 - Dokumentar - Dokumentarer - Asiatiske drama - Direktestrømmer - Andre - Asiatisk drama - Direktestrøm - 127.0.0.1 - Bytt konto - Vurdert - %d/10 - /\?\? - /%d - Anbefalt - Last inn fra fil - Last inn fra Internett - Nedlastet fil - Bakgrunn - Kilde - Tilfeldig - Vurdering: %s - Vis krasjinfo - Status - Størrelse - Støttet - Installer utvidelsen først - HLS-spilleliste - Foretrukket videospiller - Intern spiller - VLC - Alle språk - Hopp over %s - Tøm historikk - Marker som sett - Ja - Nei - Fjern siden - Maks - Film - Last ned undertekster - %s %d%s - Klon siden - Vis tilfeldig-knapp på hjemmesiden - Torrent - Min - Serie - Video - Tilbydere - Tilpasning - Tilfeldig-knapp - Emulator-tilpasning - Opphøyd - konto - Logg ut - Opprett konto - Ingen - Videospor - Legg til sporing - Alle - Beskrivelse - Legg til konto - Normal - MPV - Dobbelttrykk for å sette på pause - +30 - Skygge - 1000 ms - Kommer snart … - Versjon - Språk - Chromecast-undertekster - Autospill neste episode - Historikk - Trykk i midten for å sette på pause - Fant ingen episoder - Fyllstoff - Spill direktestrøm - Importer skrifter ved å plassere den i %s - Start neste episode når den som spiller slutter - Oppdater visningsframdrift - Vis plakater fra Kitsu - Last ned programtillegg automatisk - Tegnefilm - Navn - NGINX-tjenernettadresse - Nedlastingssti - Undertekst-koding - Hei@verden.no - MinKuleSide - eksempel.no - Logg inn - La til %s - Omriss - Nedsenket - Lastet inn %s - Ingen undertekstforsinkelse - Bruk dette hvis undertekster vises %d ms for tidlig - Hovedrolle - Støtterolle - Neste - Referent - Programtillegg slettet - Pakkebrønnsnavn - Pakkebrønnsnettadresse - Legg til pakkebrønn - 18+ - Kunne ikke laste inn %s - Nedlastet: %d - Avskrudd: %d - Ikke nedlastet: %d - Oppdaterte %d programtillegg - Last ned listen over sider du vil bruke - Dette vil også slette alle pakkebrønnsprogramtillegg - Vis gemenskapspakkebrønner - Last ned alle programtilleggene fra denne pakkebrønnen\? - %s (avskrudd) - Spor - Fant ikke programmet - For mye tekst. Kunne ikke lagre i utklippstavlen. - Søk i %s … - Strøm - Metadata tilbys ikke av siden, videoinnlasting vil mislykkes hvis den ikke finnes på siden. - Ser om igjen - Automatiske programtilleggsoppdateringer - Klarte ikke å gjenopprette data fra fil %s - Kunne ikke sikkerhetskopiere %s - passord123 - Språkkode (nb_NO) - Forrige - Krasjrapportering - Utvidelser - %d %s - MittKuleBrukernavn - Hopp over oppsett - Hva du ønsker å se - %s %s - Se videoer på disse språkene - Ferdig - programtillegg - Trygt modus påslått - Sett visningsstatus - Auto-synkroniser din nåværende episode-framdrift - %d-%d - Offentlig liste - programtillegg - %dm -\nigjen - Videooppløsning - Synkroniser undertekster - Undertekstforsinkelse - Sær golfer med kølle vant sexquiz på WC i hjemby - Blu-ray - Ugyldig data - Avspiller - Oppløsning - Plakatbilde - DVD - Oppløsning og navn - Navn - Ugyldig ID - Feil - Endre programutseende for å passe din enhet - Ugyldig nettadresse - Knippe-nedlasting - Slett pakkebrønn - CloudStream har ingen sider installert som forvalg. Du må installere sidene fra pakkebrønner. -\n -\nSom følge av en hjernedød DMCA-forespørsel fra Sky UK Limited 🤮 kan vi ikke lenke til pakkebrønnssiden i programmet. -\n -\nTa del i vår Discord, eller søk på nett. - Bruk dette hvis undertekster vises %d ms for sent - Programtillegg innlastet - Lydspor - Rulletekst - Introduksjon - Lagringstilgang mangler. Prøv igjen. - Vis forfilmer - Dubbingsetikett - Undertekstetikett - Videomellomlagringsstørrelse - Videomellomlagringslengde - Videohurtiglager på disk - Tøm video- og bildehurtiglager - Legg til en klone av en eksisterende side, med en annen nettadresse - Installer alle programtillegg som ikke er installert enda fra de pakkebrønnene som er lagt til. - Kvalitetsetikett - Slå av/på grensesnittselementer på plakat - Hopp over denne oppdateringen - Forårsaker tilfeldige krasj hvis satt for høyt. Ikke endre dette hvis du ikke har lite minne. - @string/home_play - Sikkerhetskopier data - Data lagret - Kunne ikke logge inn på %s - Store bokstaver i undertekster - Utviklere - Nettleser - Sensurerbart - Vev - Lenke til strøm - @string/anime - Skjul valgt videokvalitet i søkeresultater - Lastet inn sikkerhetkopifil - Oppdateringer og sikkerhetskopi - @string/ova - Avslutt\? - Sensurerbart - Alle %s er allerede nedlastet - Kunne ikke installere den nye versjonen av programmet - Installerer ny versjon av programmet … - Laster ned ny versjon av programmet … - Synkroniser - %s innlogget - Forårsaker problemer hvis satt for høyt på enheter med lite lagringsplass, som f.eks. Android TV. - Posisjon for plakatnavn - Blandet begynnelse - Begynnelse - Blandet slutt - Slutt - OVA - Maks. antall tegn for navn i videospiller - Vis oppsprett for å hoppe over begynnelse/slutt - Skru på sensurerbart innhold for støttede tilbydere - Cam - TS - TC - Fjern døveteksting fra undertekster - Minimale undertekster - Ekstra - Filtrer etter foretrukket mediaspråk - Vev-videosending - Tilbakeblikk - SD - Forfilm - Lastet ned %d %s - Navn under plakat - Begynte nedlasting av %d %s … - Bruk etter omstart av programmet - Cam - Cam - HQ - HD - WP - 4K - UHD - HDR - SDR - Alle utvidelser ble skrudd av som følge av et krasj for å hjelpe deg å finne det som lager trøbbel. - Spill forfilm - Lenker - Programoppgraderinger - Utvidelser - Handlinger - Hurtiglager - Håndvendinger - Avspillerfunksjoner - Tilpasning - Forvalg - Utseende - Funksjoner - Gammeldags - PakkeInstallatør - Gjenta oppsettsprosess - Undertekster - Sikkerhetskopi - APK-installatør - Noen enheter støtter ikke den nye pakkeinstallatøren. Prøv gammeldags alternativ hvis ting ikke installeres. - Oppdatering startet - Programtillegg nedlastet - Programmet vil oppgraderes når du avslutter det + 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. + + In case of copyright infringement, please directly contact the responsible parties or the streaming websites. + + The app is purely for educational and personal use. + + CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. + CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or + manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, + user-friendly interface. + + It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index a2a07dd7..6da1cc8f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1,36 +1,38 @@ - Prędkość (%.2fx) Ocena: %.1f - Znaleziono nową aktualizację! -\n%s -> %s + Znaleziono nową aktualizację!\n%s -> %s Filler %d min + %s Odc. %d - Obrazek - Obrazek odcinka - Główny obrazek + Odcinek %d będzie udostępniony + Plakat + Plakat odcinka + Główny plakat Następny losowy Wstecz - Zmień źródło + Zmień dostawcę Pogląd tła + CloudStream Główna Szukaj Pobrane Ustawienia + Szukaj… - Szukaj %s… + Szukaj %s… Brak danych Więcej opcji Następny odcinek - Odcinek %d wyjdzie za Gatunki Udostępnij Otwórz w przeglądarce Pomiń ładowanie Ładowanie… + W trakcie Zawieszone Zakończone @@ -38,6 +40,7 @@ Planowane Brak Ponowne oglądanie + Odtwórz film Odtwórz transmisję na żywo Otwórz torrent @@ -46,6 +49,8 @@ Połącz ponownie… Wstecz Odtwórz odcinek + + Pobierz Pobrane Pobieranie @@ -54,15 +59,19 @@ Błąd przy pobieraniu Anulowano pobieranie Zakończono pobieranie - Odtwórz + + Stream Błąd przy ładowaniu linków Pamięć wewnętrzna + Dub - Nap + Sub + Usuń plik Odtwórz plik Wznów pobieranie Wstrzymaj pobieranie + Wyłącz przekazywanie błędów Więcej informacji Ukryj @@ -79,6 +88,7 @@ Wyczyść Zapisz Prędkość odtwarzania + Ustawienia napisów Kolor tekstu Kolor konturu @@ -88,97 +98,107 @@ Wzniesienie napisów Czcionka Rozmiar czcionki - Szukaj według źródeł + + Szukaj według dostawców Szukaj według typów + Dano %d bananów Brak bananów + Wybierz język automatycznie Pobieranie języków Język napisów - Przytrzymaj, aby zresetować - Importuj czcionki, umieszczając je w %s + Przytrzymaj aby zresetować + Importuj czcionki umieszczając je w %s Kontyntynuj oglądanie + Usuń Więcej informacji + Połączenie przez VPN może być wymagane - To źródło jest torrentem, wskazane jest użycie połączenia VPN + Ten dostawca jest torrentem, wskazane jest użycie połączenia VPN + Metadane nie są dostarczane przez witrynę, ładowanie filmu nie powiedzie się, jeśli nie ma ich w witrynie. + Opis Nie znaleziono opisu Nie znaleziono opisu - Pokaż Logcat 🐈 + + Pokaż logcat 🐈 Obraz-w-obrazie Oglądaj w małym, pływającym okienku - Zmień rozmiar odtwarzacza + Przycisk zmiany rozmiaru Usuwanie czarnych ramek Napisy Ustawienia napisów Napisy Chromecast Ustawienia napisów Chromecast - Tryb Eigengravy + + Tryb Eigengrau Ustawienia prędkości - Przesuń aby przewinąć - Przesuń w lewo lub prawo aby kontrolować czas - Przesuń aby zmienić ustawienia - Przesuń góra-dół z lewej lub prawej aby zmienić jasność i głośność - Autoodtwarzanie następnego odcinka - Rozpocznij następny odcinek po skończeniu bieżącego - Czas przewinięcia przy podwójnym kliknięciu - Podwójne kliknięcie aby przewinąć - Kliknij 2 razy z prawej lub lewej strony aby przewinąć - Kliknij dwukrotnie aby wstrzymać - Kliknij na środku, aby zatrzymać wideo + Przesuwaj aby przeglądać + Przesuwaj w lewo lub prawo aby kontrolować czas + Przesuwaj aby zmienić ustawienia + Przesuwaj po lewej lub prawej stronie aby zmienić jasność i głośność + Podwójne stuknięcie aby przeglądać + Stuknij dwukrotnie, aby wstrzymać + Wielkość skoku przy podwójnym stuknięciu + Stuknij 2 razy z prawej lub lewej strony aby przeglądać + Stuknij na środku, aby wstrzymać Użyj jasności systemowej - Użyj jasności systemowej w odtwarzaczu aplikacji zamiast ciemnej nakładki + Użyj jasności systemowej w odtwarzaczu aplikacji zamiast ciemnej nakładki + + Aktualizuj postęp oglądania Automatycznie synchronizuj postęp aktualnego odcinka Przywracanie danych z kopii zapasowej + Kopia zapasowa danych Wczytano plik kopii zapasowej Nie udało się przywrócić danych z pliku %s Dane zapisane z powodzeniem - Brak uprawnień do pamięci, spróbuj ponownie. + Brak uprawnień do przechowywania, spróbuj ponownien Błąd tworzenia kopii zapasowej %s + Szukaj Konta Aktualizacje i kopia zapasowa + Informacje Zaawansowane wyszukiwanie - Szukaj z podziałem na źródła + Szukaj z podziałem na dostawców Wysyłaj dane tylko przy awariach Nie wysyłaj żadnych danych - Pokaż fillery dla anime + Pokaż odcinek wypełniający dla anime Pokaż zwiastuny - Pokaż obrazki z Kitsu - Ukryj wybraną jakość wideo w wynikach wyszukiwania - Automatyczne aktualizacje rozszerzeń - Automatyczne pobieranie rozszerzeń + Pokaż plakaty z Kitsu Pokazuj aktualizacje - Automatycznie wyszukuj aktualizacje przy starcie + Automatycznie wyszukuj aktualizacji przy starcie Aktualizuj do wersji beta - Wyszukuj wersji beta, zamiast oficjalnych wydań + Wyszukuj wersji beta, zamiast pełnych wydań Github - Aplikacja do light novel od nas - Aplikacja do anime od nas - Serwer Discord + Aplikacja do noweli + Aplikacja do anime + Discord Daj banana programistom Dano banana + Język aplikacji + Ten dostawca nie wspiera Chromecast Nie znaleziono linków Skopiowano do schowka Odtwórz odcinek Zresetowano - Awaria aplikacji. Anonimowe zgłoszenie błędu zostanie wysłane programistom + Awaria aplikacji. Anonimowe zgłoszenie błedu zostanie wysłane programistom + Sezon - %s %d%s Brak sezonu Odcinek Odcinki - %d-%d - %d %s S O + Nie znaleziono odcinków Usuń plik Usuń @@ -186,11 +206,10 @@ Odtwórz -30 +30 - Spowoduje to trwałe usunięcie %s -\nCzy jesteś pewien\? - %dm -\npozostało - Bieżący + Spowoduje to trwałe usunięcie %s\nCzy jesteś pewien? + %dm\npozostało + + Bierzący Zakończone Status Rok @@ -198,144 +217,154 @@ Czas trwania Strona Streszczenie + W kolejce Brak napisów Domyślne + Wolne W użyciu Aplikacja - + Filmy - Seriale telewizyjne + Serial telewizyjny Kreskówki Anime Torrenty Filmy dokumentalne OVA - Dramy azjatyckie + Filmy azjatyckie + Transmisje na żywo - NSFW - Inne - Film Serial telewizyjny Kreskówka Torrent Film dokumentalny - Drama azjatycka - Transmisja na żywo - NSFW - Inne + Film azjatycki + Błąd żródła Zdalny błąd Błąd renderowania Nieoczekiwany błąd odtwarzacza - Błąd pobierania, sprawdź uprawnienia aplikacji - odcinek Chromecast - mirror dla Chromecast + Błąd pobierania, sprawdź uprawnienia + + Chromecast odcinka + Chromecast mirroru Odtwórz w aplikacji Odtwórz w %s Odtwórz w przeglądarce Kopiuj link Automatyczne pobieranie Pobierz mirror - Odśwież linki + Odświerz linki + Pobierz napisy Etykieta jakości Etykieta dubbingu Etykieta napisów Tytuł - Pokaż elementy interfejsu na obrazkach + Włącz elementy interfejsu na plakatach Nie znaleziono aktualizacji - Sprawdź, czy jest aktualizacja + Sprawdź czy jest aktualizacja + Zablokuj - Zmień rozmiar - Źródło + Rozmiar + Źródlo Pomiń OP + Nie pokazuj ponownie Pomiń tę aktualizację Aktualizacja - Domyślna jakość (WiFi) - Maksymalna ilość znaków w tytule odtwarzacza - Rozdzielczość odtwarzacza wideo + Domyślna jakość + Maksymalna ilość znaków tytułu w odtwarzaczu + Zawartość tytułu w odtwarzaczu Rozmiar bufora wideo Długość bufora wideo Pamięć podręczna wideo na dysku Wyczyść pamięć podręczną wideo i obrazów - Ustawienie zbyt wysokiej wartości może powodować problemy na urządzeniach z małą ilością pamięci RAM, takich jak urządzenia Android TV lub stare telefony. - Zbyt wysokie ustawienie może powodować problemy na urządzeniach z małą ilością dostęponej pamięci, takich jak urządzenia Android TV. + + Ustawienie zbyt wysokiej wartości może powodować problemy w systemach z małą ilością pamięci RAM, takich jak urządzenia Android TV lub stare telefony. + Zbyt wysokie ustawienie może powodować problemy w systemach z małą ilością miejsca w pamięci, takich jak urządzenia Android TV + DNS over HTTPS Przydatne w pomijaniu blokad dostawców internetu + Sklonuj stronę Usuń stronę - Dodaj klona istniejącej strony z innym adresem URL + Dodaj klona istniejącej strony z innym adresem url Ścieżka pobierania - URL serwera Nginx - Wyświetlanie Anime z dubbingiem/napisami + Url serwera Nginx + Wyświetlanie Anime z dubbingiem/subbingiem + Dopasuj do ekranu Rozciągnij Powiększ + Zastrzeżenie - Ogólne + Przycisk do losowania Pokaż przycisk do losowania na stronie głównej - Języki źródeł + Języki dostawców + Ogólne Układ aplikacji Preferowane media - Włącz NSFW w obsługiwanych źródłach + Kodowanie napisów - Źródła Układ interfejsu Automatyczny Układ dla telewizorów Układ dla telefonów Układ dla emulatorów + Kolor podstawowy Motyw aplikacji - Pozycja tytułu względem obrazka - Ustaw tytuł pod obrazkiem - Kod języka (pl) - %s %s + + + Pozycja tytułu względem plakatu + Ustaw tytuł pod plakatem konto Wyloguj się - Zaloguj się + Logowanie Przełącz konto Dodaj konto Utwórz konto Dodaj synchronizację Dodano %s Synchronizacja - Oceniono - %d na 10 - Uwierzytelniono %s - Nie udało się uwierzytelnić %s - + + Uwierzytelniono %s + Nie udało się uwierzytelnić w %s Brak Normalne - Wszystko + Wszystkie Maks Min Kontur - Obniżone + Wciśnięte Cień - Wzniesione + Zniesione Synchronizacja napisów - 1000 ms + 1000ms Opóźnienie napisów - Użyj tego, jeśli napisy są wyświetlane %d ms za wcześnie - Użyj tego, jeśli napisy są wyświetlane %d ms za późno + Użyj tego, jeśli napisy są wyświetlane %dms za wcześnie + Użyj tego, jeśli napisy są wyświetlane %dms za późno Brak opóźnienia napisów - Pchnąć w tę łódź jeża lub ośm skrzyń fig + + Pchnąć w tę łódź jeża lub ośm skrzyń fig. + Polecane Załadowano %s Wczytaj z pliku Wczytaj z Internetu Pobrano plik - Główny + Główna Drugoplanowy Pomocniczy + Źródło Losowy + Już wkrótce… Cam Cam @@ -344,47 +373,44 @@ HD TS TC - Blu-ray + BlueRay WP DVD - 4K - SD - UHD - HDR - SDR - Web - Obrazek + Obraz plakatu Odtwarzacz Rozdzielczość i tytuł Tytuł Rozdzielczość - Niepoprawne ID + Niepoprawne id Niepoprawne dane - Niepoprawny URL + Niepoprawny url Błąd - Usuń CC z napisów + Usuń informacje dla niesłyszących z napisów Usuń nadmiarowe informacje z napisów - Filtrowanie wg preferowanego języka + Dalej + Wyświetlaj filmy w wybranych językach + Cofnij + Pomiń + Dostosuj wygląd aplikacji do urządzenia + Preferowany rodzaj filmów + Gotowe Dodatki Zwiastun + Odtwórz w CloudStream + Transmisja na żywo + Link to streamu Odsyłacz - Następny - Wyświetlaj filmy w tych językach - Poprzedni - Pomiń setup - Dostosuj wygląd aplikacji do urządzenia - Zgłaszanie błędów - Co chciałbyś obejrzeć - Gotowe + Przekazywanie błędów + Automatyczne aktualizacje rozszerzeń Rozszerzenia Dodaj repozytorium Nazwa repozytorium - Adres URL repozytorium - Załadowano rozszerzenie - Usunięto rozszerzenie + Adres url repozytorium + Rozszerzenie załadowane + Rozszerzenie usunięte Błąd ładowania %s - 18+ - Rozpoczęto pobieranie %d %s… + +18 + Zaczęto pobieranie %d %s Pobrano %d %s Wszystkie %s już pobrane Pobierz wszystko @@ -392,145 +418,42 @@ rozszerzenia Ta akcja usunie także wszystkie rozszerzenia z repozytorium Usuń repozytorium - Pobierz strony, które Cię interesują + Pobierz strony które Cię interesują Pobrano: %d Wyłączono: %d Nie pobrano: %d - Zaaktualizowano %d rozszerzeń - CloudStream nie ma domyślnie zainstalowanych żadnych witryn. Musisz zainstalować witryny z repozytoriów. -\n -\nZ powodu bezmyślnego usunięcia DMCA przez Sky UK Limited 🤮 nie możemy zamieścić linku do witryny z repozytoriami. -\n -\nDołącz do naszego Discorda lub poszukaj online. + Dodaj repozytorium aby zainstalować rozszerzenia + Ocenione + %d na 10 + Inne + Wideo Zobacz repozytoria społeczności Publiczna lista + Kod języka (en) + Filtrowanie wg preferowanego języka mediów Wszystkie napisy wielką literą - Pobrać wszystkie rozszerzenia z tego repozytorium\? + Pobrać wszystkie rozszerzenia z tego repozytorium? %s (Wyłączone) - Ścieżki + Ukryj wybraną jakość wideo w wynikach wyszukiwania + Włącz NSFW u obsługiwanych dostawców + Dostawcy + Tryb bezpieczny włączony + Wystąpiła nieoczekiwana awaria i automatycznie wyłączyliśmy wszystkie rozszerzenia, abyś mógł znaleźć i usunąć rozszerzenie, które powoduje problemy. + Wyświetl informacje o awarii Ścieżki audio Ścieżki wideo + Ścieżki Zastosuj po ponownym uruchomieniu - Tryb bezpieczny włączony - Z powodu wystąpienia błędu wszystkie rozszerzenia zostały wyłączone, aby ułatwić wykrycie tego wadliwego. - Wyświetl informacje o błędzie + Rozpocznij następny odcinek po zakończeniu bieżącego + Autoodtwarzanie następnego odcinka Ocena: %s Opis - Wersja + Versja Status Rozmiar Autorzy Wspierane Język Najpierw zainstaluj rozszerzenie - Playlista HLS - Preferowany odtwarzacz wideo - Odtwarzacz wewnętrzny - VLC - MPV - Web Video Cast - Przeglądarka - Aplikacja nie została znaleziona - Wszystkie języki - Wyczyść historię - Historia - Za dużo tekstu. Nie można skopiować do schowka. - Link do odtwarzania - Odtwórz w CloudStream - Pomiń %s - %dh %dm - Tak - Nie - Aktualizacja aplikacji nie powiodła się - %dd %dh %dm - Instalowanie aktualizacji aplikacji… - %dm - Oznacz jako obejrzane - Czy na pewno chcesz wyjść\? - Pobieranie aktualizacji aplikacji… - /%d - Obsada: %s - Automatycznie instaluj wszystkie jeszcze niezainstalowane wtyczki z dodanych repozytoriów. - Poster - Podsumowanie - Instalator APK - Niektóre telefony nie obsługują nowego instalatora pakietów. Wypróbuj tryb legacy, jeśli aktualizacje nie zostaną zainstalowane. - password123 - @string/ova - MojaFajnaWitryna - MyCoolUsername - 127.0.0.1 - Tryb kompatybilności - przyklad.pl - /\?\? - Instalator pakietów - @string/home_play - hello@world.com - @string/anime - Opening - Ending - Mixed opening - Napisy końcowe - Intro - Mixed ending - Pokaż wyskakujące okienka pomijania dla niektórych segmentów - Rozszerzenia - Działania - Pamięć podręczna - Powtórz proces konfiguracji - Linki - Aktualizacje - Kopia zapasowa - Napisy - Gesty - Funkcje odtwarzacza - Funkcje - Ustawienia domyślne - Układ - Wygląd - Odtwórz zwiastun - Aplikacja zostanie zaktualizowana po wyjściu - Rozpoczęto aktualizację - Pobrano rozszerzenie - Usuń z obejrzanych - Przeglądarka - Data aktualizacji (od nowego do starego) - Sortuj według - Sortuj - Otwórz za pomocą - Ocena (od najwyższej do najniższej) - Ocena (od najniższej do najwyższej) - Data aktualizacji (od starego do nowego) - Alfabetycznie (od A do Z) - Alfabetycznie (od Z do A) - Wybierz bibliotekę - Biblioteka - Wygląda na to, że twoja biblioteka jest pusta :( -\nZaloguj się na swoje konto lub dodaj programy do swojej lokalnej biblioteki - Wygląda na to, że ta lista jest pusta, spróbuj przełączyć się na inną - Znaleziono plik trybu bezpiecznego. -\nRozszerzenia nie zostaną wczytane, dopóki plik nie zostanie usunięty. - Używana ilość przewijania, gdy widoczny jest odtwarzacz - Ukryty odtwarzacz - ilość przewijania - Android TV - Pokazany odtwarzacz — ilość przewijania - Używana ilość przewijania, gdy ukryty jest odtwarzacz - Dziennik - Uruchom ponownie - Rozpocznij - Nie powiodło się - Ukończone powodzeniem - Serwer pośredniczący raw.githubusercontent.com - Obejścia ISP - Test dostawcy - Zatrzymaj - Przywróć - Aktualizowanie subskrybowanych programów - Zasubskrybowano - Zasubskrybowano %s - Anulowano subskrypcję %s - Został wydany odcinek %d! - Obchodzi blokadę GitHuba za pomocą jsdelivr, może spowodować opóźnienie aktualizacji o kilka dni. - Nie udało się połączyć z GitHub, włączono serwer pośredniczący jsdelivr. - Domyślna jakość (dane mobilne) + Zaaktualizowano %d rozszerzeń diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings-pt.xml similarity index 51% rename from app/src/main/res/values-pt/strings.xml rename to app/src/main/res/values-pt/strings-pt.xml index dd722f62..375c3193 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings-pt.xml @@ -1,74 +1,79 @@ - - - %s Ep %d - %dh %dm - %dm - O episódio %d será lançado em + + + + Poster - Pôster do episódio - Poster - Pôster Principal - Próximo Aleatório - Voltar - Alterar Provedor - %dd %dh %dm - Fonte - Resolução - Extras - Visualizar plano de fundo + @string/result_poster_img_des + Episode Poster + Main Poster + Next Random + Go back + Change Provider + Preview Background + + Velocidade (%.2fx) Classificado: %.1f - Nova atualização encontrada! -\n%s -> %s - Preenchimento + Nova atualização encontrada!\n%s -> %s + Enchimento + CloudStream - Assistir com o CloudStream + Reproduzir com CloudStream Início - Pesquisar - Downloads - Configurações + Pesquisa + Transferências + Opções + Procurar… - Pesquisar %s… - Sem dados - Mais opções + Procurar em %s… + + Sem Dados + Mais Opções Próximo episódio - Gêneros - Compartilhar - Abrir no navegador - Pular carregamento + Géneros + Partilhar + Abrir no Navegador + Saltar Carga Carregando… + Assistindo - Em espera + Em Espera Concluído - Desistido - Pretendo assistir - Nenhum - Reassistindo - Reproduzir filme - Reproduzir transmissão ao vivo + Abandonado + Planeio Assistir + Nenhuma + Assistindo de Novo + + Reproduzir Filme + Reproduzir Livestream Transmitir Torrent Fontes Legendas - Tentar conexão novamente… - Voltar - Reproduzir episódio - Download - Baixado - Baixando - Download Pausado - Download Iniciado - Falha no Download - Download cancelado - Download concluído - Stream + Voltar a tentar ligação… + Voltar Atrás + Reproduzir Episódio + + Transferir + Transferido + A Transferir + Transferência em Pausa + Transferência Iniciada + Transferência Falhou + Transferência Cancelada + Transferência Completa + Stream + Erro a Carregar Links Armazenamento Interno + Dob Leg + Eliminar Ficheiro Reproduzir Ficheiro Retomar Transferência Pausar Transferência + Desativar relatório automático de erros Mais info Esconder @@ -79,11 +84,13 @@ Remover Aplicar Cancelar - Copiar + Copiar Fechar Limpar Guardar + Velocidade de Reprodução + Configurações de Legendas Cor do Texto Cor do Delineado @@ -93,72 +100,91 @@ Elevação da Legenda Fonte Tamanho da Fonte + Procurar usando fornecedores Procurar usando tipos + %d Benenes dadas aos devs Nenhum Benene dada + Auto-seleção de Idioma Transferir Idiomas - Idioma da Legenda + Idioma da Legenda Segure para retornar para o padrão - Importar fontes colocando em %s + Importar fontes colocando em %s Continuar a Assistir + Remover Mais info + Uma VPN pode ser necessária para que este fornecedor funcione corretamente Este fornecedor é um torrent, uma VPN é recomendada - Metadados não são oferecidos pelo site, o carregamento do vídeo irá falhar se ele não existir no site. - Descrição + + Metadados não são oferecidos pelo site, o carregamento do vídeo irá falhar se ele não existir no site. + + Descrição Nenhum Enredo Encontrado Nenhuma Descrição Encontrada - Mostrar logcat 🐈 + + Mostrar logcat 🐈 + Picture-in-picture Continua a reprodução num player em miniatura em cima de outras apps Botão de redimensionamento do player Remover as bordas negras Legendas Configurações de legendas do player - Legendas do Chromecast + Legendas do Chromecast Configurações de legendas do Chromecast + Modo Eigengravy Acrescenta uma opção de velocidade no player Deslize para andar - Deslize para os lados para controlar a posição em um vídeo + Deslize para a esq. ou dir. para controlar o tempo no player Deslize para mudar as configurações - Deslize para cima ou para baixo, no lado esquerdo ou direito, para ajustar brilho ou volume - Reproduzir automaticamente próximo episódio + Deslize do lado esq. ou dir. para ajustar brilho ou volume + +Reproduzir automaticamente próximo episódio Começa o próximo episódio quando o atual termina + Toque duplo para avançar - Toque duplo para pôr em pausa - Tempo de busca no player (Segundos) + Toque duplo para pôr em pausa + Segundos avançados no player Toque duplo no lado esq. ou dir. para andar para trás ou para a frente - Toque duas vezes no meio para pausar + Toque no meio para pôr em pausa Usar brilho da sistema Usar brilho do sistema no player em vez de uma sobreposição escura - Atualizar progresso + + Atualizar progresso Sincronizar automaticamente o progresso do seu episódio atual + Restaurar dados a partir de backup + Fazer backup Arquivo de backup carregado Falha ao restaurar dados do ficheiro %s Dados guardados com sucesso - Permissão de armazenamento não encontrada, por favor tente novamente. + Permissões de armazenamento em falta, por favor tente de novo Erro no backup de %s + Procurar - Contas + Contas Atualizações e backup + Info Procura Avançada Mostra resultados separados por fornecedor Só envia dados sobre falhas Não envia nenhum dado Mostrar episódios de enchimento para anime - Mostrar trailers + Mostrar trailers Mostrar posters do kitsu - Esconder qualidades de vídeo selecionadas nos resultados da Pesquisa - Atualizações de plugin automáticas + + Esconder qualidades de vídeo selecionadas nos resultados da Pesquisa + Atualizações de plugin automáticas + Mostrar atualizações da app - Procurar automaticamente por novas atualizações depois de iniciar o app. + Procurar novas atualizações automaticamente ao iniciar Atualizar para pré-lançamentos Procura atualizações de pré-lançamento em vez de só lançamentos oficiais Github @@ -166,13 +192,18 @@ Junte-se ao Discord Dar um benene aos devs Benene dada + Idioma da App + Este fornecedor não tem suporte para Chromecast Nenhum Link Encontrado Link copiado para a área de transferência Reproduzir episódio Restaurar para o padrão - Desculpe, a aplicação falhou. Um relatório de erro anónimo será enviado para os desenvolvedores + Desculpe, a aplicação falhou. Um relatório de erro anónimo será enviado para os + desenvolvedores + + Temporada Nenhuma Temporada Episódio @@ -180,14 +211,14 @@ T E Nenhum Episódio encontrado + Eliminar Ficheiro Eliminar Pôr em Pausa Retomar - Isto apagará %s permanentemente -\nTem a certeza\? - %dm -\nem falta + Isto apagará %s permanentemente\nTem a certeza? + %dm\nem falta + Em Curso Concluído Estado @@ -196,36 +227,44 @@ Duração Site Sinopse + Na fila Sem Legendas Padrão + Livre Usado App + + Filmes Séries Desenhos Animados Anime Torrents - Documentários - OVA + Documentários + OVA Dramas Asiáticos Transmissões em Direto NSFW Outros - Filme - Série - Desenho Animado - Torrent - Documentário - Drama Asiático - Transmissão em Direto + + + Filme + Série + Desenho Animado + Torrent + Documentário + Drama Asiático + Transmissão em Direto NSFW + Erro de fonte Erro remoto Erro de renderização Erro inesperado do player Erro de transferência, verifique permissões de armazenamento + Episódio pelo Chromecast Alternativa pelo Chromecast Reproduzir na app @@ -235,82 +274,118 @@ Transferência Automática Transferir por servidor alternativo Recarregar links - Transferir legendas - Etiqueta de qualidade + Transferir legendas + + Etiqueta de qualidade Etiqueta Dub Etiqueta Sub Título Alternar elementos da interface no póster + Nenhuma Atualização Encontrada Procurar Atualização + Fixar Mudar Tamanho Fonte Saltar OP + Não mostrar de novo - Saltar esta Atualização + Saltar esta Atualização Atualizar - Qualidade Preferida (WiFi) - Máximo de caracteres do título no player de video + Qualidade Preferida + Máximo de caracteres do título de vídeos Resolução do player de vídeo - Tamanho do buffer do vídeo + + Tamanho do buffer do vídeo Comprimento do buffer do vídeo Cache do vídeo em disco Limpar cache de vídeo e imagem - Causará travamentos em dispositivos com pouca memória se definido muito alto , como uma Android TV. - Pode causar problemas em sistemas com pouco espaço de armazenamento se definido muito alto, como uma Android TV. + + Causará travamentos aleatórios se definido muito alto. Não mude se tiver pouca memória RAM, como um Android TV ou um telefone antigo + Pode causar problemas em sistemas com pouco espaço de armazenamento se definido muito alto, como em dispositivos Android TV + DNS sobre HTTPS Útil para contornar bloqueios do fornecedor de internet - Clonar site + + Clonar site Remover site Adiciona um clone de um site existente, com um url diferente - Caminho de transferência + + Caminho de transferência + Url do servidor Nginx + Mostrar Anime Dobrado/Legendado + Ajustar para a Tela Esticar Aumentar - Aviso Legal - Geral - Botão Aleatório - Mostra o botão Aleatório na página inicial, que pode escolher aleatoriamente um filme ou série + + Aviso Legal + Quaisquer questões legais relativas ao conteúdo desta aplicação + devem ser levados em conta com os próprios anfitriões e fornecedores de arquivos, pois não somos afiliados a eles. + + Em caso de violação de direitos autorais, favor contatar diretamente as partes responsáveis ou os sites de streaming. + + O aplicativo é puramente para uso educacional e pessoal. + + O CloudStream 3 não hospeda nenhum conteúdo no aplicativo, e não tem controle sobre qual mídia é colocada ou retirada. + O CloudStream 3 funciona como qualquer outro mecanismo de busca, como o Google. O CloudStream 3 não hospeda, não faz upload ou + gerenciar qualquer vídeo, filme ou conteúdo. Ele simplesmente rasteja, agrega e exibe links em um conveniente, + interface amigável para o usuário. + + Ela apenas raspa sites de terceiros que podem ser acessados publicamente através de qualquer navegador web normal. É o + responsabilidade do usuário de evitar qualquer ação que possa violar as leis que regem sua localidade. Utilizar + CloudStream 3 por sua própria conta e risco. + + Geral + Botão Aleatório + Mostra o botão Aleatório na página inicial Idioma dos fornecedores Layout da App Mídia preferida - Ativar NSFW em fornecedores compatíveis - Codificação das legendas - Fornecedores +Ativar NSFW em fornecedores compatíveis +Fornecedores + Codificação das legendas Layout + Auto Layout de TV Layout de telemóvel - Layout de emulador + Layout de emulador + Cor Primária Tema do App - Local do título do poster + Local do título do poster Coloca o título debaixo do poster - senha123 + + + senha123 MeuNomeFixe ola@mundo.com 127.0.0.1 MeuSiteFixe examplo.com Codigo da Língua (pt) + Conta Sair Entrar Mudar de conta Adicionar conta - Criar conta + Criar conta Adicionar sincronização %s adicionado Sincronizar Nota %d / 10 - /\?\? + /?? /%d %s autenticado Falha em autenticar para %s + + Nenhuma Normal Tudo @@ -320,13 +395,20 @@ Deprimido Sombreado Em Relevo - Sincronizar legendas - Atraso de legenda + Sincronizar legendas + Atraso de legenda Use isto se as legendas forem mostradas %dms adiantadas Use isto se as legendas forem mostradas %dms atrasadas Sem atraso de legenda + Luís argüia à Júlia que «brações, fé, chá, óxido, pôr, zângão» eram palavras do português - Recomendada + + Recomendada %s carregada Carregar de arquivo Carregar da Internet @@ -334,24 +416,34 @@ Protagonista Coadjuvante Figurante + + Fonte Aleatório + Em breve… - Imagem de Poster + + Imagem de Poster Player Resolução e título Título + Resolução Id inválida Dado inválido - URL inválido + +URL inválido Erro Remover legendas ocultas(CC) das legendas Remover bloat das legendas - Filtrar por linguagem preferida + +Filtrar por linguagem preferida + Extras Trailer + Próximo Ver vídeos nestas linguagens Anterior Saltar setup + Change the look of the app to suit your device Crash reporting What do you want to see @@ -363,7 +455,8 @@ Plugin Carregado Plugin Apagado Falha ao carregar %s - Download iniciado %d %s… + + Iniciada a transferência %d %s Transferido %d %s com sucesso Tudo %s já transferido Transferência em batch @@ -375,158 +468,11 @@ Transferido: %d Desativado: %d Não transferido: %d - O CloudStream não possui sites instalados por padrão. Você precisa instalar os sites a partir de repositórios. -\n -\nDevido a uma restrição sem sentido de direitos autorais (DMCA) pela Sky UK Limited 🤮 não podemos vincular o site do repositório no aplicativo. -\n -\nJunte-se ao nosso Discord ou pesquise online. + Adicionar um repositório para instalar extensões de sites Ver repositórios da comunidade Lista pública Todas as legendas em maiúsculas - Transferir todos os plugins deste repositório\? + + Transferir todos os plugins deste repositório? %s (Desativado) - Instalador APK - %d min - Assistir Trailer - Marcar como visto/não visto - Reproduzir - Instalar automaticamente todos os plugins ainda não instalados dos repositórios adicionados. - Baixar extensões automaticamente - Refazer o processo de configuração - -30 - Vídeo - +30 - %s %d%s - Elenco: %s - Atualização iniciada - Log - Alguns aparelhos não suportam o novo instalador de pacotes. Use a opção legado caso não esteja conseguindo atualizar. - %d-%d - %d %s - Iniciar - Falha - Sucesso - Biblioteca - Navegar - Aplicativo de Anime pelos mesmos desenvolvedores - Ova - Anime - Player visível - Procurar valor - Instalando atualização do app… - Você tem certeza que deseja sair\? - Versão - Encerramento - Limpar histórico - Abertura - Não - Ordenar por - Sim - Baixando atualização do app… - Episódio %d lançado! - Créditos - Descrição - Tamanho - Parar - Modo seguro ligado - Histórico - Ordenar - Player interno - Autores - Suportado - Idioma - Instalar a extensão primeiro - Playlist HLS - Player de vídeo preferido - Estado - Gestos - Faixas - WP - Cam - Abertura - Selecionar Biblioteca - Usando jsdelivr o bloqueio do GitHub pode ser contornado. Pode atrasar atualizações em alguns dias. - VLC - Todas as linguagens - Atualizado (Novo para Antigo) - Inscrito - HDR - Reiniciar - Navegador Web - Atualizado (Antigo para Novo) - Web Video Cast - DVD - Instalador de pacotes - MPV - Remover dos assistidos - Não foi possível instalar a nova versão do aplicativo - Inscrição cancelada em %s - Final misto - Avaliações (Decrescente) - Aplicar ao reiniciar - Referente - Player oculto - Quantidade de Busca - raw.githubusercontent.com Proxy - Blu-ray - Aparência - 1000 ms - SDR - 18+ - Abrir com - Teste de provedor - UHD - Ver informações sobre falha - Aplicativo não encontrado - Reverter - Link para transmitir - Plugins baixados - %d plugins atualizados - Pular %s - Abertura mista - Alfabético (Z a A) - Parece que esta lista está vazia, tente trocar para outra - Inscrito em %s - 4K - Faixas de vídeo - O aplicativo será atualizado ao sair - Atualizando shows inscritos - Alfabético (A a Z) - Avaliações (Crescente) - Parece que a sua biblioteca está vazia :( -\nFaça login em uma conta de biblioteca ou adicione shows à sua biblioteca local - Arquivo de modo de segurança encontrado! -\nNenhuma extensão será carregada na inicialização do app até que o arquivo seja removido. - Contorno do provedor de serviço de internet (ISP) - Links - Recursos do Player - Recursos - Atualizações de aplicativos - Qualidade Preferida (Dados Móveis) - Quantidade de busca (em segundos) usada quando o player de video está visível - Quantidade de busca (em segundos) usada quando o player de video está oculto - Falha ao conectar com GitHub, ativando proxy jsdelivr. - Cache - Android TV - Legendas - %s %s - TS - Cam - Cam - HQ - HD - TC - Web - Nota: %s - Legado - Todas as extensões foram desativadas devido a uma falha para ajudá-lo a encontrar a que está causando o problema. - Recapitular - Mostrar pop-ups para pular abertura/encerramento - Muito texto. Não é possível salvar na área de transferência. - Marcar como assistido - Backup - Extensões - Ações - Layout - Configurações padrão - SD - Faixas de áudio diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index aa443783..ce8f328c 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -1,4 +1,3 @@ - @@ -8,29 +7,33 @@ %dd %dh %dm %dh %dm %dm + Poster - Poster + @string/result_poster_img_des Poster Episod Poster Principal Următorul la Întâmplare Înapoi Schimbă furnizorul Previzualizare în fundal + Viteză (%.2fx) Evaluare: %.1f - Actualizare nouă găsită! -\n%s -> %s + Actualizare nouă găsită!\n%s -> %s Filler %d min + Prima pagină Căutare Descărcări Setări + Căutare… Căutați %s... + Fără date Mai multe opțiunii Episodul următor @@ -39,6 +42,7 @@ Deschide în browser Săriți încărcarea Se încarcă... + În curs de vizualizare În așteptare Finalizat @@ -46,6 +50,7 @@ Planificare pentru a urmări Anuleaza Reurmat + Urmărește Stream Torrent Surse @@ -53,6 +58,7 @@ Încercați să vă conectați din nou... Înapoi Redă episodul + Descărcare Descărcat Se descarcă @@ -62,14 +68,18 @@ Descărcare anulată Descărcare finalizată Stream + Eroare la încărcarea linkurilor Stocare internă + Dub Sub + Șterge fișierul Redare fișier Continuați descărcarea Opriți descărcarea + Dezactivați raportarea automată a erorilor Mai multe informații Ascunde @@ -85,7 +95,9 @@ Închide Elimină Salvează + Viteză video + Setări de subtitrare Culoarea textului Culoare de contur @@ -95,26 +107,35 @@ Elevație subtitlu Font Dimensiune + Căutare în funcție de furnizor Căutare în funcție de tip + %d banane oferite dezvoltatorilor Nu s-au oferit banane + Selectare automată a limbii Descarcă limbi străine Limba subtitrării Țineți apăsat pentru a reseta la valorile implicite Importați fonturi prin introducerea lor în %s Continuați să urmăriți + Eliminați Mai multe informații @string/home_play + Există probabilitatea necesitații unui VPN pentru ca acest furnizor să funcționeze corespunzător Acest furnizor este un torrent, se recomandă un VPN + Metadatele nu sunt furnizate de către site, există posibilitatea ca încărcarea videoclipului să eșueze. + Descriere Scenariul este indisponibil Descrierea este indisponibilă + Arată Logcat 🐈 + Picture-in-Picture Continuă redarea într-un mini-player peste alte aplicații Buton de redimensionare video @@ -123,6 +144,7 @@ Setări de subtitrare Subtitrări Chromecast Setări pentru subtitrare Chromecast + Modul Eigengravy Adăugați opțiunea de viteză în player Derulați spre înainte/înapoi @@ -136,18 +158,23 @@ Atingeți dublu pentru a căuta Utilizați luminozitatea sistemului Utilizați luminozitatea sistemului în playerul aplicației în loc de o suprapunere întunecată + Sincronizează automat episoadele vizionate + Recuperează datele din copia de rezervă + Copie de rezervă a datelor Fișier de rezervă încărcat Imposibilitatea de a restaura datele din %s - Date stocate - Permisiunea de arhivare lipșe, vă rugăm să încercați din nou. + Datele au fost salvate cu succes + Permisiuni de arhivare lipsă, vă rugăm să încercați din nou Eroare de backup %s + Căutare Conturi și credite Actualizări și copii de rezervă + Informații Căutare avansată Împărțiți rezultatele căutării în funcție de furnizor @@ -155,24 +182,28 @@ Nu trimiteți niciun fel de date Afișează etichetele [filler] pentru anime Arată trailerul - Arată afișele de la Kitsu + Arată posterele de la Kitsu + Afișați actualizările aplicației Căutați automat noi actualizări la pornire Actualizați la prerelease Căutați actualizări ale versiunilor preliminare în loc să căutați doar versiunile complete GitHub - >Aplicație Light Novel realizată de aceiași dezvoltator + >Aplicație Light Novel realizată de aceiași dezvoltator Aplicație Anime realizată de aceiași dezvoltatorilor Alăturați-vă pe Discord Oferiți dezvoltatorilor o banană Oferă o banană + Limba aplicației + Acest furnizor nu are suport pentru Chromecast Nu s-au găsit link-uri Link copiat în clipboard Redare episod Restabilirea la valorile implicite Ne pare rău, aplicația s-a blocat. Un raport de eroare anonim va fi trimis dezvoltatorilor + Sezonul Nu există sezon Episodul @@ -180,16 +211,16 @@ S E Nu s-au găsit episoade + Ștergeți fișierul Ștergeți Pauză Continuă -30 +30 - Sunteți pe cale să ștergeți definitiv %s -\nSunteți sigur\? - %dm -\nrămas + Sunteți pe cale să ștergeți definitiv %s\nSunteți sigur? + %dm\nrămas + În curs de desfășurare Finalizat Status @@ -198,12 +229,15 @@ Durată Site Rezumat + În coada de așteptare Nu există subtitrare Implicit + Liber Folosit Aplicație + Filme Seriale TV @@ -213,6 +247,7 @@ Torrent Documentare Drame asiatice + Film Serie TV @@ -222,11 +257,13 @@ Torrent Documentar Drama asiatică + Eroare de sursă Eroare de la distanță Eroare de randare Eroare neașteptată a playerului Eroare de descărcare; verificați permisiunile de stocare + Chromecast Chromecast alternativ Redă în Aplicație @@ -237,42 +274,72 @@ Descărcă prin Alernativă Reîncărcare link-uri Descărcare subtitrări + Etichetă de calitate Etichetă Dub Etichetă Sub Titlu Etichete de pe postere + Nu s-au găsit actualizări Verifică pentru actualizări + Blocare Redimensionare Sursă Săriți peste intro + Nu se mai arată din nou Treci peste această actualizare Actualizare Rezoluția preferată Limitarea caracterelor de titlu în player Rezoluția playerului video + Dimensiunea cache-ului video Lungimea buffer-ului video Dimensiunea cache-ului video pe disc Ștergeți memoria cache de imagine și video + Dacă este setat la un nivel prea ridicat, poate cauza probleme pe sistemele cu prea puțină memorie RAM. Cum ar fi dispozitivele Android TV sau telefoanele mai vechi Dacă este setat la un nivel prea ridicat, poate cauza probleme pe sistemele cu spațiu de stocare intern redus. Ca și dispozitivele Android TV + DNS peste HTTPS Folositor pentru evitarea blocajelor ISP + Adaugați site-ul Eliminați site-ul Adăugați o copie a unui site existent, cu o adresă URL diferită + Locul descărcării + Adresa URL a serverului Nginx + Afișarea anime-urilor dublate/subtitrate + Adaptare la ecran Întindere Mărire + + Aviz juridic (declinarea responsabilității și drepturi de autor) - Orice probleme legale privind conținutul acestei aplicații ar trebui să fie rezolvate cu furnizorii și gazdele actuale de fișiere, întrucât noi nu suntem afiliați cu aceștia. În caz de încălcare a drepturilor de autor, vă rugăm să contactați direct părțile responsabile sau site-urile de streaming. Aplicația este destinată exclusiv utilizării educaționale și personale. CloudStream 3 nu găzduiește niciun fel de conținut în aplicație și nu are niciun control asupra conținutului media care este pus sau retras. CloudStream 3 funcționează ca orice alt motor de căutare, cum ar fi Google. CloudStream 3 nu găzduiește, nu încarcă și nu gestionează niciun videoclip, film sau conținut. Pur și simplu navighează, adună și afișează linkuri într-o interfață convenabilă și ușor de utilizat. Pur și simplu, acesta extrage paginile web ale unor terțe părți care sunt accesibile publicului prin intermediul oricărui browser web obișnuit. Este responsabilitatea utilizatorului de a evita orice acțiune care ar putea încălca legile care guvernează locația sa. Utilizați CloudStream 3 pe propria răspundere. + Orice probleme legale privind conținutul acestei aplicații ar trebui + să fie rezolvate cu furnizorii și gazdele actuale de fișiere, întrucât noi nu suntem afiliați cu aceștia. + + În caz de încălcare a drepturilor de autor, vă rugăm să contactați direct părțile responsabile sau site-urile de streaming. + + Aplicația este destinată exclusiv utilizării educaționale și personale. + + CloudStream 3 nu găzduiește niciun fel de conținut în aplicație și nu are niciun control asupra conținutului media care este pus sau retras. + CloudStream 3 funcționează ca orice alt motor de căutare, cum ar fi Google. CloudStream 3 nu găzduiește, nu încarcă și + nu gestionează niciun videoclip, film sau conținut. Pur și simplu navighează, adună și afișează linkuri într-o interfață convenabilă + și ușor de utilizat. + + Pur și simplu, acesta extrage paginile web ale unor terțe părți care sunt accesibile publicului prin intermediul oricărui browser web obișnuit. Este + responsabilitatea utilizatorului de a evita orice acțiune care ar putea încălca legile care guvernează locația sa. Utilizați + CloudStream 3 pe propria răspundere. + + General Aleatoriu Afișați butonul Aleatoriu pe pagina de start @@ -281,14 +348,17 @@ Media preferată Codificarea subtitrărilor Interfața utilizatorului + Auto Dispunere TV Dispunere telefonică Dispunerea emulatorului + Culoare primară Tema Locația titlului posterului Locația titlului de pe poster + parola123 David Popovici @@ -297,6 +367,7 @@ David Popovici davidpopovici.com Cod limbă (RO) + %s %s Cont Deconectare @@ -309,10 +380,11 @@ Sincronizare Recenzie %d / 10 - /\?\? + /?? /%d %s autentificat Imposibil de autentificat la %s + Nu există Normal @@ -336,6 +408,7 @@ https://en.wikipedia.org/wiki/The_quick_brown_fox_jumps_over_the_lazy_dog --> Vând muzică de jazz și haine de bun-gust în New-York și Quebec la preț fix. + Recomandări A fost încărcat %s Încărcați din fișier @@ -344,9 +417,12 @@ Protagonist Suport Secundar + Sursa Aleatoriu + În curând + Cam Cam Cam @@ -354,7 +430,7 @@ HD TS TC - Blu-ray + BlueRay WP DVD 4K @@ -363,6 +439,7 @@ HDR SDR Web + Poster Player Titlu și rezoluție @@ -375,18 +452,4 @@ Trailer - Istoric - Marcare ca vizionat - Redă automat următorul episod - CloudStream - Vizionează trailerul - Actualizarea a început - Actualizați progresul ceasului - Începe următorul episod când se termină episodul curent - Ascundeți calitatea video selectată în rezultatele căutării - Redare Livestream - Librărie - Log - Browser - Joacă cu CloudStream diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml deleted file mode 100644 index 9d8f6895..00000000 --- a/app/src/main/res/values-ru/strings.xml +++ /dev/null @@ -1,532 +0,0 @@ - - - История - Нет - Да - Треки - В очереди - Приблизить - Загрузить - Поиск - Заполнить - Скачать неудачный - Подогнать - Удалить - Все - Пауза - Актёрский состав: %s - Название источника - Войти - Нет - Название - Загрузка… - Статус - Выйти - Серия %d будет выпущен в - Плакат - Плакат - Плакат эпизода - Главный плакат - Следующий случайный - Вернуться - Изменить поставщика - Предпросмотр фона - Скорость (%.2fx) - Оценили: %.1f - Новое обновление найдено! -\n%s -> %s - Заполнитель - CloudStream - Убирать - %s Ep %d - Смотреть с CloudStream - Главная - Поиск - Загрузки - Настройки - Поиск… - Поиск %s… - Нет данных - Дополнительные опции - Следующий эпизод - Жанры - Поделиться - Открыть в браузере - Пропустить загрузку - Просмотр - Приостановленно - Завершено - Брошенный - План посмотреть - Нет - Пересмотрю - Смотреть фильм - Смотреть трейлер - Смотреть Livestream - Источники - Субтитры - Смотреть эпизод - Повторная попытка подключение… - Вернуться - Скачано - Скачивание - Скачать остановлена - Скачать начатый - Скачать отменённый - Скачать выполнено - Инфо - Обновление началось - Прятать - Смотреть - Подробнее - Фильтр закладки - Закладки - Применить - Отмена - Копия - Закрыть - Очистить - Сохранить - Скорость проигрыватель - Воспроизвести Эпизод - %dд %dч %dм - %d мин. - Dub - Sub - Установите смотреть состояние - Цвет контура - Цвет текста - Настройки субтитров - Цвет фона - Цвет окна - Тип края - Субтитр подъём - Поиск с использованием поставщиков - Поиск с использованием типов - %d Бенены данность на разрабы - Бенены не дают - Автовыбор языка - Скачать языки - Язык субтитров - Удерживайте, чтобы сбросить по умолчанию - Ошибка загрузки ссылок - Поток - Шрифт - Размер шрифта - Удалить файл - Воспроизвести файл - Внутренняя память - Продолжить Скачать - Остановить скачивание - Отключить автоматическое информирование об ошибках - Импортируйте шрифты поместив их в %s - Продолжить смотреть - Удалите - Подробнее - Для корректной работы этого поставщика может потребоваться VPN - Этот поставщика - торрент, рекомендуется VPN - Метаданные не предоставляются сайтом, загрузка видео будет неудачной, если они не существуют на сайте. - Описание - Сюжет не найден - Описание не найдено - Показать Logcat 🐈 - Картинка в картинке - Продолжение воспроизведения в миниатюрном проигрывателе поверх других приложений - Кнопка изменения размера проигрывателя - Удалите черные границы - Субтитры - Настройки субтитров проигрывателя - Субтитры Chromecast - Настройки субтитров Chromecast - Режим Eigengravy - Добавляет опцию скорости в проигрывателе - Проведите пальцем для поиска - Проведите пальцем для изменения настроек - Проведите вверх или вниз по левой или правой стороне, чтобы изменить яркость или громкость - Автопроиграть следующего серия - Поток торрент - Проведите пальцем из стороны в сторону, чтобы управлять свое место в видеоролике - Начните следующий серию, когда закончится текущий - Загружена резервная копия - Не удалось восстановить данные из %s - Отсутствует разрешение на хранение. Пожалуйста попробуйте снова. - Аккаунты - Обновления и резервное - Информация - Расширенный поиск - Показывать трейлеры - Скрыть выбранное качество видео в результатах поиска - Автоматическое обновление плагинов - Автоматическая загрузка плагинов - Показать обновления приложения - Автоматически проверять обновления при старте приложения. - Обновится до пре-релиза - APK установщик - Github - Язык приложения - Ссылок не найдено - Ссылка скопирована в буфер обмена - Восстановить по умолчанию - Извините, приложение прекратило работу. Анонимный отчет об ошибке будет отправлен разработчикам - Эпизод - Эпизодов - С - Э - Эпизоды не найдены - Удалить файл - Продолжить - -30 - +30 - Это будет удалено безвозвратно%s -\nВы уверены\? - %d мин. -\nосталось - Завершено - Год - Рейтинг - Продолжительность - Нет субтитров - По умолчанию - Приложение - Аниме - Торренты - Другое - Ошибка загрузки, проверьте разрешения хранилища - Копировать ссылку - Автоскачивание - Загрузка. Зеркало - Сезон - Аниме приложение от тех же разработчиков - Автоматически загружать еще не установленные плагины из добавленных репозиториев. - Присоединится в Discord - Бесплатно - %d мин. - %d ч. %d мин. - Фильмы - Мультфильм - Сериалы - Азиатские драмы - Видео - Мультфильмы - Документальные фильмы - OVA - NSFW - NSFW - Фильм - Сериал - Торрент - Документальный - Азиатская драма - Общие - Провайдеры - Макет - Расширения - Плеер - Резервное копирование данных - Отправлять данные только при вылетах - Использовано - Двойное нажатие для паузы - Коснитесь дважды правой или левой стороны для поиска вперед или назад - Нажмите дважды в центре, чтобы сделать паузу - Использовать системную яркость - Автоматически синхронизировать текущий прогресс эпизода - Ошибка резервного копирования %s - Восстановить данные из резервной копии - Показывать Филлер эпизоды для аниме - Показывать афиши из Kitsu - Некоторые телефоны не поддерживают новые установщики. Попробуйте использовать legacy (старый) вариант, если обновления не устанавливаются. - Выходит - Блокировка - Обновление не найдено - Изменить размер - Источник - Проверить обновления - Клон сайта - DNS через HTTPS - Удалить сайт - Оговорка - Синхронизация субтитров - Добавить клон существующего сайта с другим URL-адресом - Используется для обхода блокировок интернет провайдера - Путь скачивания - Давал бенен - Обновить - Основной цвет - Языки поставщиков - Название репозитория - Очистить историю - Referer - Дайте бенен разрабам - Ссылки - Макет - Макет приложения - Тема приложения - Добавить репозиторий - Убрать отметку - Вы уверены, что хотите выйти\? - Плагин скачан - Плагин удалён - Описание - Версия - Статус - Размер - Авторы - Поддерживается - VLC - MPV - Пропустить %s - Концовка - Используйте яркость системы в проигрывателе приложения вместо темного наложения - Обновить состояние хода просмотра - Данные сохранены - Показывает результаты поиска, разделенные по провайдеру - Поиск предварительных обновлений вместо полных выпусков - Повторить процесс настройки - Этот провайдер не поддерживает Chromecast - %s %d%s - Нет сезона - %d-%d - %d %s - Прямые трансляции - Прямая трансляция - Ошибка источника - Ошибка пульта - Ошибка рендера - Неожиданная ошибка плеера - Эпизод Chromecast - Воспроизведение на %s - Воспроизвести в браузере - Скачать субтитры - Знак качества - Переключение элементов интерфейса на плакате - Пропустить OP - Больше не показывать - Пропустить это обновление - URL сервера NGINX - Создать учётную запись - Добавить слежение - Добавлено %s - Синхронизировать - Оценено - %d из 10 - Посмотреть информацию о сбое - Предпочитаемый видеоплеер - Веб-браузер - Приложение не найдено - Все языки - Вступление - Титры - Отметить как просмотренное - Разрешение видеоплеера - Предпочтительное качество видео (WiFi) - Максимум символов - Длинна буфера - Кеш видео на диске - Размер буфера - Отчистить кеш видео и изображений - Вызывает сбои, если установлено слишком высокое значение на устройствах с небольшим объемом памяти, таких как Android TV. - Вызывает проблемы, если установлено слишком высокое значение на устройствах с небольшим объемом памяти, таких как Android TV. - Легкая новелла от тех же разработчиков - Язык - Плейлист HLS - Сначала установить расширение - Внутренний проигрыватель - Синопсис - Интро - hello@world.com - Зеркало Chromecast - Воспроизвести в приложении - По умолчанию - Возможности плеера - Субтитры - Расположение названия плаката - ТV (телевидение) - Телефон - Эмулятор - Под плакатом - parol123 - МоёИмяПользователя - Сменить учётную запись - Добавить учётную запись - МойКрутойСайт - example.com - Код языка (ru) - учётная запись - Автоматически - 127.0.0.1 - Обновления приложения - Резервная копия - Действия - Кэш - Жесты - Готово - Расширения - URL репозитория - Плагин загружен - @string/home_play - Перемотка двойным нажатием - /\?\? - /%d - 18+ - Не удалось загрузить %s - плагины - SDR - TS - Удалить репозиторий - SD - 4K - Web - UHD - плагин - Пропустить настройку - Ошибка - Cam - Cam - HDR - DVD - WP - TC - HD - Blu-ray - Cam - HQ - Отключено: %d - %s %s - %s аутентифицировано - Не удается логин на %s - Макс - Мин - Контурный - С тенью - 1000 мс - Задержка субтитров - Без задержки субтитров - Используйте, если субтитры отображаются на %d мс слишком рано - Используйте, если субтитры отображаются %d мс слишком поздно - Нормально - Поднятые - Съешь ещё этих мягких французских булок, да выпей же чаю - Рекомендуется - Загружено %s - Аниме - OVA - Этикетка Dub - Сайт - Функции - Главное - Источник - Случайный - Скоро… - Этикетка Sub - Фон - Oтoбpaжeниe - Трейлер - %s (отключено) - Следующий - В CloudStream по умолчанию не установлены сайты. Вам необходимо установить сайты из репозиториев. -\n -\nИз-за безмозглой DMCA-атаки со стороны Sky UK Limited 🤮 мы не можем привязать сайт репозитория в приложении. -\n -\nПрисоединяйтесь к нашему Discord или ищите в интернете. - Недопустимые данные - Разрешение и название - Предыдущий - Разрешение - Браузер - Библиотека - Обновленный (старый - новый) - Алфавитный (А до Я) - Алфавитный (Я до А) - Выбрать библиотеку - Открыть с - Похоже, ваша библиотека пуста :( -\nВойдите в аккаунт с библиотекой или добавьте сериалы в локальную библиотеку - Сортировка - Открытый список - Рейтинг (высокий - низкий) - Рейтинг (низкий - высокий) - Обновленный (новый - старый) - Сортировать по - PackageInstaller - Кодировка субтитров - Загрузить из файла - Рейтинг: %s - Скачано %d %s - Все %s уже скачаны - Начата загрузка %d %s… - Не скачано: %d - Скачать все плагины из этого репозитория\? - Включен безопасный режим - Скачано: %d - Обновлено %d плагинов - Загрузить из интернета - Загрузка обновления приложения… - Недопустимый URL - Применить при перезапуске - Отчеты ошибках - Что вы хотите увидеть - Смотрите видео на этих языках - Скачано файл - Изображение постера - Пакетная загрузка - Скачайте список сайтов, который вы хотите использовать - Отображать Аниме с Дубляжом/Субтитрами - Включить NSFW на поддерживаемых провайдерах - Удалять скрытые субтитры из субтитров - Дополнительно - Изменить вид интерфейса, чтобы соответствовать устройству - Аудио дорожки - Это также удалит все плагины репозитория - Просмотреть репозитории сообщества - Видео дорожки - Все расширения были отключены из-за сбоя, чтобы помочь вам найти то, которое вызывает проблемы. - Повтор - Слишком много текста. Не удалось сохранить в буфер обмена. - Установка обновления приложения… - Не удалось установить новую версию приложения - Файл безопасного режима найден! -\nНе загружаются никакие расширения при запуске, пока файл не будет удален. - Приложение будет обновлено после выхода - Похоже, этот список пуст, попробуйте переключиться на другой - Все субтитры заглавными - Показывать всплывающие окна для пропуска вступления/заключения - Фильтровать по предпочитаемому языку медиа - Неверный ID - Ссылка на стрим - Показывает кнопку на главной странице, с помощью которой можно выбрать случайный фильм или сериал с главной страницы - Рандомная кнопка - Legacy (старый) - Веб видеокаст - Не отправляет данные - Перезагрузить ссылки - Предпочтительные медиа - Опущенные - Объем перемотки плеера (секундах) - Объем перемотка, используемый, когда плеер виден - Плеер показан - Перемотки объем - Плеер спрятан - Перемотки объем - Удалять лишнее из субтитров - Местоположение ползунка, когда игрок скрыт - Android TV - Второго планa - Смешанный опенинг - Смешанный конец - Тест провайдер - Журнал - Запустить - Выполнено - Неудачный - Прекратить - Перезапустить - Вернуться - Подписался на %s - Предпочтительное качество видео (Мобильный интернет) - raw.githubusercontent.com Прокси-сервер - Не удалось подключиться к GitHub. Будет выполнен прокси jsdelivr. - Эпизод %d выпущен! - Обходы провайдера - Обновление подписки на фильмы и сериалы - Обход ограничения доступа к GitHub с помощью jsdelivr может задержать обновления на несколько дней. - Подписные - Отказались от подписки на %s - diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml deleted file mode 100644 index a1afd6d9..00000000 --- a/app/src/main/res/values-sk/strings.xml +++ /dev/null @@ -1,107 +0,0 @@ - - - Našla sa nová aktualizácia! -\n%s -> %s - Výplň - %dh %dm - Epizóda %d bude vydaná za - %s Ep %d - Ďalšia epizóda - Žánre - Zdielať - Otvoriť v prehliadači - Preskočiť načítavanie - Hrajú: %s - CloudStream - Plagát - %dd %dh %dm - %dm - %d min - Plagát - Plagát epizódy - Hlavný plagát - Prehrať s CloudStream - Nastavenia - Hľadať %s… - Pokračovať v sťahovaní - Hodnotenie: %.1f - Ísť späť - Rýchlosť (%.2fx) - Zmeniť poskytovateľa - Domov - Hľadať - Hľadať… - Sťahovanie - Žiadne dáta - Zrušiť - Kopírovať - Zavrieť - Uložiť - Stiahnuť - Stiahnuté - Ďalšie možnosti - Zdroje - Ísť späť - Sťahovanie zlyhalo - Sťahovanie pozastavené - Sťahovanie dokončené - Chyba pri načítavaní odkazov - Aktualizácia spustená - Interné úložisko - Načítavanie… - Dokončené - Plánujem pozerať - Zakázať automatické nahlasovanie chýb - Viac informácií - Záložky - Prehrať film - Prehrať upútavku - Sťahovanie - Sťahovanie zrušené - Dab - Zmazať súbor - Žiadny - Tit - Opätovné sledovanie - Prehrať súbor - Info - Prehrať živý prenos - Titulky - Prehrať epizódu - Pozastaviť sťahovanie - Skryť - Filtrovať záložky - Odstrániť - Použiť - Sťahovanie spustené - Vyčistiť - Prehrať - Nastaviť stav sledovania - Rýchlosť prehrávania - Farba obrysu - Farba okna - Typ hrany - Nastavenia titulkov - Farba pozadia - Farba textu - Vyvýšenie titulkov - Hľadať pomocou poskytovateľov - Písmo - Hľadať pomocou typov - Automaticky vybrať jazyk - Jazyk titulkov - Veľkosť písma - Nedarovali ste žiadne benény - Podržaním obnovíte predvolené nastavenia - %d benénov darovaných vývojárom - Stiahnuť jazyky - Odstrániť - Tento poskytovateľ je torrent, odporúča sa VPN - Importovať písma ich umiestnením do %s - Viac informácií - @string/home_play - Pokračovať v sledovaní - Na správne fungovanie tohto poskytovateľa môže byť potrebná VPN - Stránka neposkytla žiadne metadáta, načítanie videa zlyhá, ak na stránke neexistuje. - Popis - diff --git a/app/src/main/res/values-so/strings.xml b/app/src/main/res/values-so/strings.xml deleted file mode 100644 index ce7d557a..00000000 --- a/app/src/main/res/values-so/strings.xml +++ /dev/null @@ -1,490 +0,0 @@ - - - Metalaya: %s - %dm %ds %dd - %ds %dd - %dd - %s Xlq %d - Xalqadda %d waxa lasoo deyn doonaa - Daji - Dejintii ma guulaysan - Raadi - Tirtir - Haki - Horran - Le-ekaysii shaashadda - Kala jiid - Soo dhawee - Dhammaan - Muusik - Ku daawo CloudStream - Habaynta - Buuxiye - Beddel qaybiyaha - Cloudstream - Xawaaraha (%.2fx) - %d dqq - Kadinka - Qiimaysan: %.1f - App cusub baa soo baxay! -\n%s -> %s - Soo dejinta - Raadi - Dookhyo kale - Xog ma leh - Xalqadda xigta - Daawanaysid - Raadi %s… - Caynadda - Faafi - Ku fur barawsarka - Is dhaafi soo kicitaanka - Soo kacaya… - Raadi… - Kor u hay - Dib u daawasho - Dhamaystirmay - Soo dhacay - Ku talo jira - Midna - Daaro filinka - Daar goos-gooska - Midabka daaqadda - Saar faylka - Daar faylka - Si toos u daawo - Daalaco toorentiga - Qrl-hoosaadka - Dib u bilow dejinta - Jooji - Midabka dambeedka - Xigashooyinka - Dib u xidhiidhinaya… - Dib u noqo - Xalqadda daaro - Waad hakisey la degista - Way dhamaatay dejintu - Kaydka gudaha - Trj - Way bilaabmatay soo dejintu - Daalaco - Jooji cillad gudbinta iskeed ah - Waa la joojiyey dejintan - Way bilaabantay cuaboonaysiintu - Soo raridda lifaaqyadu way fashilantay - Qrl - Qari - Warbixin - Warbixin dheeraad ah - Habaynta Qoraal-hoosaadka - Midabka lakabka sare - Daar - Shaandhee caalamad-sanayaasha - Calaamad-sanayaasha - Xagee marinaysaa - Kaydi - Xawaaraha daaraha - Xajmiga farta - Haki dejinta - Ka dhig - Midabka farta - Xeji si aad asalkiisa ugu soo celiso - Koobi/ka reeb - Xidh - Tirtir - Nooca cidhifyada - Isticmaal xaddiga iftiinka ee moobilkaaga halkii aad adigu habayn lahayd ugu horreynba - Gurmadka iyo cusbooneysiinta - Waa la kaydiyet xogta gurmadka - Raadinta waxay kuugu kala qaybinaysaa mid kasta iyo qaybiyihiisa - Ma jirto xog la dirayo - Way fashilantay in xogta laga soo celiyo faylka gurmadka%s - Moobillada qaar ayaa awoodin iney Rakibaha cusub een isticmaalnay, kan ku day haddii cusbooneysiintu kuu shaqayn weydo. - +30 - Xogta waa la kaydiyey - Heerka - Ogolaanshaha lagu kaydinayo ma hayno. Fadlan isku day mar kale. - Koontooyinka - Xog - Si iskii ah usoo deji sidkanaha - Qari tayada muqaalka aad dooratay natiijada waxa aad raadiso - Raadin heer-sare ah - Keliya xogta waxaa ka dirayaa marka appku duqeeyo - Xalqad buuxis ah tusi anemiga - Dhiseyaasha appka sii beniin - Way fashilantay dejintu, hubi ogolaanshaha isticmaalka kaydka - Xalqadda koromakaastiga - Soo dejiso Qoraal-hoosaadyada - Summadda Tayada - Humaagga-bixinta koromokaastiga - Asalkiisa - Isla appkan ku daawo - Ku daawo %s - Ku daawo barawsarka - Dejinta iskeed ah - Deji toorentiga(mirror) - Cusbooneysii lifaaqyada - Eeg iney jirto cusbooneysiin - Summadda codeynta - Sumadda Qrl-hoosaadka - Ciwaanka - Maxaa ka muuqan doona boodhka filinka - Ma jiro wax ku cusub Appka - Quful - Cabbirka - isha - Is dhaafi - Mar dambe ha i tusin - Is dhaafi - Cusbooneysii - Tayada aad doorbiddo - Qaabka - Qaab emulator - Waxa uu ku tusayaa badhin aad si xukasho la\'aan ah ugu dooran kartid - Kor-u-baxa Qrl-hoosaadka - Farta - Muuq dambeedka - Ku raadi qaybiyeyaasha - Ku raadi noocyada - %d oo beniin ayaad siisey dhiseyaasha - Wax beniin ah maad bixin - Iskeed-u-doorashada luuqadda - Luuqadda dejinta - Luuqadda Qoraal-hoosaadka - Xogta muqaalkan ma bixinno, ma shaqayn doonto haddii aannu weyno xogta muqaalka. - Dhig nooca farta aad dooneyso %s - Sii wado daawashada - Ka saar - Xog dheeri ah - Tusi Boodhka Kitsu - Waxa laa raadinayaa haddii ay wax jiraan baahin-horaad ah, halkii la sugi lahaa baahin buuxda - Si iskii ah u cusbooneysii sidkanaha - Si iskii ah usoo deji dhamaan sidkanayaasha aan weli lasoo dejin ee ku jira kaydka aad keentay. - 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 - %d %s - Ku cusbooneysii baahinta ugu horreysa - Rakibaha APKga - Socda - Github - Filin - Anime app, isla dhiseyaashan appkan baa leh - Kal ma leh - Discord naga soo qabo - Beeniinyada aad bixisey - Luuqadda - Bixiyahan kuma shaqeeyo koromakaastiga - Asalkiisii hore kusoo celi - Dulucuda - Raalliahow, wuu duqeeyey appku, waxa warbixin cillad-saarka ka caawisa loo direy dhiseyaasha - Kalka - %s %d%s - Xalqad - Xalqado - %d illaa %d - K - X - Kartoon - Daraamada eeshiyaanka - Tirtir faylka - Sii wado - -30 - Dhamaantii waa la saari doona %s -\nSow ma hubtid\? - Fashil ka yimi xigashada - %ddq -\nAyaa hadhsan - Dhamaystirmay - Sannadka - Qiimaynta - Muddada - La isticmaalay - Qrl-hoosaad ma leh - Sidiisaa - Bilaash - Appka - Kartoob - Anemi - Daaraha ayaa ku fashilmay - OVA - Toorenti - Tooska - OVA - Daraamada Eeshiyaanka - Daaraha korkiisa - kaydka kumeelgaadhka - Ka reebo fadhigan - Kuwa kale - Taxane - Anemi - Dhokumentari - Toorentiyo - Dhokumentariyo - Toos - CEEB - Kuwa kale - Fashil fog - DNS ka horreysi HTTPS - Maxaa ku cusub appka - Gurmad - Kordhiyeyaal - Qoraal-hoosaadyada - Muqaal - Jiid si aad u habaysato - Laga yaabaa inaad u baahato VPN si qaybiyahan kuugu shaqeeyo - Qaybiyahan waa toorenti, inaad VPN isticmaasho ayaa wanaagsan - Ka sii wado daawashada daaqad yar oo appska kale dul istaagi doonta - Jiid si aad u dhaafiso - Eeg xogaha hoose 🐈 - Badhinka xajmiga daaraha - Fashil ka yimi dhiibaha - Koobu garee lifaaqa - Sharraxaad ma leh - Sharraxaadda - Duluc ma leh - Sawir-daaqadlaha - Habaynta Qrl-hoosaadka daaraha - Qrl-hoosaadka koromakaastiga - Hareeraha madow ka saar markaad daawanayso - Qoraal-hoosaadka - Habaynta Qrl-hoosaadka Koromakaastiga - Habka Xawaare-kordhinta - Waxaad kordhin karta xawaaraha aad ku daawanayso - 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 - Laba jeer taabo si aad u qallajiso - Dhexda taabo si aad u qallajisato - Gurmad u samee xogta - Iftiinka moobilka istcimaal - Cusbooneysii marba halkaad marayso daawashada - Tus goos-gooska - Waa fashilmay kaydinta gurmadku %s - Kasoo celi Xogta gurmadka aad kaydsatay - Dhererka K. kumeelgaadhka ah - Noocyada muqaal eed doorbiddo - Qaab TV - Heerka Kayd-yaraha - Masax kayd yaraha sawirrada/muqaalka - Laga yaaba inuu appku duqeeyo haddii aad badiso. - Laga yaabaa inuu wax xumeeyo haddii aad badiso, weliba aaladaha qaadka yar, sida Androyd tiifiiga. - Wuxuu muhiim u yahay inaad dhaafto Xannibaadaha ISPga - Ka saar fadhigan - Ku dar ka-reeb fadhi hore u jirey, laakiin hayb cusub wata - Aaraar - Haybta Seerfarka NGINX - Tusi Anemaha Turjuman/Leh Qrl-hoosaad - Lifaaqyada - Barta kaydka - Astaamaha - Badhinka X. la\'aanta - Qaybiyeyaasha - Iskii - Qaab moobil - Falal - Kayd-yare - Ishaarooyinka - Astaamaha daaraha - Guud - Tusi CEEBta qaybiyayaasha ku shaqeeya - Qaabka - Midab asaaska - Horidda Qoraal-hoosaadka - Nashqadda Appka - Goobta Cinwaanka boodhka - La degtay - Soo degaya - Ka Saar - 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 - Lifaaq lagama helin - Waad koobiyeysay lifaaqa - Daari xalqaddan - Xalqado ma leh - Wabsaydka - Filimaan - Taxanayaal - CEEB - Tirada inta ka muuqanaysa magaca filinka - Luuqadda bixiyaha - Qaabka Appka - Ka saar kuwa la daawaday - Eeg Kuwa Dadka kale Rakibeen - Codad kale - Far weyn ka dhig Qrl-hoosaadka - Sharraxaad - Xaaladda - Xajmiga - Safe mode ayaa daaran - Dhammaan Kordhiyeyaasha waa la wada demiyey cillad timi aawadeed si aad u ogaato ka cillada sababaya. - %s (La xayiray) - Marka dambe eed gasho isticmaal - Eeg warbixinra Cilladda - Qiimeynta: %s - Lahaanshaha - Qaybtii - HLS playlist - Ku shaqeeya - Luuqadda - Rakib kordhiyahan ugu horreyn - Muqaal daaraha aad doorbiddo - Masax taariikhda - Taariikhda - Furitaanka - Qoraalku wuu dheeryahay. Ma koobi garayn kartid. - Haa - Tusi badhin aad iska dhaafin karto bilowga/dhamaadka - Ku caalamadi in la daawaday - Dejinaya cusbooneysiinta appka… - Ma hubtaa inaad ka baxdid\? - May - PackageInstaller - Rakibaya cusbooneysiinta… - Waa la rakibi kari waayay qaybtan cusub ee appka - Lagama-maarmaanka - Marka aad ka baxdo baa ka cusbooneysiin doona appka - Dhig ciwaanka boodhka hoostiisa - Akoonka - Soo gal - Akoon geli - %s xaqiijisan - iiwarran@sxb.com - 127.0.0.1 - MagacKaYaabAh - Ka bax - Tusaale.com - Waxa lagu daray %s - Midna - Websaydkayga.com - Koodhka Luuqadda (en) - %s %s - Akoon samee - Talantaalli - Qiimaysan - Beddel akoonka - Ku dar raad-raace - /%d - %d / 10 - /\?\? - Waa lagu guuldarraystey inaad ku gasho %s - Caadi - Ugu weyn - Ugu yar - Hoos u degsan - Lakabka sare - Hagaaji Qrl-hosaadka - Isticmaal intan haddii Qrl-hoosaadku ay %d ms soo habsaanayaan - 4K - Dib u noqo - Hadhka - Kor-u-qaadan - Isticmaal intan haddii Qrl-hoosaadku ay %d ms soo hordhacayaan - 1000 ms - Ka guud - La jilaya - Sheekheenna wiilkiisa curad, gabadhiisu foox la\'aan ma heesi karto - HQ - Lagugula taliyey - Waxa laso kiciyey %s - Faylashaada ka keen - Internetka ka keen - Waa ka dejiyey faylka - Wajhadda dambe - Cam - HD - TS - SDR - Daaraha - Ciwaanka - Ka saar Qrl-hoosaadka asalka ah - Ka saar bararsanaanta Qrl-hoosaadka - Goor dhow… - Xigashada - Xulasho la\'aan - Cam - Cam - TC - Blu-ray - SD - WP - UHD - HDR - DVD - Sawirka Boodhka - Web - Hayb aan sax ahayn - Tayada iyo ciwaanka - Tayada - Aqoonsi aan sax ahayn - Xog ana sax ahayn - Xumaaday - Noocyada Muqaalka - Waxba kamaad bedelin daahitaanka Qrl-hoosaadka - U kala hor sida luuqadaha aad doorbidday - X.la\'aanta xigta - Sidkane - Sidkanayaasha - Saar kayd-weynaha - Dejiso liiska websaydyada aad dooneyso - Waxa la dejiyey: %d - Dejin xidhmeed - Waxa la saarayaa dhammaan sidkanayaasha kayd-weynaha - 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... -\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 - Lambarkasirta123 - Dib-u-dhaca Qrl-hoosaadka - Dheeraadka - Goo-gooska - Lifaaqa daalacashada - Tixraacaha - Xiga - Ku daawo muuqaallada luuqadahan - Kii hore - Is dhaafi fadhiisintan - Beddel sida appku u muuqdo si uu ugu habboonaado moobilekaaga - Warbixinra cillad-saarka - Maxaad rabtaa inaad daawato - Dhan - Ku dar kayd-weyne - Magaca kayd-weynaha - Haybta kayd-weynaha - Waa la keenay sidkanaha - Waa ka dejiyey sidkanaha - Waa la tiray sidkanahan - Waa ka keeni kari waayey %s - 18+ - Waa la dejinayaa %d %s… - Waa la dejiyey %d %s - Dhammaan %s way dejisan yihiin - Kordhiyeyaasha - Liiska bulshada kale - Muqaal daaraha appka - VLC - MPV - Web Video Cast - Barawsarka - Kuuguma jiro appkaasi - Dhammaan luuqadaha - Is dhaafi %s - Dhamaadka - Soo koobidda - Dhamaad isku qasan - Bilowga - Bilow isku qasan - Qoraalka dhamaadka - diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 0b7ba89e..58ff060e 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -1,10 +1,9 @@ - Betygsatt: %.1f Hastighet (%.2fx) - Ny uppdatering hittad! -\n%s -> %s + Ny uppdatering hittad!\n%s -> %s + CloudStream Hem Sök @@ -22,12 +21,14 @@ Öppna i webbläsaren Hoppa över Laddar… + Tittar på Pausad Avslutad Dropped Plannerad Ingen + Spela Upp Strömma Torrent Källor @@ -38,10 +39,13 @@ Spela Avsnitt Ladda ner Intern lagring + Dub Sub + Ta bort Spela upp + Inaktivera automatisk felrapportering Mer information Hide @@ -67,19 +71,24 @@ Font Sök med följande leverantörer Sök med följande filmtyper + %d Bananer donerade till utvecklarna Inga bananer givna + Automatisk val av undertextspråk Automatisk nerladdaning av språk Håll inne för att återställa till standard Fortsätt titta + Ta bort Mer information + En VPN kan behövas för att den här leverantören ska fungera korrekt Denna leverantör är en torrent, en VPN rekommenderas Beskrivning Ingen sammanfattning hittades Ingen beskrivning hittades + Bild-i-bild Fortsätter uppspelning i en miniatyrspelare ovanpå andra appar Lägger till en knapp för att justera bildförhållandet @@ -110,7 +119,9 @@ Gå med i Discord-gruppen Ge en banan till utvecklarna Ge banan + Språk + Denna leverantör har inget Chromecast-stöd Inga länkar hittades Länken kopierades till urklipp @@ -118,16 +129,18 @@ Återställd till standardvärdet Programmet kraschade tyvärr. En anonym felrapport kommer att skickas till utvecklarna Fel uppstod vid laddning av länkarna + Säsong Ingen Säsong Avsnitt Avsnitt S A + Ta bort nerladdad fil Ta bort - %s kommer att raderas permanent -\nÄr du helt säker\? + %s kommer att raderas permanent\nÄr du helt säker? + Pågående Färdig Status @@ -136,17 +149,21 @@ Längd Sida Sammanfattning + på kö Inga undertexter Standard + Tillgängligt Använtt App + Filmer Tv Serier Cartoons Anime Torrent + Chromecasta ett Avsnitt Chromecasta en Länk Spela upp i appen @@ -156,14 +173,18 @@ Automatisk nerladdning Ladda ner en specifik länk Ladda om alla länkar + Ingen uppdatering hittad Sök efter uppdatering + Skärmlås Bildförhållande Källa Hoppa över OP + Visa inte igen Uppdatera + Nerladdningar startad Nerladdning Misslyckades Nerladdad @@ -173,6 +194,7 @@ Nerladdning Färdig Återupta nerladdning Pausa nerladdning + Pausa Återuppta Ett nerladdningsfel uppstod, kolla om appen har lagringsbehörigheter @@ -213,160 +235,4 @@ " " Appens tema %s A%d - %dh %dm - %dm - Spela med CloudStream - Chromecast-undertexter - Tryck i mitten för att pausa - Återställ data från backup - Konton - Uppdateringar och backup - Automatiska pluginuppdateringar - %dd %dh %dm - Sök %s… - Kopiera - Stäng - Dubbeltryck för att pausa - Spara - Undertextspråk - Visa Logcat 🐈 - Autospela nästa episod - Spela Trailer - Starta nästa episod när nuvarande slutar - Episod %d kommer släppas om - %d min - Visa trailers - @string/home_play - OVA - %d-%d - %d %s - %s %d%s - -30 - %dm -\nåterstår - NSFW - @string/ova - Torrent - NSFW - +30 - Metadata är inte givet av sidan, videon kommer inte ladda om den inte existerar på sidan. - Automatiskt ladda ner plugin - Dölj vald videokvalitet i sökresultat - Ladda ner undertexter - Film - Serier - Skippa denna uppdatering - Lagringsbehörigheter saknas. Var vänlig försök igen. - Inga episoder hittade - Visa bilder från Kitsu - Vissa telefoner stöder inte den nya paketinstallatören. Prova alternativet för äldre versioner om uppdateringarna inte installeras. - APK-installatör - Importera typsnitt genom att placera filerna i %s - Automatiskt uppdatera antalet episoder sedda - Säkerhetskopiera data - Läste in säkerhetskopia - Misslyckades att återställa data från %s - Data lagrad - Problem att säkerhetskopiera %s - Installera automatiskt alla ännu inte installerade plugins från tillagda repositories. - Gör om installationsprocessen - Dokumentärer - Asiatiska draman - Andra - Tecknade serier - @string/anime - Dokumentär - Asiatisk drama - Video - Kvalitetsetikett - Titel - Växla UI-element på affisch - Videospelarens upplösning - Historik - Markera som sedd - Inställningar för Chromecast-undertexter - TV-layout - Skapa konto - %d / 10 - /\?\? - /%d - Använd detta om undertexterna visas %d ms för tidigt - HD - SD - UHD - Upplösning och titel - Fel - Referer - Nästa - 1000 ms - Lägga till en klon av en befintlig webbplats med en annan webbadress. - Ogiltigt ID - Ogiltig webbadress - Orsakar krascher om inställningen är för hög på enheter med lågt minne, t.ex. Android TV. - Orsakar problem om inställningen är för hög på enheter med lågt lagringsutrymme, t.ex. Android TV. - Ta bort webbplats - Klona webbplats - Telefonlayout - Primärfärg - Användarnamn - namn@email.se - 127.0.0.1 - Källa - Slumpmässig - Kommer snart… - Filtrera efter föredraget språk - Kraschrapportering - Kunde inte logga in på %s - Utseende - Layout - 4K - Ändra appens utseende så att den passar din enhet - Skådespelare: %s - Spela Livestream - %s %s - Ingen undertextfördröjning - Spelarfunktioner - Trailer - Applayout - Funktioner - exempel.se - Språkkod (en) - Videocache på disken - Rensa video- och bildcache - Länkar - Appuppdateringar - Layout - Titel - Hoppa över installationen - Klar - Tillägg - Rensa - Direktsändning - Max tecken i videospelartiteln - Videobuffertstorlek - Videobuffertlängd - Ansvarsfriskrivning - Tillägg - Cache - Säkerhetskopiering - Undertexter - Aktivera NSFW på sidor som stöds - Undertextkodning - lösenord123 - Fördröjning av undertexter - Använd detta om undertexterna visas %d ms för sent - Importera från fil - Importera från Internet - Nedladdad fil - TS - TC - DVD - HDR - SDR - Upplösning - Ogiltig data - Ta bort dold textning från undertexter - Titta på videor på dessa språk - Föregående - Spår diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml deleted file mode 100644 index 4370e760..00000000 --- a/app/src/main/res/values-ta/strings.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - தேடுக - தேடல் %s… - வேகம் - முகப்பு - தேடு - பதிவிறக்கம் - தகவல் எதுவும் இல்லை - மேலும் விருப்பங்கள் - அடுத்த எபிசோட் - வகைகள் - பகிர் - Browser இல் திற - ஏற்றுவதைத் தவிர் - பார்த்து கொண்டிருப்பது - நிறுத்தி வைக்கப்பட்டுள்ளது - நிறைவடைந்தது - பார்க்கத் திட்டமிடப்பட்டுள்ளது - மீண்டும் பார்க்கத் தொடங்கியது - ஸ்ட்ரீம் டோரண்ட் - வசன வரிகள் - பின் செல் - எபிசோடை இயக்கு - எபிசோட் பதிவிற்கான அனுமதி கொடுக்கவும் - பதிவிறக்கம் செய்யப்பட்டது - பதிவிறக்குகிறது - பதிவிறக்கம் இடைநிறுத்தப்பட்டது - பதிவிறக்கம் தொடங்கியது - பதிவிறக்கம் தோல்வியடைந்தது - ஸ்ட்ரீம் - உள் சேமிப்பு - மொழிபெயர்க்கப்பட்டது - கோப்பை நீக்கு - கோப்பை இயக்கவும் - பதிவிறக்கத்தை நிறுத்து - தானியங்கி பிழை அறிக்கையை முடக்கு - மேலும் தகவல்கள் - மறை - நீக்கு - ரத்து செய்க - நீக்கு - சேமிக்கவும் - உரை வண்ணம் - வெளிப்புற நிறம் - பின்னணி நிறம் - வசன உயர்வு - எழுத்துரு - வழங்குபவர்கள் பயன்படுத்தி தேடுங்கள் - வகைகளைப் பயன்படுத்தி தேடவும் - மொழிகளைப் பதிவிறக்கவும் - வசன மொழி - தொடர்ந்து பார்க்கவும் - முறையாக இயங்க vpn பயன்படுத்தவும் - பிளேயர் அளவை மாற்றும் பொத்தான் - Chromecast வசனங்கள் - அமைப்புகளை மாற்ற ஸ்வைப் செய்யவும் - அடுத்த எபிசோடை தானாக இயக்கவும் - தற்போதைய அத்தியாயம் முடிந்ததும் அடுத்த அத்தியாயத்தைத் தொடங்கவும் - தேடுவதற்கு இருமுறை தட்டவும் - பிளேயரில் தேடுதல் வேகம் - இடைநிறுத்துவதற்கு நடுவில் தட்டவும் - நடிகர்கள் - பின் செல் - அமைப்புகள் - ஏதும் இல்லை - ஏற்றுகிறது… - கைவிடப்பட்டது - பதிவிறக்கம் முடிந்தது - இணைப்பை மீண்டும் முயற்சிக்கவும்… - திரைப்படத்தை இயக்கு - லைவ்ஸ்ட்ரீம் இயக்கு - டிரெய்லரை இயக்கவும் - மூலம் - இணைப்புகளை ஏற்றுவதில் பிழை - இயக்கு - பதிவிறக்கம் ரத்து செய்யப்பட்டது - வசன அமைப்புகள் - பதிவிறக்கத்தை மீண்டும் தொடங்கவும் - புக்மார்க்குகளை வடிகட்டவும் - தகவல் - பிளேயர் வேகம் - புக்மார்க்கு - பயன்படுத்து - நகலெடுக்கவும் - மூடு - எழுத்துரு அளவு - நீக்கு - மேலும் தகவல்கள் - தானாக மொழியை தேர்ந்தெடு - முன்னோக்கி அல்லது பின்னோக்கி தேட வலது அல்லது இடது பக்கத்தில் இருமுறை தட்டவும் - மொபைலில் பிரகாசத்தை பயன்படுத்த - இயல்புநிலைக்கு மீட்டமைக்க அழுத்திப் பிடிக்கவும் - முறையாக இயங்க vpn பரிந்துரைக்கப்பட்டது - கருப்பு எல்லைகளை அகற்றவும் - விளக்கம் - கதை எதுவும் காணப்படவில்லை - விளக்கம் ஏதும் காணப்படவில்லை - படத்தில்-படம் - பிளேயர் வசனங்கள் அமைப்புகள் - Logcat 🐈 காட்டு - பிற பயன்பாடுகளுக்கு மேல் மினியேச்சர் பிளேயரில் பிளேபேக் தொடர்கிறது - வசன வரிகள் - பிளேயரில் வேக விருப்பத்தை சேர்க்க - வீடியோ பிளேயரில் நேரத்தைக் கட்டுப்படுத்த இடது அல்லது வலதுபுறம் ஸ்வைப் செய்யவும் - பிரகாசம் அல்லது ஒலியளவை மாற்ற இடது அல்லது வலது பக்கத்தில் ஸ்வைப் செய்யவும் - இடைநிறுத்துவதற்கு இருமுறை தட்டவும் - Chromecast வசன அமைப்புகள் - இருண்ட மேலடுக்குக்குப் பதிலாக ஆப் பிளேயரில் சிஸ்டம் பிரகாசத்தைப் பயன்படுத்தவும் - diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index cf3b1263..dfe922ee 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -1,9 +1,11 @@ - + + %s Ep %d + Poster @string/result_poster_img_des @@ -13,16 +15,18 @@ Go back Change Provider Preview Background + Bilis (%.2fx) Rated: %.1f - Bagong update! -\n%s -> %s + Bagong update!\n%s -> %s + CloudStream Home Maghanap Mga Downloads Mga Settings + Maghanap… Walang datos Opsyon @@ -32,12 +36,14 @@ Buksan sa browser Skip Loading… Loading… + Pinapanood Inihinto Tapos nang panoorin Ayaw nang panoorin Balak panoorin None + I-play ang Movie Stream Torrent Sources @@ -46,6 +52,7 @@ Go back I-play ang episode + Download Downloaded Downloading @@ -54,14 +61,18 @@ Download Failed Download Canceled Download Done + Error Loading Links Internal Storage + Dub Sub + I-delete ang file I-play ang file I-resume ang download I-pause ang download + Huwag awtomatikong mag-ulat ng bug More Info Itago @@ -73,6 +84,7 @@ Kumpirmahin Kanselahin Bilis ng Playback + Subtitle Setting Kulay ng Teksto Kulay ng Outline @@ -82,29 +94,35 @@ Subtitle Elevation Istilo ng Font Laki ng Font + Search using providers Search using types + %d Benenes na ibinigay sa mga devs Walang Benenes na binigay + Awtomatikong magpili ng lenggwahe - I-download ang mga Wika + Download Languages Pindutin nang matagal upang i-reset sa default Ipagpatuloy ang pinapanood + Tanggalin Impormasyon + Maaaring nangangailangan gumamit ng VPN upang gumana ito Ito ay torrent, inirerekomenda ang paggamit ng VPN Deskripsyon Walang nakitang plot Walang nakitang deskripsyon + Picture-in-picture - Magpatuloy ang playback sa isang maliit na player sa ibabaw ng ibang apps + Nagpapatuloy ang playback sa isang maliit na player sa itaas ng ibang apps Pindutan upang baguhin ang laki Alisin ang black borders Subtitles Player subtitles settings Eigengravy Mode - Magdagdag ng \'speed option\' sa \'player\' + Nagdadagdag ng opsyon sa player upang baguhin ang bilis ng pinapanood Swipe to seek Swipe pakanan o pakaliwa upang makontrol ang oras ng pinapanood Swipe to change settings @@ -113,6 +131,7 @@ Tapikin ng dalawang beses sa kanan o kaliwang bahagi ng player upang mag-forward/backward Gamitin ang brightness ng system Gamitin ang brightness ng system para sa player + Maghanap Impormasyon Adbans na maghanap @@ -130,25 +149,29 @@ Sumali sa Discord Magbigay ng benene sa ang devs Sumali benene + Wika + Walang chromecast support ang provider na ito Walang link na nakita Link copied to clipboard Play Episode I-reset sa default value Paumanhin, ang app ay nag-crash. Isang anonymous bug report ang ipapadala sa developers + Season No season Episode Episodes S E + Burahin ang file Tanggalin I-pause I-resume - This will permanently delete %s -\nAre you sure\? + This will permanently delete %s\nAre you sure? + Patuloy Tapos na Katayuan @@ -157,22 +180,27 @@ Tagal Site Sinopsis + nakapila walang subtitles Default + Bakante Gamit App + Pelikula Palabas sa TV Cartoons Anime Torrent + Source error Remote error Renderer error Unexpected player error Download error, check storage permissions + Chromecast Episode Chromecast Mirror I-play sa App @@ -182,23 +210,29 @@ Awtomatiking i-download Download mirror Subukan muli + Walang bagong update Tingnan kung may bagong update + I-lock Palakihin Source Skip OP + Huwag nang ipakita ulit Update Preferred watch quality DNS over HTTPS Mainam gamitin sa pag-bypass ng ISP blocks + Display Dubbed/Subbed Anime + Fit to screen Punan ang buong screen Zoom + Pangkalahatan - Wika ng Provider + Lenggwahe ng Provider App Layout Awtomatik TV Layout @@ -206,62 +240,4 @@ Pangunahin Kulay Tema ng app Preferred Media - Wika ng Subtitle - Mag-Import ng font na nakalagay sa %s - Ipakita ang Logcat 🐈 - Kusang i-play ang next episode - Payagan ang NSFW sa mga suportadong providers - %dm - I-Cast: %s - Episode %d ay ipapalabas sa loob ng - Hanapin ang %s… - %d min - %dd, %dh:%dm - %dh : %dm - I-play gamit ang CloudStream - Pinapanuod Uli - Panuorin ang Livestream - I-set ang Status - Kopyahin - Isara - Tanggalin Lahat - I-save - Ang Metadata ay di galing sa site, mag-fail ang pag load ng video pag wala ito sa site. - Simulan ang susunod na episode kapag natapos ang kasalukuyan - Ibalik ang data gamit ang backup - I-backup ang data - Nabuksan ang backup file - Naitabi ang Data - Nagka error sa pag backup ng %s - Ipakita ang trailers - Ipakita ang posters mula sa Kitsu - Wag ipakita ang mga video quality sa search results - Kusang i-update ang plugins - Kusang i-download ang plugins - Lokasyon ng Poster title - Ilagay ang title sa ibaba ng poster - Mag log out - Kusang install ang mga di naka install na plugins mula sa repositories. - -30 - +30 - Iba pa - Dokumentaryo - I-download ang subtitles - Maglagay ng ibang url para sa site na meron na. - Ipakita ang Random Button sa Homepage - Language code (tl) - Magdagdag ng tracking - Dinagdag ang %s - Walang Storage permissions. Paki subukan muli. - Dokumentaryo - Walang nakita na Episode - Nagkamali sa pagbalik ng data galing sa file na %s - Linisin ang video at image cache - Magpalit ng account - Magkakaron ng crashes kapag sobrang taas. Iwan lang kapag mababa ang memory ng iyong device. - Posibleng magka problema kapag sobrang taas kapag mababa ang storage ng device (tulad ng Android TV) - Gumawa ng account - Magdagdag ng Account - Kasaysayan - I-tanda bilang napanood na diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 74754008..54b9ebea 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1,4 +1,3 @@ - @@ -14,36 +13,40 @@ %d %s Ep %d Cast: %s - Bölüm %d şu tarihte yayınlanacak + Bölüm %d şu tarihte yayınlanacak: %dd %dh %dm %dh %dm %dm + Poster - Afiş - Bölüm Posteri - Ana Poster - Sonraki Rastgele + @string/result_poster_img_des + Episode Poster + Main Poster + Next Random @string/play_episode - Geri git + Go back @string/home_change_provider_img_des Change Provider Preview Background + Hız (%.2fx) Puan: %.1f - Yeni güncelleme bulundu! -\n%s -> %s + Yeni güncelleme bulundu!\n%s -> %s Filler %d dakika + CloudStream CloudStream ile oynat Ana sayfa Arama İndirilenler Ayarlar + Ara… %s sağlayıcısında ara… + Veri yok Daha fazla seçenek Sonraki bölüm @@ -53,6 +56,7 @@ Tarayıcıda aç Yüklemeyi atla Yükleniyor… + İzleniyor Beklemede Tamamlandı @@ -60,6 +64,7 @@ Planlandı Hiçbiri Yeniden izleniyor + Filmi oynat Canlı yayını oynat Torrent oynat @@ -69,6 +74,7 @@ Geri dön Bölümü oynat + İndir İndirildi İndiriliyor @@ -79,14 +85,18 @@ İndirme bitti %s - %s Yayınla + Bağlantılar yüklenirken hata oluştu Dahili depolama + Dublajlı Alt yazılı + Dosyayı sil Dosyayı oynat İndirmeyi sürdür İndirmeyi duraklat + Otomatik hata raporlamayı kapat Daha fazla bilgi Gizle @@ -102,7 +112,9 @@ Kapat Temizle Kaydet + Oynatıcı hızı + Alt yazı ayarları Yazı rengi Dış hat rengi @@ -112,88 +124,109 @@ Alt yazı yüksekliği Yazı tipi Yazı boyutu + Sağlayıcıları kullanarak ara Türleri kullanarak ara + Geliştiricilere %d muz verildi Hiç muz verilmedi + Otomatik seçilecek dil İndirilecek diller - Altyazı dili - Sıfırlamak için basılı tut + Alt yazı dili + Varsayılana döndürmek için basılı tut Fontları içe aktarmak için %s konumuna yerleştirin İzlemeye devam et + Kaldır Daha fazla bilgi - @string/home_play + @string/home_play + Bu sağlayıcının düzgün çalışması için bir VPN gerekebilir - Bu sağlayıcı torrent kullanıyor, bir VPN önerilir - Metadata site tarafından sağlanmamış, veri site\'de bulunmuyorsa video yüklenmesi başarısız olacak. + Bu sağlayıcı bir torrent. VPN önerilir + + Metadata verisi site tarafından sağlanmadı, video yüklemesi eğer sitede bulunmuyorsa oynatma başarısız olacak + Açıklama Konu bulunamadı Açıklama bulunamadı - Logcat\'i görüntüle 🐈 - Görüntü içinde görüntü - İçerik diğer uygulamaların üzerinde küçük bir pencerede oynatılmaya devam eder + + Logcat\'i göster 🐈 + + Resim-içinde-resim + Diğer uygulamaların üzerinde minyatür bir oynatıcıda oynatmaya devam eder Oynatıcı yeniden boyutlandırma butonu - Siyah sınır çizgilerini kaldır - Alt yazılar + Siyah sınırları kaldır + Alt yazı Oynatıcı alt yazı ayarları - Chromecast alt yazıları + Chromecast alt yazı Chromecast alt yazı ayarları - Eigengrau modu - Oynatıcıya hız seçeneği ekler - Atlamak için kaydır - Zamanı ayarlamak için yanlardan kaydır + + Eigengravy modu + Oynatıcıya bir hız seçeneği ekle + Gözlemek için kaydır + Zamanı ayarlamak için sağa veya sola kaydır Ayarları değiştirmek için kaydır - Sol ve sağ taraftan yukarı kaydırarak ekran parlaklığı ve sesi ayarla + Sol ve sağ taraftan kaydırarak parlaklık ve sesi ayarla + Sonraki bölümü otomatik oynat Mevcut bölüm bittiğinde sonraki bölüme başla - Çift dokunarak atla - İki kez dokunarak duraklat - Atlanacak süre (Saniye) - İleri ve geri atlamak için sağa ve sola iki kez dokun - Durdurmak için ekranın ortasına çift dokun + + Gözlemek için çift tıkla + Durdurmak için çift tıkla + Oynatıcı gözleme miktarı + İleri ve geri atlamak için sağa ve sola çift tıkla + Durdurmak için ortaya tıkla Sistem parlaklığını kullan - Oynatıcıyı karartmak yerine sistem parlaklığını kullan + Oynatıcıda karanlık kaplama yerine sistem parlaklığını kullan + İzleme ilerlemesini güncelle Mevcut bölüm ilerlemesini otomatik güncelle - Verileri yedekten geri yükle - Verileri yedekle + + Yedekten geri yükle + + Yedekle Yedek dosyası yüklendi Geri yükleme başarısız oldu: %s Başarıyla yedeklendi - Depolama izinleri eksik. Lütfen tekrar deneyin. + Depolama izinleri eksik, lütfen tekrar deneyin %s yedeklenirken hata + Ara Hesaplar - Güncellemeler ve yedekleme + Güncellemeler ve yedek + Bilgi Gelişmiş arama - Arama sonuçlarını sağlayıcıya göre ayırır + Sağlayıcılara göre ayrılmış arama sonuçlarını ver Yalnızca çökmelerle ilgili verileri gönderir - Veri göndermez - Anime için filler bölümleri göster + Hiç veri göndermez + Anime için filler bölümleri gösterir Fragmanları göster Kitsu\'dan posterleri göster - Seçilen video kalitelerini arama sonuçlarında gösterme + Arama sonuçlarında seçilen video kalitelerini gizle + Otomatik eklenti güncellemeleri Uygulama güncellemelerini göster - Uygulama başlatıldıktan sonra güncellemeleri otomatik olarak kontrol et. - Deneysel sürümlere güncelle - Yalnızca tam sürümler yerine deneysel güncellemeleri de ara + Başlangıçta yeni güncellemeleri otomatik olarak ara + Ön sürümlere güncelle + Sadece tam sürümler yerine ön sürüm güncellemelerini de ara GitHub Aynı geliştiriciler tarafından LightNovel uygulaması Aynı geliştiriciler tarafından anime uygulaması Discord\'a katıl Geliştiricilere muz ver Verilen muz + Uygulama dili + Bu sağlayıcının Chromecast desteği yok Bağlantı bulunamadı Bağlantı panoya kopyalandı Bölümü oynat - Varsayılan değere sıfırla - Üzgünüz, uygulama çöktü. Geliştiricilere anonim bir hata raporu gönderilecek + Varsayılana sıfırla + Üzgünüz, uygulama çöktü. Geliştiricilere isimsiz bir hata raporu gönderilecek + Sezon %s %d%s Sezon yok @@ -204,6 +237,7 @@ S B Bölüm bulunamadı + Dosyayı sil Sil @string/sort_cancel @@ -211,10 +245,9 @@ Sürdür -30 +30 - %s tamamen silinecek -\nEmin misiniz\? - %dm -\nkaldı + %s dosyası tamamen silinecek\nEmins misiniz? + %dm\nkaldı + Devam ediyor Tamamlandı Durum @@ -223,12 +256,15 @@ Süre Site Özet + Sıraya alındı Alt yazı yok Varsayılan + Boş Kullanılan Uygulama + Filmler Diziler @@ -237,10 +273,11 @@ Torrentler Belgeseller OVA - Asya dizileri + Asya dramaları Canlı yayınlar - +18 + NSFW Diğerleri + Film Dizi @@ -249,25 +286,28 @@ @string/ova Torrent Belgesel - Asya dizisi + Asya draması Canlı yayın - +18 + NSFW Video + Kaynak hatası Sunucu hatası İşleyici hatası Beklenmeyen oynatıcı hatası İndirme hatası, depolama izinlerini kontrol edin + Bölümü Chromecast ile yayınla Bağlantıyı Chromecast ile yayınla - Burada oynat - %s üzerinden oynat + Uygulamada oynat + %s\'de\/da oynat Tarayıcıda oynat - Bağlantıyı kopyala + Linki kopyala Otomatik indir Şu kaynaktan indir Bağlantıları yenile Alt yazıları indir + Kalite etiketi Dublaj etiketi Alt yazı etiketi @@ -277,56 +317,87 @@ show_sub_key show_title_key Poster üzerindeki öğeler + Güncelleme bulunamadı Güncellemeleri denetle + Kilitle Yeniden boyutlandır Kaynak - Jeneriği geç + OP\'yi geç + Bir daha gösterme Bu güncellemeyi atla Güncelle - Tercih edilen görüntü kalitesi (WiFi) - Video oynatıcı başlığı karakter üst sınırı - Oynatıcının çözünürlüğü + Tercih edilen izleme kalitesi + Oynatıcıdaki maksimum başlık karakter sayısı + Oynatıcının üst tarafındaki öğeler + Video arabelleği boyutu Video arabelleği uzunluğu - Hafızadaki video önbelleği + Diskteki video önbelleği Video ve resim önbelleğini temizle - Çok yükseğe ayarlanırsa düşük belleğe sahip cihazlarda çökmelere neden olur (örn. Android TV). - Çok yükseğe ayarlanırsa düşük depolama alanına sahip sistemlerde sorunlara neden olur (örn. Android TV). - HTTPS üzerinden DNS (DoH) - İnternet Servis Sağlayıcısı (İSS) kısıtlamalarını aşmak için kullanışlıdır - Siteyi kopyala + + Çok yükseğe ayarlanırsa rastgele çökmelere neden olur. Android TV veya eski bir telefon gibi ram miktarı düşük cihazlarda değiştirmeyin + Çok yükseğe ayarlanırsa, Android TV cihazları gibi düşük depolama alanına sahip sistemlerde sorunlara neden olabilir + + HTTPS üzerinden DNS + ISP bloklarını atlatmak için kullanışlıdır + + Klon site Siteyi kaldır Farklı bir URL ile mevcut bir sitenin klonunu ekleyin + İndirme konumu - NGINX sunucu URL\'si + + Nginx sunucu URL\'i + Dublajlı/Alt yazılı animeleri göster + Ekrana sığdır Uzat Yakınlaştır - Yasal Uyarı + + Disclaimer legal_notice_key - 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. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. + 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. + + In case of copyright infringement, please directly contact the responsible parties or the streaming websites. + + The app is purely for educational and personal use. + + CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. + CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or + manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, + user-friendly interface. + + It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. + Genel - Rastgele İçerik - Ana sayfada rastgele bir film veya dizi seçen bir tuş gösterir + Rastgele butonu + Ana sayfada rastgele butonunu göster Sağlayıcı dilleri Uygulama düzeni Tercih edilen medya - Desteklenen sağlayıcılarda +18 içeriği etkinleştir + Desteklenen sağlayıcılarda NSFW\'yi etkinleştir Alt yazı kodlaması Sağlayıcılar Düzen + Otomatik TV düzeni Telefon düzeni Emülatör düzeni + Birincil renk Uygulama teması Poster başlık konumu Başlığı posterin altına yerleştir + + anilist_key mal_key @@ -337,8 +408,9 @@ hello@world.com 127.0.0.1 MyCoolSite - ornek.com + example.com Dil kodu (tr) + Hiçbiri Normal @@ -375,11 +448,12 @@ Gölge Yükseltilmiş Alt yazı senkronu - 1000 ms + 1000ms Alt yazı gecikmesi - Alt yazılar %d ms erken görüntüleniyorsa bunu kullanın - Alt yazılar %d ms geç gözüküyorsa bunu kullanın + Alt yazılar %dms erken gözüküyorsa bunu kullanın + Alt yazılar %dms geç gözüküyorsa bunu kullanın Alt yazı gecikmesi yok + Pijamalı hasta yağız şoföre çabucak güvendi + Önerilen - %s eklendi + %s yüklendi Dosyadan yükle İnternetten yükle İndirilen dosya Baş Yardımcı Geri plan + Kaynak Rastgele + Çok yakında… + Cam Cam Cam @@ -405,7 +483,7 @@ HD TS TC - Blu-ray + BlueRay WP DVD 4K @@ -414,6 +492,7 @@ HDR SDR Web + Poster fotoğrafı Oynatıcı Çözünürlük ve başlık @@ -423,10 +502,10 @@ Geçersiz veri Geçersiz URL Hata - Alt yazılardan seçmeli alt yazıyı (CC) kaldır + Alt yazılardan seçmeli alt yazıyı kaldır Alt yazılardaki şişkinliği kaldır Tercih edilen medya diline göre filtrele - Ek içerikler + Ekstralar Fragman Yayına bağlan Yönlendiren @@ -434,20 +513,20 @@ Videoları bu dillerde izle Geri Kurulumu atla - Cihazınıza uygun uygulama görünümünü seçin + Cihazınıza uygun görünümü seçin Çökme raporları - Ne izlemek istiyorsunuz + Ne izlemek istiyorsunuz? Bitti Eklentiler Depo ekle Depo ismi - Depo URL\'si + Depo URL\'i Eklenti yüklendi Eklenti silindi %s yüklenemedi +18 - %d %s indirilmeye başlandı… - %d %s indirildi + %d %s indirilmeye başlandı + %d %s başarıyla indirildi %s\'nin tamamı zaten indirildi Toplu indir eklenti @@ -459,123 +538,40 @@ Devre dışı: %d İndirilmeyen: %d %d eklenti(ler) güncellendi - CloudStream\'in varsayılan olarak yüklü sitesi yoktur. Siteleri depolardan kurmanız gerekir. -\n -\nSky UK Limited tarafından beyinsiz bir DMCA yayından kaldırma 🤮 nedeniyle uygulama içinde depo linklerini bulunduramıyoruz. -\n -\nDiscord\'umuza katılın veya çevrimiçi arama yapın. + Site eklentilerini yüklemek için bir depo ekleyin Topluluk depolarını görüntüle Herkese açık liste Tüm alt yazılar büyük harf - Bu depodaki tüm eklentiler indirilsin mi\? + + Bu depodaki tüm eklentiler indirilsin mi? %s devre dışı bırakıldı Parçalar Ses parçaları Video parçaları Yeniden başlatmada uygula - Güvenli mod açık - Çöküşe neden olan eklentiyi bulmaya yardımcı olabilmek için tüm eklentiler kapatıldı. + + Güvenli mod etkin + Kurtarılamaz bir çökme meydana geldi ve soruna neden olan eklentiyi bulup kaldırabilmeniz için tüm eklentileri otomatik olarak devre dışı bıraktık. Çökme bilgisini göster + Puan: %s Açıklama - Sürüm + Versiyon Durum Boyut Geliştiriciler Desteklenen Dil Önce eklentiyi yükleyin + HLS Oynatma Listesi + Tercih edilen video oynatıcısı Dahili oynatıcı VLC MPV Web Video Yayını - İnternet tarayıcısı + Tarayıcı Uygulama bulunamadı - Geçmiş - İzlendi olarak işaretle - Uygulama güncellemesi yükleniyor… - Kurulum işlemini tekrarla - Fragmanı oynat - Eklenen depolardan henüz yüklenmemiş tüm eklentileri otomatik olarak yükleyin. - Güncelleme başladı - Bazı cihazlar yeni paket yükleyiciyi desteklemez.. Güncellemeler yüklenmezse eski seçeneği deneyin. - Eklentileri otomatik olarak indir - APK indirici - Bağlantılar - Uygulama güncellemeleri - Yedek - Oynatıcı özellikleri - Alt yazılar - Düzen - Varsayılanlar - Eklentiler - Önbellek - Görünüm - Özellikler - Uygulama çıkışta güncellenecektir - Bitiş - Açılış - Hayır - Özet - Çıkmak istediğine emin misin\? - Evet - Uygulama güncellemesi indiriliyor… - Uygulamanın yeni sürümü yüklenemedi - Geçmişi temizle - Paket yükleyici - Eski - Hareketler - Tüm diller - Geç %s - İzlenenlerden kaldır - Karışık son - Karışık başlangıç - Katkıda Bulunanlar - Giriş - Eklenti İndirildi - Eylemler - Açılış/bitiş için atlama açılır pencerelerini göster - Çok fazla metin. Panoya kaydedilemiyor. - Kütüphane - Tarayıcı - Görünüşe göre kütüphaneniz boş :( -\nBir kütüphane hesabına giriş yapın veya yerel kütüphanenize içerik ekleyin - Güvenli mod dosyası bulundu! -\nDosya kaldırılana kadar başlangıçta herhangi bir uzantı yüklenmiyor. - Sırala - Sırala - Güncellenme (Yeniden Eskiye) - Güncellenme (Eskiden Yeniye) - Alfabetik (A\'dan Z’ye) - Alfabetik (Z - A) - Kütüphane Seçin - Şununla aç - Görünüşe göre bu liste boş, başka bir listeye geçmeyi deneyin - Derecelendirme (Yüksekten Düşüğe) - Derecelendirme (Düşükten Yükseğe) - Yeniden başlat - Oynatıcı gizlenmişken atlanacak süre - İSS Kısıtlamaları - GitHub\'a ulaşılamadı, jsdelivr vekil sunucusu etkinleştiriliyor. - Başlat - Başarılı oldu - raw.githubusercontent.com vekil sunucusu (proxy) - Tercih edilen görüntü kalitesi (Mobil veri) - Oynatıcı görünürken atlanacak süre - Oynatıcı gizli durumdayken atlanacak süre miktarı - jsdelivr kullanarak GitHub kısıtlamasını aşar. Güncellemeler birkaç gün gecikebilir. - Android TV - Yeni bölüm %d yayınlandı! - Sağlayıcıyı kontrol et - Başarısız oldu - Durdur - Geri al - Abone olunan gösteriler güncelleniyor - Abone olunan - %s kanalına abone olundu - %s kanalı aboneliğinden çıkıldı - Günlük - Oynatıcı görünür durumdayken atlanacak süre miktarı + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml deleted file mode 100644 index bd062394..00000000 --- a/app/src/main/res/values-uk/strings.xml +++ /dev/null @@ -1,532 +0,0 @@ - - - Постер - Постер до епізоду - Завантаження скасовано - Змінити постачальника - Назад - Рейтинг: %.1f - Актори: %s - Епізод %d вийде через - Poster - %s Еп. %d - %dд %dгод %dхв - %dгод %dхв - %dхв - Головний постер - Наступний випадковий - Попередній перегляд фону - Швидкість (%.2fx) - Знайдено нове оновлення! -\n%s -> %s - Пошук - Завантаження - %d хв - Налаштування - Пошук… - Пошук на %s… - Дані відсутні - Більше опцій - Наступний епізод - Жанри - Відкрити в браузері - Пропустити завантаження - Завантаження… - Завершено - Заплановано - Покинуто - Переглянути фільм - Переглянути трейлер - Трансляція через торрент - Повторити підключення… - Назад - Переглянути епізод - Завантажено - Завантаження - Завантаження завершено - Дуб. - Суб. - Видалити файл - Відновити завантаження - Вимкнути відправку повідомлень про помилки - Сховати - Переглянути - Інфо - Закладки - Вилучити - Змінити статус перегляду - Застосувати - Скопіювати - Закрити - Зберегти - Швидкість програвача - Колір вікна - Тип контуру - Шрифт - Розмір шрифту - Пошук за допомогою постачальників - Пошук за типами - Бананів немає - Автовибір мови - Завантажити мови - Мова субтитрів - Утримуйте для скидання до налаштувань за замовчанням - Імпортуйте шрифти, помістивши їх у %s - Продовжити перегляд - Вилучити - Детальніше - Цей постачальник є торрентом, рекомендується VPN - Опис - Сюжет не знайдено - Опис не знайдено - Показати Logcat 🐈 - Продовження відтворення в мініатюрному плеєрі поверх інших програм - Прибирає чорні рамки - Субтитри - Субтитри Chromecast - Налаштування субтитрів Chromecast - Режим Eigengravy - Проведіть пальцем, щоб змінити налаштування - Проведіть пальцем вгору або вниз ліворуч або праворуч, щоб змінити яскравість або гучність - Відтворення наступного епізоду після закінчення поточного - Головна - CloudStream - Філер - Програти в CloudStream - Трансляція - Переглядаю - Поділитися - Відкладено - Повторний перегляд - Завантажити - Переглянути трансляцію - Джерела - Субтитри - Внутрішнє сховище - Завантаження призупинено - Завантаження розпочато - Помилка завантаження - Оновлення розпочато - Помилка завантаження посилань - Призупинити завантаження - Переглянути файл - Детальніше - Фільтр закладок - Нічого - Скасувати - Очистити - Налаштування субтитрів - Колір фону - Висота субтитрів - Колір тексту - Колір контуру - Автовідтворення наступного епізоду - Проведіть пальцем з боку в бік, щоб керувати своїм положенням у відео - %d Бананів для розробників - Кнопка зміни розміру плеєра - @string/home_play - Для коректної роботи цього постачальника може знадобитися VPN - Метадані не надаються сайтом, завантаження відео не відбудеться, якщо їх немає на сайті. - Картинка в картинці - Налаштування субтитрів плеєра - Додає опцію швидкості в плеєрі - Проведіть пальцем, щоб перемотати - Двічі торкніться, щоб перемотати - Двічі торкніться для паузи - Крок перемотки (Секунди) - Натисніть двічі посередині, щоб призупинити - Використовувати яскравість системи - Оновити прогрес перегляду - Відновлення даних з резервної копії - Резервне копіювання даних - Не вдалося відновити дані з файлу %s - Дані збережено - Помилка резервного копіювання %s - Пошук - Облікові записи - Оновлення та резервне копіювання - Інформація - Розширений пошук - Надає результати пошуку, розділені за постачальниками - Надсилає дані лише про збої - Не надсилає даних - Показати заповнюючий епізод для аніме - Показати трейлери - Приховати вибрану якість відео в результатах пошуку - Автоматичне завантаження плагінів - Показати оновлення програми - Повторний процес налаштування - Пошук лише попередніх оновлень, а не повних релізів - Встановлювач APK - Github - Застосунок для легких новел від тих же розробників - Застосунок для аніме від тих же розробників - Дайте бананів розробникам - Мова програми - Цей постачальник не має підтримки Chromecast - Посилань не знайдено - Переглянути епізод - Скинути до значення за замовчуванням - Немає сезону - епізодів - %d %s - С - Е - Видалити файл - Видалити - Відновити - -30 - Це назавжди видалить %s -\nВи впевнені\? - %dхв -\nзалишилося - Триває - Завершено - Рейтинг - Тривалість - у черзі - Без субтитрів - За замовчуванням - Вільно - Зайнято - Програма - Телесеріали - Мультфільми - Аніме - ОВА - Азіатські драми - Прямі трансляції - Інше - Серіал - Мультфільм - Аніме - Документальний фільм - Азіатська драма - Пряма трансляція - Відео - Помилка джерела - Віддалена помилка - Помилка рендеринга - Дзеркало Chromecast - Переглянути в програмі - Переглянути в %s - Автозавантаження - Завантажити дзеркало - Перевірити наявність оновлень - Заблокувати - Пропустити OP - Не показувати знову - Оновити - Бажана якість перегляду (WiFi) - Заголовок - Перемикання елементів інтерфейсу на плакаті - Оновлення не знайдено - Двічі торкніться праворуч або ліворуч, щоб перейти вперед або назад - Використовуйте системну яскравість у плеєрі замість темної накладки - Завантажено файл резервної копії - Торренти - Автоматична синхронізація прогресу поточного епізоду - Відсутні дозволи на зберігання. Будь ласка, спробуйте ще раз. - Показати постери від Kitsu - Автоматичне оновлення плагінів - Автоматично встановлювати всі ще не встановлені плагіни з доданих репозиторіїв. - Автоматично шукати нові оновлення після запуску застосунку. - Оновлення до бета-версій - Посилання скопійовано в буфер обміну - Деякі телефони не підтримують новий інсталятор пакетів. Спробуйте стару версію, якщо оновлення не встановлюються. - Приєднуйтесь до Discord - Дано бананів - Рік - +30 - Вибачте, у застосунку стався збій. Анонімне повідомлення про помилку буде відправлено розробникам - %s %d%s - Епізод - %d-%d - Епізодів не знайдено - Пауза - Сезон - Статус - Сайт - Змінити розмір - Короткий зміст - Фільми - Скопіювати посилання - Перезавантажити посилання - Документальні фільми - NSFW - Фільм - OVA - Торрент - Мітка якості - NSFW - Переглянути в браузері - Несподівана помилка плеєра - Помилка завантаження, перевірте дозволи на зберігання - Епізод Chromecast - Мітка субтитрів - Джерело - Завантажити субтитри - Мітка дубляжу - Пропустити це оновлення - Усе - На весь екран - Заповнити - Збільшити - Доріжки - Оновлення програми - Кеш - Жести - Особливості плеєра - Субтитри - За замовчуванням - Вигляд - Особливості - Загальне - Випадкова кнопка - Показує кнопку на Головній сторінці, яка може вибрати випадковий фільм або серіал на Головній сторінці - Мови постачальника - Макет програми - Бажані медіа - Авто - Макет телевізора - Макет телефону - Макет емулятора - Основний колір - Тема програми - Розташування назви постера - Розмістіть назву під постером - пароль123 - Моє круте ім\'я - hello@world.com - Мій крутий сайт - Код мови (uk) - Вийти - Увійти - Змінити обліковий запис - Додати обліковий запис - Додати відстеження - Синхронізація - %d / 10 - /%d - %s автентифіковано - Не вдалося ввійти у %s - Нічого - Нормальний - Мін. - Контур - Тінь - Піднятий - Синхронізувати субтитри - Затримка субтитрів - Без затримки субтитрів - Рекомендовано - Завантажити з інтернету - Завантажений файл - Головна - Джерело - Випадковий - Скоро буде… - WP - SD - UHD - HDR - SDR - Web - Зображення постера - Плеєр - Роздільна здатність та назва - Недійсний ID - Недійсна URL-адреса - Резервне копіювання - Макс. кількість символів у назві відеоплеєра - Розмір буфера відео - Кеш відео на диску - Клон сайту - Видалити сайт - URL-адреса сервера NGINX - Посилання - Роздільна здатність відеоплеєра - Довжина буфера відео - Очистити кеш відео та зображень - Спричиняє збої, якщо встановлено занадто високе значення на пристроях із малим об\'ємом пам\'яті, наприклад Android TV. - Корисно для обходу блокувань провайдера - Спричиняє збої, якщо встановлено занадто високе значення на пристроях із малим об\'ємом вільної пам\'яті, наприклад Android TV. - DNS через HTTPS - Шлях завантаження - Додайте клон існуючого сайту, з іншою URL-адресою - Відображати Дубляж/Субтитри аніме - Застереження - Розширення - Дії - 127.0.0.1 - Макет - Кодування субтитрів - Включити NSFW на підтримуваних постачальників - Макет - Постачальники - example.com - %s %s - Депресивний - обліковий запис - Створити обліковий запис - Додано %s - /\?\? - Оцінений - Завантажити з файлу - Макс. - Щастям б\'єш жук їх глицю в фон й ґедзь пріч - 1000 мс - Використовуйте цей параметр, якщо субтитри з\'являються на %d мс занадто рано - Використовуйте це, якщо субтитри з\'являються із запізненням на %d мс - Завантажено %s - Підтримка - Фон - Blu-ray - Видалити закриті титри з субтитрів - DVD - Недійсні дані - Фільтрувати за бажаною мовою медіа - 4K - Назва - Роздільна здатність - Помилка - Трейлер - Додатково - Посилання на трансляцію - Cam - Cam - Cam - HQ - HD - TS - TC - Видалити роздуття субтитрів - Реферер - Далі - Дивіться відео на цих мовах - Пропустити налаштування - Звіт про збої - Що ви хочете побачити - Готово - Розширення - Додати репозиторій - Назва репозиторію - URL-адреса репозиторію - Плагін завантажено - Плагін завантажено - Не вдалося завантажити %s - Почалося завантаження %d %s… - Завантажено %d %s - Всі %s вже завантажено - Пакетне завантаження - плагін - плагіни - Видалити репозиторій - Завантажте список сайтів, які ви бажаєте використовувати - Завантажено: %d - Вимкнено: %d - Не завантажено: %d - Оновлено %d плагіни - За замовчуванням в CloudStream не встановлені сайти. Вам потрібно встановити сайти з репозиторіїв. -\n -\nЧерез безмозкий DMCA від Sky UK Limited 🤮 ми не можемо прив\'язати сайт репозиторію в застосунку. -\n -\nПриєднуйтесь до нашого Discord або шукайте в інтернеті. - Переглянути репозиторії спільноти - Публічний список - Усі субтитри у верхньому регістрі - %s (Вимкнено) - Відео доріжки - Застосувати при перезавантаженні - Переглянути інформацію про збій - Рейтинг: %s - Опис - Версія - Статус - Розмір - Підтримується - Мова - Спочатку встановіть розширення - Список відтворення HLS - Вбудований плеєр - VLC - MPV - Відтворення веб-відео - Веб-браузер - Кінець - Коротке повторення - Пропустити %s - Змішаний кінець - Подяки - Опенінг - Вступ - Очистити історію - Історія - Показувати спливаючі вікна для опенінгу/кінця - Забагато тексту. Не вдалося зберегти в буфер обміну. - Позначити як переглянуте - Ви впевнені що хочете вийти\? - Так - Ні - Установлення оновлення програми… - Не вдалося встановити нову версію програми - Старий - Інсталятор пакетів - Програму буде оновлено після виходу - Це також призведе до видалення всіх плагінів репозиторію - Всі мови - Назад - Змініть вигляд застосунку відповідно до вашого пристрою - Плагін видалено - 18+ - Аудіо доріжки - Завантажити всі плагіни з цього репозиторію\? - Бажаний відеоплеєр - Увімкнено безпечний режим - Автори - Завантаження оновлення програми… - Усі розширення вимкнено через збій, щоб допомогти вам знайти те, що спричиняє проблеми. - Програму не знайдено - Змішаний опенінг - Видалити з переглянутого - За оновленням (від старого до нового) - За оновленням (від нового до старого) - Бібліотека - Сортувати - За рейтингом (від високого до низького) - Сортувати за - За алфавітом (від А до Я) - За рейтингом (від низького до високого) - Схоже, ваша бібліотека порожня :( -\nУвійдіть в обліковий запис бібліотеки або додайте серіали до вашої локальної бібліотеки - За алфавітом (від Я до А) - Виберіть бібліотеку - Відкрити з - Браузер - Схоже, цей список порожній, спробуйте перейти до іншого - Файл безпечного режиму знайдено! -\nРозширеня не завантажуються під час запуску, доки файл не буде видалено. - Android TV - Плеєр сховано - обсяг пошуку - Плеєр показано - обсяг пошуку - Обсяг пошуку, який використовується, коли плеєр видимий - Обсяг пошуку, який використовується, коли гравець прихований - Не вдалося - Пройдено - Перезапуск - Журнал - Старт - Стоп - Тест постачальника - Оновлення підписаних шоу - Підписано - Підписано на %s - Відписатися від %s - Епізод %d випущено! - Повернути - raw.githubusercontent.com Proxy - Не вдалося зв\'язатися з GitHub, увімкнувши проксі-сервер jsdelivr. - Обходи ISP - За допомогою jsdelivr можна обійти блокування GitHub. Можлива затримка оновлень на кілька днів. - Бажана якість перегляду (Мобільні дані) - diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml deleted file mode 100644 index c19c6472..00000000 --- a/app/src/main/res/values-ur/strings.xml +++ /dev/null @@ -1,359 +0,0 @@ - - - کاسٹ: %s - قسط %d جاری کیا جائے گا - پروموشنل تصویر - قسط کا پوسٹر - مرکزی پوسٹر - اگلا بے ترتیب - رجوع - ذریعہ تبدیل کریں - پس منظر کا دیکھنا - درجہ بندی: %.1f - نیا update آگیا ہے! -\n%s -> %s - بھرنے والا - %d منٹ - %d دن %d گھنٹے %d منٹ - %d گھنٹے %d منٹ - %d منٹ - CloudStream - CloudStream کے ساتھ چلائیں - ہوم - تلاش کریں - ڈاؤن لوڈ - ترتیبات - تلاش کریں… - فراہم کنندہ میں تلاش کریں%s… - مزید آپشنز - اگلا قسط - براؤزر میں کھولیں - لوڈ کرنا سکیپ کر دیں - لوڈ ہو رہا ہے… - دیکھنا - آن ہولڈ - گرا دیا - دیکھنا ہے - کوئی نہیں - دوبارہ دیکھنا - مووی لگائے - ٹریلر چلائیں - لائیو اسٹریم چلائیں - سٹریم ٹورینٹ - ذرائع - کنکشن کی دوبارہ کوشش کی جارہی ہے… - واپس جاو - قسط چلائیں - ڈاؤن لوڈ کردہ ویڈیوز - ڈاؤن لوڈ ہو رہا ہے - ڈاؤن لوڈ موقوف - ڈاؤن لوڈ شروع ہو گیا - ڈاؤن لوڈ نہیں ہو پا رہا - ڈاؤن لوڈ منسوخ ہو گیا - سٹریم - لنک میں مسئلہ - ڈب - سبد - فائل کو ڈیلیٹ کریں - کوئی مواد نہیں - فائل چلائیں - ڈاؤن لوڈ دوبارہ شروع کریں - مزید معلومات - چھپائیں - چلائیں - معلومات - بک مارکس کو فلٹر کریں - بک مارکس - ریمو - واچ اسٹیٹس کو سیٹ کریں - لاگو کریں - منسوخ کریں - کاپی - بند کریں - صاف کریں - محفوظ کریں - چلانے کی رفتار - Subtitles کی رفتار - لکھائی کا رنگ - آؤٹ لائن کا رنگ - پس منظر کا رنگ - کھڑکی کا رنگ - کنارے کی قسم - ترجمہ کی اونچائی - لکھائی - حرف کا سائز - انواع کا استعمال کرتے ہوئے تلاش کریں - %d ایپ بنانے والے کیلئے 🍌 - کیلے نہیں - زبان خود بخود منتخب کریں - زبانیں ڈاؤن لوڈ کریں - ترجمہ کی زبان - لکھائی کو یہاں رکھ کر درآمد کریں %s - دیکھنا جاری رکھیں - حذف کریں - مزید معلومات - کھلا (آن) - یہ سورس Torrent ہے، ضرورت پڑنے پر VPN کا استعمال کریں - تفصیل - کوئی تفصیل نہیں ملی - کوئی تفصیل نہیں ملی - غلطی کا لاگ دیکھیں 🐈 - PIP - دوسری ایپس کے اوپر ایک منی لانچر میں چلتا رہنا - پلیئر کا سائز تبدیل کرنے کا بٹن - سیاہ سرحد کو ہٹا دیں - سنٹیٹلز - Chromecast ترجمہ - Chromecast ترجمہ کی ترتیبات - پلیئر کی رفتار - پلیئر میں رفتار کا اختیار شامل - ویڈیو کو آگے سوائپ کریں - ترتیبات کو تبدیل کرنے کے لیے سوائپ کریں - اگلا ایپی سوڈ خود بخود چلائیں - موجودہ قسط ختم ہونے پر اگلی قسط شروع کریں - آگے یا پیچھے جانے کے لیے سکرین پر دو بار تھپتھپائیں - روکنے کے لیے دو بار تھپتھپائیں - آگے یا پیچھے کی طرف جانے کے لیے دائیں یا بائیں جانب دو بار تھپتھپائیں - سسٹم کی Brightness کا استعمال کریں - ڈارک overlay کے بجائے ایپ پلیئر میں سسٹم کی Brightness کا استعمال کریں - بیک اپ سے ڈیٹا کو بحال کریں - ڈیٹا کا بیک اپ لیں - بیک اپ فائل اپ لوڈ کر دی گئی ہے - فائل سے ڈیٹا درآمد کرنا ناکام %s - ذخیرہ شدہ ڈیٹا - بیک اپ بنانا ناکام ہوگیا %s - دیکھنے کی پیشرفت کو اپ ڈیٹ کریں - تلاش کریں - اکاؤنٹس - معلومات - آپ کو فراہم کنندہ کے ذریعہ علیحدہ کردہ تلاش کے نتائج فراہم کرتا ہے - صرف کریش پر ڈیٹا بھیجتا ہے - کوئی ڈیٹا نہیں بھیجتا - تلاش کے نتائج میں منتخب ویڈیو کوالٹی چھپائیں - خودکار پلگ ان اپ ڈیٹس - ایپ کی تازہ کاریاں نمایش کریں - سیٹ اپ کا عمل دوبارہ کریں - پری ریلیز کے لیے اپ ڈیٹ کریں - صرف مکمل ریلیز کے بجائے پری ریلیز اپ ڈیٹس تلاش کریں - APK انسٹالر - Github - ایک ہی dev کی طرف سے light ناول اپلی کیشن - اسی devs کے ذریعے anime ایپ - ڈسکارڈ میں شامل ہوں - دیووں کو ایک بینین دیں - دی گئی بینین - ایپ کی زبان - اس فراہم کنندہ کے پاس کروم کاسٹ سپورٹ نہیں ہے - کوئی لنکس نہیں ملے - کلپ بورڈ میں نقل ہوا - قسط چلائیں - طے شدہ قدر پر ری سیٹ کریں - %s قسط %d - پوسٹر - شیئر - انواع - رفتار (%.2fx) - مکمل - سب ٹائٹلز - ڈاؤنلوڈ - ڈاؤن لوڈ ہو گیا - انٹرنل سٹوریج - ڈاؤن لوڈ کو روکیں - خودکار بگ رپورٹنگ کو غیر فعال کریں - ذرائع کا استعمال کرتے ہوئے تلاش کریں - اس فراہم کنندہ کو مناسب طریقے سے کام کرنے کے لیے VPN کی ضرورت پڑ سکتی ہے - ڈیفالٹ سیٹنگز پر ری سیٹ کرنے کے لیے دبائیں اور تھامیں - سائٹ کی طرف سے میٹا ڈیٹا فراہم نہیں کیا گیا ہے، اور اگر ویڈیو سائٹ میں موجود نہیں ہے تو وہ لوڈ ہونے میں ناکام ہو جائے گا. - پلیئر کے ترجمہ کی ترتیبات - ویڈیو پلیئر میں وقت کو کنٹرول کرنے کے لیے بائیں یا دائیں سوائپ کریں - سکرین کی روشنی یا والیوم تبدیل کرنے کے لیے بائیں یا دائیں سوائپ کریں - کنٹرول کریں کہ پلیئر کتنا آگے ہے - توقف کرنے کے لیے درمیان میں دبائیں - سٹوریج کی اجازتیں غائب ہیں، براہ کرم دوبارہ کوشش کریں۔ - کٹسو سے پوسٹر نمایش کریں - آپ کی حالیہ قسط کی پیش رفت خود بخود مطابقت پذیر کریں - اپ ڈیٹس اور بیک اپ - اعلی درجے کی تلاش - ٹریلر نمایش کریں - anime کے لیے فلر ایپیسوڈ دکھائیں - پلگ ان خود بخود ڈاؤن لوڈ کریں - شامل کردہ ذخیروں سے خود بخود تمام ابھی تک انسٹال نہیں ہوئے پلگ ان انسٹال کریں۔ - شروع ہونے پر خودکار طور پر نئی اپ ڈیٹس تلاش کریں - کچھ فون نئے پیکیج انسٹالر کو سپورٹ نہیں کرتے ہیں. اگر اپ ڈیٹس انسٹال نہیں ہوتے ہیں تو لیگیسی آپشن کو آزمائیں. - معذرت، ایپلی کیشن کریش ہو گئی. ایک گمنام بگ رپورٹ ڈویلپرز کو بھیجی جائے گی - سیزن - %s %d%s - کوئی سیزن نہیں - قسط - اقساط - %d-%d - %d %s - S - E - کوئی اقساط نہیں ملی - فائل کو ڈیلیٹ کریں - مٹا دیں - توقف - از سر نو شروع کریں - -30 - +30 - یہ مستقل طور پر حذف ہوجائے گا %s -\nتمھيں يقين ہے\? - %dm -\nباقی - احوال - مکمل - حالت - سال - درجہ بندی - دورانیہ - سائٹ - خلاصہ - قطار میں - کوئی سب ٹائٹلز نہیں - ڈیفالٹ - مفت - استعمال شُدہ - کارٹون - App - فلمیں - ٹی وی سیریز - ٹورینٹ - anime - دستاویزی فلمیں - او وی اے - ایشیائی ڈرامے - لائیو اسٹریمز - این ایس ایف ڈبلیو - دوسرے - فلم - سلسلہ - کارٹون - انیمی - اووا - ٹورینٹ - دستاویزی فلم - ایشیائی ڈرامے - لائیو اسٹریمز - ویڈیو - ماخذ نقص - بعید نقص - پیش کنندہ کی خرابی - غیر متوقع پلیئر نقص - ڈاؤن لوڈ کی خرابی، سٹوریج کی اجازتیں چیک کریں - کروم کاسٹ قسط - کروم کاسٹ مرر - ایپ میں چلائیں - %s میں چلائیں - کاپی لنک - ڈاؤنلوڈ مرر - لنکس کو دوبارہ لوڈ کریں - سب ٹائٹلز ڈاؤن لوڈ - کوالٹی لیبل - ڈب لیبل - ذیلی لیبل - عنوان - پوسٹر پر یو آئی عناصر کو ٹوگل کریں - کوئی اپ ڈیٹ نہیں ملا - اپ ڈیٹ کے لیے چیک کریں - تالا - سائز تبدیل کریں - ماخذ - او پی چھوڑیں - دوبارہ نہ دکھائیں - اس اپ ڈیٹ کو چھوڑ دیں - اپ ڈیٹ - ترجیحی ویڈیو کا معیار - ویڈیو پلیئر کے عنوان کے زیادہ سے زیادہ حروف - ویڈیو پلیئر ریزولوشن - ویڈیو بفر سائز - ویڈیو بفر لمبائی - ڈسک پر ویڈیو کیش - ویڈیو اور تصویری کیشے صاف کریں - کم اسٹوریج اسپیس والے آلات، جیسے کہ Android TV پر بہت زیادہ سیٹ کرنے پر مسائل کا سبب بنتا ہے. - HTTPS پر DNS - آئی ایس پی بلاکس کو بائی پاس کرنے کے لئے مفید - کلون سائٹ - ڈاؤنلوڈ کی جگہ - NGINX سرور URL - ڈسپلے ڈب/سببیڈ انیمی - زوم - دستبرداری - لینکس - ایپ کی تازہ کاریاں - بیک اپ - کیسہ - اشارے - توسیعات - اعمال - پلیئر کی خصوصیات - سب ٹائٹلز - لے آؤٹ - طے شدہ - لگتا ہے - خصوصیات - عمومی - بے ترتیب بٹن - ہوم پیج پر بے ترتیب بٹن نمایش کریں - فراہم کنندہ کی زبانیں - ایپ لے آؤٹ - ترجیحی میڈیا - معاون فراہم کنندگان پر NSFW اہل بنائیں - سب ٹائٹل انکوڈنگ - فراہم کنندگان - لے آؤٹ - خودکار - ٹی وی لے آؤٹ - فون لے آؤٹ - ایمولیٹر لے آؤٹ - بنیادی رنگ - ایپ تھیم - پوسٹر کا عنوان محل وقوع - عنوان کو پوسٹر کے نیچے رکھیں - password123 - مائی کولUsername - hello@world.com - 127.0.0.1 - مائی کول سائٹ - example.com - زبان کا کوڈ (en) - اكاؤنٹ - لاگ آوٹ - لاگ ان کریں - /\?\? - /%d - %s تصدیق شدہ - %s پر لاگ ان نہیں ہو سکا - کچھ نہیں - عام - سب - زیادہ سے زیادہ - کم از کم - خاکہ - سایہ - ذیلی ہم وقت سازی کریں - 1000 ms - سب ٹائٹل تاخیر - اگر سب ٹائٹلز %d ms بہت جلد دکھائے جائیں تو اسے استعمال کریں - سب ٹائٹل تاخیر - این ایس ایف ڈبلیو - آٹو ڈاؤن لوڈ - براؤزر میں چلائیں - کم میموری والے آلات، جیسے کہ Android TV پر بہت زیادہ سیٹ ہونے پر کریشوں کا سبب بنتا ہے. - سائٹ ہٹائیں - ایک مختلف URL کے ساتھ ، موجودہ سائٹ کا کلون شامل کریں. - اسکرین پر فٹ - کھینچیں - اکاؤنٹ بنائیں - %s %s - اکاؤنٹ سوئچ کریں - ٹریکنگ شامل کریں - تواقت - دپرسڈ - اکاؤنٹ شامل کریں - شامل کیا گیا %s - ریٹیڈ - %d / 10 - اٹھایا - اگر سب ٹائٹلز %d ms بہت جلد دکھائے جائیں تو اسے استعمال کریں - diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 520cfaa4..ce8358cf 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1,7 +1,7 @@ - + %s Tập %d Diễn viên: %s @@ -9,30 +9,34 @@ %dd %dh %dm %dh %dm %dm + Poster - Ảnh bìa + @string/result_poster_img_des Episode Poster Main Poster Next Random Go back Change Provider Preview Background + Tốc độ (%.2fx) Đánh giá: %.1f - Đã có bản cập nhật mới! -\n%s -> %s + Đã có bản cập nhật mới!\n%s -> %s Bộ lọc %d phút + CloudStream Mở với CloudStream Trang Chủ Tìm Kiếm Tải Về Cài Đặt + Tìm kiếm… Tìm kiếm %s… + Không có dữ liệu Thêm tuỳ chọn Tập tiếp theo @@ -41,6 +45,7 @@ Mở bằng trình duyệt Bỏ qua Đang tải… + Đang xem Đang chờ Đã xem @@ -48,6 +53,7 @@ Xem sau Mặc định Xem lại + Xem Ngay Phát trực tiếp Xem Torrent @@ -57,6 +63,7 @@ Quay lại Xem Tập Phim + Tải xuống Đã tải Đang tải @@ -66,14 +73,18 @@ Đã hủy Tải thành công Trực tiếp + Đã có lỗi xảy ra - Bộ nhớ trong + Bộ nhớ máy + Lồng Tiếng Phụ Đề + Xóa Tệp Xem Tệp Tiếp tục tải Tạm dừng tải + Tắt tự động gửi dữ liệu khi xảy ra lỗi Thông tin thêm Ẩn @@ -89,7 +100,9 @@ Đóng Huỷ bỏ Lưu + Tốc độ phát + Cài đặt hiển thị phụ đề Màu chữ Màu viền chữ @@ -99,60 +112,81 @@ Độ nâng Kiểu chữ Kích thước chữ + Tìm kiếm theo nguồn phim Tìm kiếm theo thể loại + %d lời cảm ơn đã được gửi tặng nhà phát triển Hãy tặng cho nhà phát triển một lời cảm ơn + Tự động chọn ngôn ngữ Ngôn ngữ khi tải xuống Ngôn ngữ phụ đề Giữ để làm mới toàn bộ Thêm phông chữ tại %s Tiếp tục xem + Loại bỏ Thông tin thêm @string/home_play + Bạn có thể sẽ cần sử dụng VPN để xem phim này Phim này được chiếu dưới dạng Torrent. Hãy sử dụng VPN để xem + + Siêu dữ liệu không được cung cấp bởi trang web, quá trình tải video sẽ không thành công nếu nó không tồn tại trên trang web. + Thông tin phim Đang cập nhật Không tìm thấy thông tin - Hiển thị Logcat 🐈 + + Hiển thị logcat 🐈 + Chế độ cửa sổ nhỏ - Tiếp tục xem phim khi thoát ứng dụng hoặc khi tìm kiếm + Tiếp tục xem phim khi thoát app hoặc đang tìm kiếm Bật nút thu phóng khi xem Xóa khoảng đen của phim Phụ đề Cài đặt phụ đề Phụ đề Chromecast Cài đặt phụ đề Chromecast + Chỉnh tốc độ phim Có thể điều chỉnh tốc độ phát phim Vuốt để tua nhanh Bạn có thể vuốt trái hoặc phải để tua nhanh khi xem phim Vuốt để chỉnh độ sáng và âm lượng Vuốt từ dưới lên trên ở bên trái hoặc phải đều điều chỉnh độ sáng và âm lượng + Tự động phát tập tiếp theo Phát tập tiếp theo sau khi hết tập hiện tại + Nhấn 2 lần để tua Nhấn 2 lần để tạm dừng Thời lượng tua Nhấn 2 lần vào bên trái hoặc bên phải màn hình để tua trước hoặc sau + Nhấn vào giữa để tạm dừng Sử dụng độ sáng hệ thống Sử dụng độ sáng hệ thống trong trình phát ứng dụng + + + Cập nhật tiến trình xem Tự động đồng bộ tiến trình hiện tại của bạn + Khôi phục dữ liệu từ bản sao lưu + Sao lưu dữ liệu Đã tải dữ liệu sao lưu Không thể khôi phục dữ liệu từ %s Khôi phục dữ liệu thành công - Thiếu quyền truy cập bộ nhớ, hãy thử lại. + Thiếu quyền truy cập bộ nhớ, hãy thử lại Lỗi khi sao lưu %s + Tìm kiếm Tài khoản Cập nhật và sao lưu + Thông tin Tìm kiếm nâng cao Cho phép tìm kiếm theo bộ lọc từng nhà cung cấp @@ -160,8 +194,9 @@ Không gửi dữ liệu Hiển thị tập phụ cho anime Hiển thị trailer - Hiển thị poster từ Kitsu - Ẩn chất lượng video khi tìm kiếm + Hiển thị poster từ kitsu + Ẩn chất lượng Video khi tìm kiếm + Tự động cập nhật plugin Hiển thị thông báo cập nhật App Tự động tìm kiếm và thông báo khi có bản cập nhật mới @@ -173,13 +208,18 @@ Tham gia cộng đồng trên Discord Gửi lời cảm ơn tới nhà phát triển Gửi lời cảm ơn + Ngôn ngữ ứng dụng + Nguồn phim này chưa hỗ trợ Chromecast Không tìm thấy link Đã copy link vào bộ nhớ đệm Xem Phim Làm mới toàn bộ cài đặt Rất tiếc! Ứng dụng đã xảy ra lỗi. Chúng tôi đang tìm hiểu nguyên nhân và khắc phục nó + + + Mùa Không có mùa nào Tập @@ -189,16 +229,17 @@ M T Không có tập nào + Xóa Tệp Xóa Tạm Dừng Tiếp Tục -30 +30 - %s sẽ bị xoá vĩnh viễn -\nBạn có chắc chắn muốn xóa\? - %dm -\ncòn lại + %s sẽ bị xoá vĩnh viễn\nBạn có chắc chắn muốn xóa? + %dm\ncòn lại + + Đang Chiếu Hoàn Thành Trạng Thái @@ -207,12 +248,15 @@ Thời Lượng Nguồn Thông tin + Hàng chờ Không có phụ đề Mặc Định + Còn trống - Đã sử dụng + Đã dùng App + Phim Lẻ Phim Bộ @@ -225,11 +269,12 @@ Trực tiếp NSFW Khác + Phim Lẻ Phim Bộ Hoạt Hình - Anime + @string/anime @string/ova Torrent Phim Tài Liệu @@ -237,11 +282,13 @@ Trực tiếp NSFW Video + Lỗi nguồn phim Lỗi kết nối tới máy chủ Không thể render Đã có lỗi xảy ra. Vui lòng thử lại sau Lỗi tải xuống. Hãy kiểm tra quyền truy cập bộ nhớ của ứng dụng + Tập Chromecast Chiếu Chromecast Xem với trình phát mặc định @@ -252,41 +299,55 @@ Nguồn tải xuống Lấy link mới nhất Tải phụ đề + Nhãn chất lượng phim Nhãn lồng tiếng Nhãn phụ đề Tiêu đề Thay đổi giao diện trên poster + Bạn đang dùng phiên bản mới nhất Kiểm tra cập nhật + Khóa Thu Phóng - Nguồn - Bỏ qua OP + Tuỳ chọn + Tập tiếp + Không hiện lại - Bỏ qua bản cập nhật này + Bỏ qua Cập nhật - Chất lượng xem ưu tiên (WiFi) + Tự động chọn chất lượng phim Kí tự tối đa trên tiêu đề - Độ phân giải trình phát video - Kích thước bộ nhớ đệm video + Định dạng trình phát + + Dung lượng video cache Thời lượng bộ nhớ đệm - Lưu bộ nhớ đệm video trên ổ cứng - Xoá bộ nhớ đệm hình ảnh và video - Sẽ gây lỗi nếu đặt quá cao trên máy có dung lượng ram thấp như Android TV. - 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. + Dung lượng video cache + Xoá hình ảnh và video + + Sẽ gây lỗi nếu đặt quá cao. Không thay đổi nếu máy có dung lượng ram thấp, chẳng hạn như Android TV hoặc điện thoại cũ + Sẽ thể gây lỗi trên các máy có dung lượng lưu trữ thấp, chẳng hạn như thiết bị Android TV nếu bạn đặt nó quá cao + DNS over HTTPS Rất hữu ích để bỏ chặn ISP + Sao chép trang web Xoá trang web Thêm bản sao của một trang web, với một địa chỉ khác + Đường dẫn tải xuống + Địa chỉ máy chủ Nginx + Hiển thị nhãn Phụ đề hoặc Thuyết minh + Vừa màn hình Kéo dãn Phóng to + Disclaimer + Tổng quan Nút ngẫu nhiên Hiện nút ngẫu nhiên trên trang chủ @@ -297,14 +358,18 @@ Mã hoá phụ đề Nguồn phim Giao diện + Tự động Giao diện TV Giao diện điện thoại Giao diện giả lập + Màu chính Chủ đề App Vị trí tiêu đề Đặt tiêu đề dưới poster + + Mật khẩu Tài khoản @@ -313,6 +378,7 @@ Địa chỉ trang web example.com Mã ngôn ngữ (vi) + %s %s tài khoản Đăng xuất @@ -325,10 +391,11 @@ Đồng bộ Đánh giá %d / 10 - /\?\? + /?? /%d Đã xác thực %s Không thể xác thực %s + Mặc định Bình thường @@ -345,6 +412,7 @@ Dùng nếu phụ đề bị nhanh %dms Dùng nếu phụ đề bị trễ %dms Không chỉnh + Xem trước mẫu phụ đề + Được đề xuất Đã tải %s Chọn từ máy @@ -360,9 +429,12 @@ Vai chính Vai phụ Lý lịch + Nguồn Ngẫu nhiên + Sắp có… + Cam Cam Cam @@ -370,7 +442,7 @@ HD TS TC - Blu-ray + BlueRay WP DVD 4K @@ -379,6 +451,7 @@ HDR SDR Web + Áp phích Trình phát Độ phân giải và Tiêu đề @@ -411,7 +484,7 @@ Đã xoá plugin Không tải được %s 18+ - Bắt đầu tải %d %s… + Bắt đầu tải %d %s Tải xuống %d %s thành công Toàn bộ %s đã được tải xuống Tải hàng loạt @@ -423,106 +496,19 @@ Đã tải: %d Đã vô hiệu: %d Không tải: %d - CloudStream không có sẵn trang web nào. Bạn cần cài đặt các trang web từ kho lưu trữ. -\n -\nDo Sky UK Limited đã gỡ xuống theo DMCA một cách thiếu suy nghĩ 🤮 chúng tôi không thể cài sẵn trang web. -\n -\nHãy tham gia Discord của chúng tôi hoặc tìm kiếm trực tuyến. + Thêm kho lưu trữ để cài tiện ích Xem kho lưu trữ của cộng đồng Danh sách công khai In hoa toàn bộ phụ đề - Tải toàn bộ plugin từ kho lưu trữ này\? + + Tải toàn bộ plugin từ kho lưu trữ này? %s (Đã vô hiệu) Thêm Âm thanh Chất lượng Video Áp dụng khi khởi động lại + Bật chế độ an toàn Đã xảy ra sự cố và chúng tôi đã tự động tắt tất cả các tiện ích mở rộng, hãy tìm và xóa tiện ích mở rộng đang gây ra sự cố. Xem thông tin sự cố - Lịch sử - Đánh dấu là đã xem - Tự động tải xuống plugin - Thiết lập lại - Bộ cài APK - Một số máy không hỗ trợ trình cài đặt gói mới. Hãy thử tùy chọn cũ nếu các bản cập nhật không cài đặt. - %s %d%s - Xem giới thiệu - Tự động tải plugin còn thiếu. - Bắt đầu cập nhật - Liên kết - Danh sách HLS - Trình phát ưu tiên - Trình phát mặc định - Đánh giá: %s - Không - Phiên bản - Tác giả - Cập nhật ứng dụng - Sao lưu - Tiện ích - Hành động - Cache - Cử chỉ - Tính năng trình phát - Phụ đề - Bố cục - Mặc định - Giao diện - Tính năng - Đã cập nhật %d plugin - Mô tả - Trạng thái - Kích thước - Hỗ trợ - Ngôn ngữ - Cài đặt tiện ích trước - VLC - MPV - Web Video Cast - Trình duyệt web - Không thấy ứng dụng - Tất cả ngôn ngữ - Tua %s - Mở đầu - Kết thúc - Tóm tắt - Mở đầu tuỳ chọn - Kết thúc tuỳ chọn - Danh đề - Giới thiệu - Xoá lịch sử - Hiển thị nút tua nhanh cho mở đầu/kết thúc - Văn bản quá dài. Không thể lưu vào bộ nhớ tạm. - Xoá khỏi đã xem - Bạn có chắc muốn thoát\? - - Đang tải bản cập nhật… - Đang cài bản cập nhật… - Không thể cài đặt phiên bản mới - Ứng dụng sẽ được cập nhật khi thoát - Thư viện - Trình duyệt - Plugin đã tải - Mặc định - Tải lên (Mới đến Cũ) - Tải lên (Cũ đến Mới) - Thư viện của bạn đang trống :( -\nHãy đăng nhập vào thư viện hoặc thêm phim vào thư viện cục bộ - Mở với - Siêu dữ liệu không có sẵn, video sẽ không được tải nếu nó không tồn tại trên trang web. - PackageInstaller - Sắp xếp - Xếp hạng (Cao đến Thấp) - Xếp hạng (Thấp đến Cao) - Chữ cái (Z đến A) - Sắp xếp - Có vẻ như danh sách này trống, hãy thử chuyển sang danh sách khác - Chữ cái (A đến Z) - Chọn Thư viện - Nhật ký - Chất lượng xem ưu tiên (Dữ liệu di động) - Thất bại - Thành công - Bắt đầu diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml deleted file mode 100644 index 3364ea86..00000000 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ /dev/null @@ -1,538 +0,0 @@ - - - - - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %.1f/10.0 - %d - %s 共 %d 集 - 演員:%s - 第 %d 集即將發佈於 - %dd %dh %dm - %dh %dm - %dm - - 封面 - 封面 - 劇集封面 - 主封面 - 隨機下一個 - @string/play_episode - 返回 - @string/home_change_provider_img_des - 更改片源 - 預覽背景 - - 速度(%.2fx) - 評分:%.1f - 發現新版本! -\n%s -> %s - 填充 - %d 分鐘 - CloudStream - 使用 CloudStream 播放 - 主頁 - 搜尋 - 下載 - 設定 - 搜尋… - 搜尋 %s… - 無資料 - 更多選項 - 下一集 - @string/synopsis - 類型 - 分享 - 在瀏覽器中打開 - 跳過載入 - 載入中… - 正在觀看 - 暫時擱置 - 觀看完畢 - 放棄觀看 - 計畫觀看 - - 重新觀看 - 播放電影 - 播放直播 - 播放種子 - 來源 - 字幕 - 重試連接… - 返回 - 播放劇集 - - 下載 - 已下載 - 下載中 - 下載暫停 - 下載開始 - 下載失敗 - 下載取消 - 下載完畢 - %s - %s - 播放 - 載入連結錯誤 - 內部存儲 - 配音 - 字幕 - 刪除檔案 - 播放檔案 - 繼續下載 - 暫停下載 - 禁用自動錯誤報告 - 更多資訊 - 隱藏 - 播放 - 資訊 - 篩選書籤 - 書籤 - 移除 - 設定觀看狀態 - 套用 - 取消 - 複製 - 關閉 - 清除 - 保存 - 播放速度 - 字幕設定 - 字體顏色 - 輪廓顏色 - 背景顏色 - 視窗顏色 - 邊緣類型 - 字幕高度 - 字體 - 字體大小 - 按片源搜尋 - 按類型搜尋 - 送開發者 %d 根香蕉 - 不送香蕉 - 自動選擇語言 - 下載語言 - 字幕語言 - 按住重設為預設值 - 將字體導入到 %s - 繼續觀看 - 移除 - 更多資訊 - @string/home_play - 此片源可能需要 VPN 才能正常使用 - 此片源是種子,建議使用 VPN - 站點不提供元數據,如果站點上不存在元數據,影片載入將失敗。 - 簡介 - 未找到簡介 - 未找到簡介 - 顯示 Logcat 🐈 - 字母畫面 - 在其他應用程式上的子母畫面中繼續播放 - 播放器調整大小按鈕 - 移除黑色邊框 - 字幕 - 播放器字幕設定 - Chromecast 字幕 - Chromecast 字幕設定 - 播放速度 - 在播放器中添加播放速度選項 - 活動控制進度 - 左右滑動控制播放進度 - 滑動更改設定 - 上下滑動更改亮度或音量 - 自動播放下一集 - 播放完畢後播放下一集 - 輕按兩下以控制進度 - 輕按兩下以暫停 - 輕按兩下以控制進度時間 - 在右側或左側輕按兩次以向前或向後快轉 - 輕按兩下中間以暫停 - 使用系統亮度 - 在應用程序播放器中使用系統亮度替代黑色遮罩 - 更新觀看進度 - 自動同步當前劇集進度 - 從備份中恢復資料 - 備份資料 - 已載入備份資料 - 無法從 %s 檔案中還原資料 - 已儲存資料 - 缺少儲存權限,請再試一次。 - 備份 %s 錯誤 - 搜尋 - 帳號 - 更新與備份 - 資訊 - 進階搜尋 - 為您提供按片源分開的搜尋結果 - 僅在崩潰時傳送資料 - 不傳送資料 - 顯示動畫外傳 - 顯示預告片 - 顯示來自 Kitsu 的封面 - 在搜尋結果中隱藏選中的影片畫質 - 自動更新外掛程式 - 顯示應用更新 - 啟動時自動搜尋更新 - 更新至預覽版 - 搜尋預覽版更新而不是僅搜尋正式版 - Github - 由相同開發者開發的輕小說應用程式 - 由相同開發者開發的動漫應用程式 - 加入 Discord - 送開發者一根香蕉 - 送香蕉 - 應用程式語言 - 此片源不支援 Chromecast - 未找到連結 - 連結已複製到剪貼簿 - 播放劇集 - 重設為預設值 - 很抱歉,應用崩潰了,將傳送一份匿名錯誤報告給開發者 - - %s %d%s - 無季 - - - %d-%d - %d %s - S - E - 未找到劇集 - 刪除文件 - 刪除 - @string/sort_cancel - 暫停 - 繼續 - -30 - +30 - 這將永遠刪除 %s -\n你確定嗎\? - %d 分鐘 -\n剩餘的 - 連載中 - 已完結 - 狀態 - 年份 - 評分 - 時間 - 網站 - 簡介 - 已加入佇列 - 無字幕 - 預設 - 空閒 - 已使用 - 應用程式 - - 電影 - 電視劇 - 卡通 - 動漫 - 種子 - 紀錄片 - 原創動畫錄影帶 - 亞洲劇 - 直播 - NSFW - 其他 - - 電影 - 電視劇 - 卡通 - @string/anime - @string/ova - 種子 - 紀錄片 - 亞洲劇 - 直播 - NSFW - 其他 - 來源錯誤 - 遠端錯誤 - 渲染器錯誤 - 意料之外的播放器錯誤 - 下載錯誤,請檢查儲存權限 - Chromecast 劇集 - Chromecast 鏡像 - 在應用程式中播放 - 在 %s 中播放 - 在瀏覽器中播放 - 複製連結 - 自動下載 - 下載鏡像 - 重新載入連結 - 下載字幕 - 畫質標籤 - 配音標籤 - 字幕標籤 - 標題 - show_hd_key - show_dub_key - show_sub_key - show_title_key - 封面內容 - 未找到更新 - 检查更新 - 鎖定 - 調整大小 - 來源 - 跳過片頭 - 不再顯示 - 跳過此更新 - 更新 - 偏好播放畫質 - 影片播放器標題最大字數 - 影片播放器標題 - 影片緩衝大小 - 影片緩衝長度 - 影片快取存儲 - 清除影片和圖片快取 - 如果在記憶體不足的裝置(例如 Android TV)上設定得太高會導致崩潰。 - 如果在儲存空間較小的裝置(例如 Android TV)上設定過高會導致問題。 - DNS over HTTPS - 用於繞過網路服務供應商的封鎖 - 複製片源 - 移除片源 - 使用不同的 URL 添加現有站點的複製 - 下載路徑 - NGINX 伺服器 URL - 顯示有配音/字幕的動漫 - 適應螢幕 - 拉伸 - 縮放 - 免責聲明 - legal_notice_key - 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. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. - 通用 - 隨機按鈕 - 在主頁中顯示隨機按鈕 - 片源語言 - 應用佈局 - 偏好類型 - 在支援的片源中啟用 NSFW 內容 - 字幕編碼 - 片源 - 佈局 - 自動 - 電視佈局 - 手機佈局 - 模擬器佈局 - 主題色 - 應用程式主題 - 封面標題位置 - 將標題移到封面下方 - - anilist_key - mal_key - opensubtitles_key - nginx_key - 密碼 - 用戶名 - 電子郵件 - IP - 網站名稱 - 網站連結 - 語言代號 (zh_TW) - - %s %s - 帳號 - 登出 - 登入 - 切換帳號 - 添加帳號 - 創建帳號 - 添加同步 - 已添加 %s - 同步 - 評分 - %d / 10 - /\?\? - /%d - %s 已驗證 - 無法在 %s 登入 - - - 普通 - 全部 - 最大 - 最小 - @string/none - 輪廓 - 凹陷 - 陰影 - 凸出 - 同步字幕 - 1000 毫秒 - 字幕延遲 - 如果字幕過早顯示 %d 毫秒,請使用此選項 - 如果字幕過晚顯示 %d 毫秒,請使用此選項 - 無字幕延遲 - - The quick brown fox jumps over the lazy dog - 推薦 - 已載入 %s - 從檔案載入 - 從網路載入 - 下載的檔案 - 主角 - 配角 - 群演 - 來源 - 隨機 - 即將到來… - Cam - Cam - Cam - HQ - HD - TS - TC - Blu-ray - WP - DVD - 4K - SD - UHD - HDR - SDR - Web - 封面圖片 - 播放器 - 解析度與標題 - 標題 - 解析度 - 無效 ID - 無效資料 - 無效 URL - 錯誤 - 移除隱藏式字幕 - 移除字幕廣告 - 按偏好片源語言過濾 - 附加 - 預告片 - 播放連結 - 推薦 - 下一個 - 觀看這些語言的影片 - 上一個 - 跳過設定 - 更改應用程式的外觀以適應你的設備 - 崩潰報告 - 你想要看什麼 - 完成 - 擴充功能 - 添加資源庫 - 資源庫名稱 - 資源庫 URL - 外掛程式已載入 - 外掛程式已刪除 - 無法載入 %s - 18+ - 開始下載 %d %s … - 已下載 %d %s - 全部 %s 已經下載 - 批次下載 - 外掛程式 - 外掛程式 - 這也將刪除所有資源庫外掛程式 - 刪除資源庫 - 下載你所需的片源 - 已下載:%d - 已禁用:%d - 未下載:%d - 已更新 %d 外掛程式 - CloudStream 預設沒有安裝任何片源。您需要從資源庫安裝站點。 -\n -\n由於 Sky UK Limited 的無腦 DMCA 刪除🤮,我們無法在應用程式中連結資源庫站點。 -\n -\n加入我們的 Discord 或自己在網路上搜尋。 - 查看 - 公開列表 - 字幕全大寫 - 從此資源庫下載所有外掛程式? - %s (禁用) - 軌道 - 音頻軌道 - 影片軌道 - 重新啟動時生效 - 安全模式已啟用 - 由於崩潰,所有外掛程式皆已關閉,以幫助您找到導致問題的程式。 - 查看崩潰資訊 - 評分:%s - 簡介 - 版本 - 狀態 - 大小 - 作者 - 類型 - 語言 - 請先安裝外掛程式 - HLS 播放清單 - 偏好影片播放器 - 內部播放器 - VLC - MPV - 網路影片播放 - 網頁瀏覽器 - 未找到應用 - 所有語言 - 跳過 %s - 片頭 - 片尾 - 前情回顧 - 混合片尾 - 混合片頭 - 致謝名單 - 介紹 - 清除歷史紀錄 - 歷史紀錄 - 自動下載外掛程式 - 你確定要離開? - 從添加的資源庫自動安裝所有尚未安裝的外掛程式。 - 在開始/結束顯示跳過彈出窗口 - 無法安裝新版本的應用程式 - APK 安裝器 - 有些手機不支援新的軟體包安裝程式。 如果未安裝更新,請嘗試使用舊版選項。 - 文字太多。 無法儲存到剪貼簿。 - - 正在下載應用程式更新… - 正在安裝應用程式更新… - 套件安裝程式 - 標記為已觀看 - 否』 - 傳統』 - 播放器功能 - 字幕 - 播放預告片 - 重設設定過程 - 連結 - 應用程式更新 - 備份 - 擴充功能 - 動作 - 快取 - 手勢 - 版面 - 預設 - 外觀 - 功能 - 瀏覽器 - diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 44b93430..8f436fe9 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -1,4 +1,3 @@ - @@ -18,9 +17,10 @@ %dd %dh %dm %dh %dm %dm + 封面 - 封面 + @string/result_poster_img_des 剧集封面 主封面 随机下一个 @@ -29,21 +29,24 @@ @string/home_change_provider_img_des 更改片源 预览背景 + 速度(%.2fx) 评分:%.1f - 发现新版本! -\n%s -> %s + 发现新版本!\n%s -> %s 填充 %d 分钟 + CloudStream 使用 CloudStream 播放 主页 搜索 下载 设置 + 搜索… 搜索 %s… + 无数据 更多选项 下一集 @@ -53,6 +56,7 @@ 在浏览器中打开 跳过加载 加载中… + 正在观看 暂时搁置 观看完毕 @@ -60,6 +64,7 @@ 计划观看 重新观看 + 播放电影 播放直播 播放种子 @@ -69,6 +74,7 @@ 返回 播放剧集 + 下载 已下载 下载中 @@ -79,14 +85,18 @@ 下载完毕 %s - %s 播放 + 加载链接错误 内部存储 + 配音 字幕 + 删除文件 播放文件 继续下载 暂停下载 + 禁用自动错误报告 更多信息 隐藏 @@ -102,7 +112,9 @@ 关闭 清除 保存 + 播放速度 + 字幕设置 文本颜色 轮廓颜色 @@ -112,61 +124,80 @@ 字幕高度 字体 字体大小 + 按片源搜索 按类型搜索 + 给开发者送 %d 根香蕉 不送香蕉 + 自动选择语言 下载语言 字幕语言 按住重置为默认值 将字体导入到 %s 继续观看 + 移除 更多信息 - @string/home_play + @string/home_play + 此片源可能需要 VPN 才能正常使用 - 此片源为种子文件,建议使用 VPN + 此片源是种子,建议使用 VPN + 站点不提供元数据,如果站点上不存在元数据,视频加载将失败。 + 简介 未找到简介 未找到简介 - 显示 Logcat 🐈 + + 显示日志 🐈 + 画中画 - 在其他应用上层的迷你播放器中继续播放 + 在其他应用之上的迷你播放器中继续播放 播放器画面调整按钮 移除黑色边框 字幕 播放器字幕设置 - Chromecast 字幕 - Chromecast 字幕设置 + 投屏字幕 + 投屏字幕设置 + 倍速模式 在播放器中添加播放速度选项 滑动控制进度 - 左右滑动以控制视频中的位置 + 左右滑动控制播放进度 滑动更改设置 上下滑动修改亮度或音量 + 自动播放下一集 播放完毕后播放下一集 + 双击控制进度 双击暂停 - 双击控制进度时间 (秒) - 在左右侧双击快进或快退 + 双击控制进度时间 + 在左右侧双击快进或快退 + 双击中间暂停 使用系统亮度 - 在应用播放器中使用系统亮度替代黑色遮罩 + 在应用播放器中使用系统亮度替代黑色遮罩 + + 更新观看进度 自动同步当前剧集进度 + 从备份中恢复数据 + 备份数据 已加载备份文件 无法从 %s 文件中还原数据 - 数据已储存 - 缺少存储权限,请重试。 + 成功保存数据 + 缺少存储权限,请重试 备份 %s 出错 + 搜索 账户 更新与备份 + 信息 高级搜索 按片源分割搜索结果 @@ -176,10 +207,10 @@ 显示预告片 显示来自 Kitsu 的封面 在搜索结果中隐藏选中视频画质 + 自动更新插件 - 自动下载插件 显示应用更新 - 启动应用后自动搜索更新。 + 启动时自动搜索更新 更新至预览版 搜索预览版更新替代仅搜索完整版本 Github @@ -188,13 +219,17 @@ 加入 Discord 给开发者送一根香蕉 送香蕉 + 应用语言 - 此片源不支持 Chromecast + + 此片源不支持投屏 未找到链接 - 链接已复制到剪贴板 + 连接已复制到剪贴板 播放剧集 重置为默认值 - 抱歉,应用崩溃了,将发送一份匿名错误报告给开发者 + 抱歉,应用崩溃了,将发送一份匿名错误报告给开发者 + + %s %d%s 无季 @@ -205,6 +240,7 @@ S E 未找到剧集 + 删除文件 删除 @string/sort_cancel @@ -212,10 +248,10 @@ 继续 -30 +30 - 这将永久删除 %s -\n您确定吗\? - %d 分钟 -\n剩余 + 这将永久删除 %s\n您确定吗? + 剩余 %d 分钟 + + 连载中 已完结 状态 @@ -224,12 +260,15 @@ 时间 网站 简介 + 已加入队列 无字幕 默认 + 空闲 已使用 应用 + 电影 电视剧 @@ -242,25 +281,28 @@ 直播 NSFW 其他 + 电影 电视剧 卡通 - 动漫 - OVA + @string/anime + @string/ova 种子 纪录片 亚洲剧 直播 NSFW 其他 + 来源错误 远程错误 渲染器错误 意外的播放器错误 下载错误,请检查存储权限 - Chromecast 剧集 - Chromecast 镜像 + + 投屏剧集 + 投屏镜像 在应用中播放 在 %s 中播放 在浏览器中播放 @@ -269,6 +311,7 @@ 下载镜像 重新加载链接 下载字幕 + 画质标签 配音标签 字幕标签 @@ -278,41 +321,68 @@ show_sub_key show_title_key 封面内容 + 未找到更新 检查更新 + 锁定 调整画面 来源 跳过片头 + 不再显示 跳过此更新 更新 - 首选播放画质(WiFi) + 首选播放画质 视频播放器标题最多字符 视频播放器标题 + 视频缓冲大小 视频缓冲长度 视频缓存存储 清除视频与图像缓存 - 如果在内存较少的设备(例如 Android TV)上设置得太高会导致崩溃。 - 如果在存储空间较小的设备(例如 Android TV)上设置过高会导致问题。 + + 设置过高可能导致崩溃。如果您使用如 Android TV 或旧手机此类低内存设备,请勿修改 + 设置过高可能导致如 Android TV 此类低存储空间设备出现系统问题 + DNS over HTTPS - 用于绕过 ISP 的封锁 + 防止 DNS 劫持 + 复制片源 移除片源 添加不同链接的已有片源复制 + 下载路径 - NGINX 服务器链接 - 显示带配音/字幕的动漫 + + Nginx 服务器链接 + + 显示有配音/字幕的动漫 + 适应 拉伸 缩放 + 免责声明 legal_notice_key - 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. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. + 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. + + In case of copyright infringement, please directly contact the responsible parties or the streaming websites. + + The app is purely for educational and personal use. + + CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. + CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or + manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, + user-friendly interface. + + It merely scrapes 3rd-party websites that are publicly accessable 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 3 at your own risk. + 通用 随机按钮 - 在主页上显示按钮,可以从主页上随机选择电影或电视剧 + 在主页中显示随机按钮 片源语言 应用布局 首选类型 @@ -320,14 +390,18 @@ 字幕编码 片源 布局 + 自动 电视布局 手机布局 模拟器布局 + 主题色 应用主题 封面标题位置 将标题移至封面下方 + + anilist_key mal_key @@ -340,6 +414,7 @@ 网站名称 网站链接 语言代码 (zh) + 普通 @@ -376,11 +452,12 @@ 阴影 凸出 同步字幕 - 1000 ms + 1000ms 字幕延迟 - 如果字幕过早显示 %d ms,请使用此选项 - 如果字幕过晚显示 %d ms,请使用此选项 + 如果字幕过早显示 %dms,请使用此选项 + 如果字幕过晚显示 %dms,请使用此选项 无字幕延迟 + The quick brown fox jumps over the lazy dog + 推荐 已加载 %s 从文件加载 @@ -396,9 +474,12 @@ 主演 配演 群演 + 来源 随机 + 即将到来… + Cam Cam Cam @@ -406,7 +487,7 @@ HD TS TC - Blu-ray + BlueRay WP DVD 4K @@ -415,6 +496,7 @@ HDR SDR Web + 封面图像 播放器 分辨率与标题 @@ -445,10 +527,10 @@ 仓库链接 插件已加载 插件已删除 - 无法加载 %s + 加载 %s 失败 18+ - 开始下载 %d %s… - 已下载 %d %s + 开始下载 %d %s + 下载 %d %s 成功 全部 %s 已经下载 批量下载 插件 @@ -460,23 +542,22 @@ 已禁用:%d 未下载:%d 已更新 %d 插件 - CloudStream 默认不安装片源。您需要从仓库中安装片源。 -\n -\n由于 Sky UK Limited 的无脑 DMCA 下架 🤮 我们不能在应用内提供仓库链接。 -\n -\n加入我们的 Discord 或在网上搜索。 + CloudStream 默认不安装片源。您需要从仓库中安装片源。\n\nBecause of a brainless DMCA takedown by Sky Uk Limited 🤮 we cannot link the repository sites in app.\n\n加入我们的 Discord 获得链接或在网上搜索。 查看社区仓库 公开列表 字幕全大写 + 从此仓库下载所有插件? %s(禁用) 轨道 音频轨道 视频轨道 重启生效 + 安全模式已启用 - 由于崩溃,所有扩展都已关闭,以帮助您找到导致问题的扩展。 + 发生了不可恢复的崩溃,已自动禁用所有扩展,因此可以找到并删除导致问题的扩展。 查看崩溃信息 + 评分:%s 简介 版本 @@ -486,7 +567,9 @@ 类型 语言 请先安装扩展 + HLS 播放列表 + 首选视频播放器 内部播放器 VLC @@ -494,88 +577,4 @@ 投屏 浏览器 未找到应用 - 所有语言 - 跳过 %s - 片头 - 片尾 - 前情回顾 - 混合片尾 - 混合片头 - 致谢名单 - 介绍 - 清除历史记录 - 历史记录 - 显示跳过片头/片尾弹窗 - 文本过多,无法保存到剪贴板。 - 标记为已观看 - 您确定要离开吗? - - - 正在下载应用更新… - 正在安装应用更新… - 无法安装新版的应用 - 自动安装已添加的仓库内所有未安装的插件。 - APK 安装器 - 部分手机不支持新的软件包安装程序。如果未安装更新,请尝试使用传统选项。 - 传统 - 软件包安装程序 - 播放预告片 - 功能 - 默认 - 重置设置向导 - 布局 - 链接 - 扩展 - 字幕 - 应用更新 - 备份 - 缓存 - 手势 - 播放器功能 - 行为 - 外观 - 更新开始 - 应用退出后将会更新 - 插件已下载 - 从已观看中移除 - 发现安全模式文件! -\n启动时不加载任何扩展,直到文件被删除。 - 浏览器 - - 排序方式 - 排序 - 评分(从高到低) - 评分(从低到高) - 更新(从新到旧) - 更新(从旧到新) - 字母排序(从 A 到 Z) - 字母排序(从 Z 到 A) - 选择库 - 打开方式 - 看来您的库是空的 :( -\n登录库账户或添加节目到您的本地库 - 看来此列表是空的,请尝试切换到另一个 - 播放器显示 - 快进快退秒数 - 播放器可见时使用的快进快退秒数 - 播放器隐藏 - 快进快退秒数 - 播放器隐藏时使用的快进快退秒数 - Android TV - 失败 - 片源测试 - 重启 - 停止 - 正在更新订阅节目 - 已订阅 - 已订阅 %s - 已取消订阅 %s - 开始 - 第 %d 集已发布! - 成功 - 日志 - raw.githubusercontent.com 代理 - 连接 Github 失败,正在启用 jsdelivr 代理。 - 使用 jsdelivr,可以绕过 GitHub 的封锁。可能会延迟几天的更新。 - ISP 绕过 - 还原 - 首选播放画质(移动数据) diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index 6aff7a59..3554beb2 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -33,16 +33,6 @@ 6 - - @string/apk_installer_package_installer - @string/apk_installer_legacy - - - 0 - 1 - - - @string/player_settings_play_in_app @string/player_settings_play_in_vlc diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 95094311..fc718008 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -1,7 +1,7 @@ - + @@ -13,10 +13,6 @@ ?attr/colorPrimary - - - - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 7dd4c989..c3e51ab5 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -13,7 +13,6 @@ #161616 #e9eaee - #1AFFFFFF #9ba0a4 #DCDCDC @@ -26,18 +25,14 @@ #FFF #000 - #1AFFFFFF - - #121950 + #3d50fa #121213 #3B65F5 - #571711 + #F54A3B #F53B66 - #BEC8FF + #3BF585 ?attr/colorPrimaryDark - #4C3115 - #FFA662 #FF6F63 @@ -82,7 +77,4 @@ #515151 #FFFFFF #622C00 - - #48E484 - #ea596e \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 567b6d35..e748868b 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -16,5 +16,5 @@ 2000 3dp - 62dp + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 911c0d07..144d2477 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,26 +1,22 @@ - - + + search_providers_list app_locale search_type_list auto_update auto_update_plugins - auto_download_plugins_key skip_update_key prerelease_update manual_check_update fast_forward_button_time benene_count subtitle_settings_key - test_providers_key subtitle_settings_chromecast_key quality_pref_key - quality_pref_mobile_data_key player_pref_key prefer_limit_title_key prefer_limit_title_rez_key - apk_installer_key video_buffer_size_key video_buffer_length_key video_buffer_clear_key @@ -32,9 +28,7 @@ pip_enabled_key double_tap_enabled_key double_tap_pause_enabled_key - double_tap_seek_time_key2 - android_tv_interface_off_seek_key - android_tv_interface_on_seek_key + double_tap_seek_time_key swipe_vertical_enabled_key autoplay_next_key display_sub_key @@ -44,7 +38,6 @@ random_button_key provider_lang_key dns_key - jsdelivr_proxy_key download_path_key Cloudstream app_layout_key @@ -64,7 +57,7 @@ filter_sub_lang_key pref_filter_search_quality_key enable_nsfw_on_providers_key - enable_skip_op_from_database + %d %s | %s %s • %s @@ -82,9 +75,10 @@ %dd %dh %dm %dh %dm %dm + Poster - Poster + @string/result_poster_img_des Episode Poster Main Poster Next Random @@ -93,20 +87,24 @@ @string/home_change_provider_img_des Change Provider Preview Background + Speed (%.2fx) Rated: %.1f - New update found!\n%s -> %s + New update found!\n%s -> %s Filler %d min + CloudStream Play with CloudStream Home Search Downloads Settings + Search… Search %s… + No Data More Options Next episode @@ -114,9 +112,9 @@ Genres Share Open In Browser - Browser Skip Loading Loading… + Watching On-Hold Completed @@ -124,8 +122,8 @@ Plan to Watch None Rewatching + Play Movie - Play Trailer Play Livestream Stream Torrent Sources @@ -134,6 +132,7 @@ Go Back Play Episode + Download Downloaded Downloading @@ -143,16 +142,19 @@ Download Canceled Download Done %s - %s - Update Started Stream + Error Loading Links Internal Storage + Dub Sub + Delete File Play File Resume Download Pause Download + Disable automatic bug reporting More info Hide @@ -168,7 +170,9 @@ Close Clear Save + Player Speed + Subtitle Settings Text Color Outline Color @@ -178,27 +182,35 @@ Subtitle Elevation Font Font Size + Search using providers Search using types + %d Benenes given to devs No Benenes given + Auto-Select Language Download Languages Subtitle Language Hold to reset to default Import fonts by placing them in %s Continue Watching + Remove More Info @string/home_play + A VPN might be needed for this provider to work correctly This provider is a torrent, a VPN is recommended + Metadata is not provided by site, video loading will fail if it does not exist on site. + Description No Plot Found No Description Found - Show Logcat 🐈 - Log + + Show logcat 🐈 + Picture-in-picture Continues playback in a miniature player on top of other apps Player resize button @@ -207,37 +219,44 @@ Player subtitles settings Chromecast Subtitles Chromecast subtitles settings + Eigengravy Mode Adds a speed option in the player Swipe to seek - Swipe from side to side to control your position in a video + Swipe left or right to control time in the videoplayer Swipe to change settings - Slide up or down on the left or right side to change brightness or volume + Swipe on the left or right side to change brightness or volume + Autoplay next episode Start the next episode when the current one ends + Double tap to seek Double tap to pause - Player seek amount (Seconds) + Player seek amount Tap twice on the right or left side to seek forwards or backwards - Tap twice in the middle to pause + Tap in the middle to pause Use system brightness Use system brightness in the app player instead of a dark overlay + Update watch progress Automatically sync your current episode progress + Restore data from backup - Back up data + + Backup data Loaded backup file Failed to restore data from file %s - Data stored - Storage permissions missing. Please try again. + Successfully stored data + Storage permissions missing, please try again Error backing up %s + Search - Library Accounts Updates and backup + Info Advanced Search Gives you the search results separated by provider @@ -245,25 +264,23 @@ Sends no data Show filler episode for anime Show trailers - Show posters from Kitsu - Hide selected video quality in search results + Show posters from kitsu + Hide selected video quality on Search results + Automatic plugin updates - Automatically download plugins - Automatically install all not yet installed plugins from added repositories. Show app updates - Automatically search for new updates after starting the app. - Redo setup process + Automatically search for new updates on start Update to prereleases Search for prerelease updates instead of full releases only - APK Installer - Some phones do not support the new package installer. Try the legacy option if updates do not install. Github Light novel app by the same devs Anime app by the same devs Join Discord Give a benene to the devs Given benene + App Language + This provider has no Chromecast support No Links Found Link copied to clipboard @@ -272,6 +289,7 @@ Sorry, the application crashed. An anonymous bug report will be sent to the developers + Season %s %d%s No Season @@ -282,18 +300,18 @@ S E No Episodes found + Delete File Delete @string/sort_cancel Pause - Start - Failed - Passed Resume -30 +30 This will permanently delete %s\nAre you sure? %dm\nremaining + + Ongoing Completed Status @@ -302,12 +320,15 @@ Duration Site Synopsis + queued No Subtitles Default + Free Used App + Movies TV Series @@ -320,23 +341,26 @@ Livestreams NSFW Others + Movie Series Cartoon - Anime - OVA + @string/anime + @string/ova Torrent Documentary Asian Drama Livestream NSFW Video + Source error Remote error Renderer error Unexpected player error Download error, check storage permissions + Chromecast episode Chromecast mirror Play in app @@ -347,6 +371,7 @@ Download mirror Reload links Download subtitles + Quality label Dub label Sub label @@ -356,43 +381,47 @@ show_sub_key show_title_key Toggle UI elements on poster + No Update Found Check for Update + Lock Resize Source Skip OP + Don\'t show again Skip this Update Update - Preferred watch quality (WiFi) - Preferred watch quality (Mobile Data) + Preferred watch quality Video player title max chars Video player resolution + Video buffer size Video buffer length Video cache on disk Clear video and image cache - Player Shown - Seek Amount - The seek amount used when the player is visible - Player Hidden - Seek Amount - The seek amount used when the player is hidden - Causes crashes if set too high on devices with low memory, such as Android TV. - Causes problems if set too high on devices with low storage space, such as Android TV. + + Will cause random crashes if set too high. Don\'t change if you have a low amount of ram such as an Android TV or old phone + May cause problems on systems with low storage space such as Android TV devices if you set it too high + DNS over HTTPS Useful for bypassing ISP blocks - raw.githubusercontent.com Proxy - Failed to reach GitHub, enabling jsdelivr proxy. - Using jsdelivr, GitHub blocking can be bypassed. May delay updates by a few days. + Clone site Remove site - Add a clone of an existing site, with a different URL + Add a clone of an existing site, with a different url + Download path - NGINX server URL + + Nginx server url + Display Dubbed/Subbed Anime + Fit to screen Stretch Zoom + Disclaimer legal_notice_key Any legal issues regarding the content on this application @@ -411,40 +440,28 @@ responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. - ISP Bypasses - Links - App updates - Backup - Extensions - Actions - Cache - Android TV - Gestures - Player features - Subtitles - Layout - Defaults - Looks - Features General Random Button - Shows button on Homepage which can choose a random movie or TV series from the Homepage + Show random button on Homepage Provider languages App Layout Preferred media Enable NSFW on supported providers Subtitle encoding Providers - Provider test Layout + Auto TV layout Phone layout Emulator layout + Primary color App theme Poster title location Put the title under the poster + + anilist_key mal_key @@ -457,6 +474,7 @@ MyCoolSite example.com Language code (en) + %s %s account - Log out - Log in + Logout + Login Switch account Add account Create account @@ -479,8 +497,9 @@ %d / 10 /?? /%d - %s authenticated - Could not log in at %s + Authenticated %s + Failed to authenticate to %s + None Normal @@ -493,11 +512,12 @@ Shadow Raised Sync subs - 1000 ms + 1000ms Subtitle delay - Use this if the subtitles are shown %d ms too early - Use this if subtitles are shown %d ms too late + Use this if the subtitles are shown %dms too early + Use this if subtitles are shown %dms too late No subtitle delay + The quick brown fox jumps over the lazy dog + Recommended Loaded %s Load from file @@ -513,9 +534,12 @@ Main Supporting Background + Source Random + Coming soon… + Cam Cam Cam @@ -523,7 +547,7 @@ HD TS TC - Blu-ray + BlueRay WP DVD 4K @@ -532,14 +556,15 @@ HDR SDR Web + Poster Image Player Resolution and title Title Resolution - Invalid ID + Invalid id Invalid data - Invalid URL + Invalid url Error Remove closed captions from subtitles Remove bloat from subtitles @@ -559,14 +584,13 @@ Extensions Add repository Repository name - Repository URL + Repository url Plugin Loaded - Plugin Downloaded Plugin Deleted - Could not load %s + Failed to load %s 18+ - Started downloading %d %s… - Downloaded %d %s + Started downloading %d %s + Downloaded %d %s successfully All %s already downloaded Batch download plugin @@ -578,25 +602,22 @@ Disabled: %d Not downloaded: %d Updated %d plugins - CloudStream has no sites installed by default. You need to install the sites from repositories. -\n -\nBecause of a brainless DMCA takedown by Sky UK Limited 🤮 we cannot link the repository site in app. -\n -\nJoin our Discord or search online. + CloudStream has no sites installed by default. You need to install the sites from repositories.\n\nBecause of a brainless DMCA takedown by Sky Uk Limited 🤮 we cannot link the repository sites in app.\n\nJoin our discord for links or search online. View community repositories Public list Uppercase all subtitles + Download all plugins from this repository? %s (Disabled) Tracks Audio tracks Video tracks Apply on Restart - Restart - Stop - Safe mode on - All extensions were turned off due to a crash to help you find the one causing trouble. + + Safe Mode enabled + An unrecoverable crash occurred and we\'ve automatically disabled all extensions, so you can find and remove the extension which is causing trouble. View crash info + Rating: %s Description Version @@ -606,15 +627,18 @@ Supported Language Install the extension first + HLS Playlist + Preferred video player Internal player VLC MPV Web Video Cast - Web browser + Browser App not found All Languages + Skip %s Opening Ending @@ -623,38 +647,4 @@ Mixed opening Credits Intro - Clear history - History - Show skip popups for opening/ending - Too much text. Unable to save to clipboard. - Mark as watched - Remove from watched - Are you sure you want to exit\? - Yes - No - Downloading app update… - Installing app update… - Could not install the new version of the app - Legacy - PackageInstaller - App will be updated upon exit - Sort by - Sort - Rating (High to Low) - Rating (Low to High) - Updated (New to Old) - Updated (Old to New) - Alphabetical (A to Z) - Alphabetical (Z to A) - Select Library - Open with - Looks like your library is empty :(\nLogin to a library account or add shows to your local library - Looks like this list is empty, try switching to another one - Safe mode file found!\nNot loading any extensions on startup until file is removed. - Revert - Updating subscribed shows - Subscribed - Subscribed to %s - Unsubscribed from %s - Episode %d released! - \ No newline at end of file + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index b9648162..9c2f62fc 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -40,6 +40,7 @@ ?attr/textColor ?attr/grayTextColor + ?attr/grayTextColor ?attr/grayTextColor ?attr/textColor @@ -69,8 +70,6 @@ - - - - - - - - - - - - - - @@ -483,7 +437,7 @@ 20dp 20dp true - @drawable/outline_drawable_less + @color/transparent ?attr/textColor ?attr/selectableItemBackgroundBorderless ?android:attr/textAppearanceListItemSmall @@ -507,10 +461,10 @@ wrap_content wrap_content 5dp - 7dp - 7dp - 3dp - 3dp + 10dp + 10dp + 5dp + 5dp 12sp @color/textColor @color/textColor diff --git a/app/src/main/res/xml/settings_player.xml b/app/src/main/res/xml/settings_player.xml index 2d2905ea..6946a5c9 100644 --- a/app/src/main/res/xml/settings_player.xml +++ b/app/src/main/res/xml/settings_player.xml @@ -1,183 +1,126 @@ - - - - - - - - - - - - - - - - - - + - + - - - + + + + + + + + - + - + - - - - - - + - + + android:summary="@string/swipe_to_change_settings_des" + app:defaultValue="true" /> + - + + android:summary="@string/double_tap_to_pause_settings_des" + app:defaultValue="false" /> - - + android:title="@string/double_tap_to_seek_amount_settings" + app:defaultValue="false" /> - - + + + android:icon="@drawable/ic_baseline_storage_24" /> - + android:icon="@drawable/ic_baseline_storage_24" /> - + android:icon="@drawable/ic_baseline_storage_24" /> - - - - - - + android:title="@string/video_buffer_clear_settings" + android:icon="@drawable/ic_baseline_delete_outline_24" /> \ No newline at end of file diff --git a/app/src/main/res/xml/settings_providers.xml b/app/src/main/res/xml/settings_providers.xml index 1ee58faf..a177865b 100644 --- a/app/src/main/res/xml/settings_providers.xml +++ b/app/src/main/res/xml/settings_providers.xml @@ -1,30 +1,24 @@ + xmlns:app="http://schemas.android.com/apk/res-auto"> + android:icon="@drawable/ic_baseline_language_24" + android:key="@string/provider_lang_key" + android:title="@string/provider_lang_settings" /> + android:key="@string/prefer_media_type_key" + android:title="@string/preferred_media_settings" + android:icon="@drawable/ic_baseline_play_arrow_24" /> + android:key="@string/display_sub_key" + android:title="@string/display_subbed_dubbed_settings" + android:icon="@drawable/ic_outline_voice_over_off_24" /> - - - + android:key="@string/enable_nsfw_on_providers_key" + android:title="@string/enable_nsfw_on_providers" + android:icon="@drawable/ic_baseline_extension_24" + android:summary="@string/apply_on_restart" + app:defaultValue="false"/> \ No newline at end of file diff --git a/app/src/main/res/xml/settings_updates.xml b/app/src/main/res/xml/settings_updates.xml index f2ec6747..eaceb785 100644 --- a/app/src/main/res/xml/settings_updates.xml +++ b/app/src/main/res/xml/settings_updates.xml @@ -1,80 +1,52 @@ + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/settins_general.xml b/app/src/main/res/xml/settins_general.xml index c4900bca..ae87c611 100644 --- a/app/src/main/res/xml/settins_general.xml +++ b/app/src/main/res/xml/settins_general.xml @@ -6,6 +6,18 @@ android:title="@string/app_language" android:icon="@drawable/ic_baseline_language_24" /> + + + + + android:title="@string/github" + android:icon="@drawable/ic_github_logo" + app:summary="https://github.com/recloudstream/cloudstream"> + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/settins_ui.xml b/app/src/main/res/xml/settins_ui.xml index 0d9115a1..071df918 100644 --- a/app/src/main/res/xml/settins_ui.xml +++ b/app/src/main/res/xml/settins_ui.xml @@ -1,65 +1,57 @@ - - - - - - + + + + + - - - - - - - - - - - + + + + + \ No newline at end of file diff --git a/gradlew b/gradlew old mode 100755 new mode 100644