diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 7fb5cad2..49864e65 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -41,6 +41,7 @@ import com.lagradost.cloudstream3.CommonActivity.updateLocale import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.network.initClient import com.lagradost.cloudstream3.plugins.PluginManager +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 @@ -78,6 +79,7 @@ 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.* @@ -130,6 +132,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { companion object { const val TAG = "MAINACT" val afterPluginsLoadedEvent = Event() + val mainPluginsLoadedEvent = + Event() // homepage api, used to speed up time to load for homepage val afterRepositoryLoadedEvent = Event() } @@ -436,6 +440,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { changeStatusBarState(isEmulatorSettings()) ioSafe { + getKey(USER_SELECTED_HOMEPAGE_API)?.let { homeApi -> + mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi)) + } ?: run { + mainPluginsLoadedEvent.invoke(false) + } + if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) { PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity) } else { diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt b/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt new file mode 100644 index 00000000..05836269 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt @@ -0,0 +1,102 @@ +package com.lagradost.cloudstream3.network + +import android.util.Log +import android.webkit.CookieManager +import androidx.annotation.AnyThread +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.mvvm.debugWarning +import com.lagradost.nicehttp.Requests.Companion.await +import com.lagradost.nicehttp.cookies +import kotlinx.coroutines.runBlocking +import okhttp3.* + + +@AnyThread +class CloudflareKiller : Interceptor { + companion object { + const val TAG = "CloudflareKiller" + fun parseCookieMap(cookie: String): Map { + return cookie.split(";").associate { + val split = it.split("=") + (split.getOrNull(0)?.trim() ?: "") to (split.getOrNull(1)?.trim() ?: "") + }.filter { it.key.isNotBlank() && it.value.isNotBlank() } + } + } + + val savedCookies: MutableMap> = mutableMapOf() + + override fun intercept(chain: Interceptor.Chain): Response = runBlocking { + val request = chain.request() + val cookies = savedCookies[request.url.host] + + if (cookies == null) { + bypassCloudflare(request)?.let { + Log.d(TAG, "Succeeded bypassing cloudflare: ${request.url}") + return@runBlocking it + } + } else { + return@runBlocking proceed(request, cookies) + } + + debugWarning({ true }) { "Failed cloudflare at: ${request.url}" } + return@runBlocking chain.proceed(request) + } + + private fun getWebViewCookie(url: String): String? { + return CookieManager.getInstance()?.getCookie(url) + } + + /** + * Returns true if the cf cookies were successfully fetched from the CookieManager + * Also saves the cookies. + * */ + private fun trySolveWithSavedCookies(request: Request): Boolean { + // Not sure if this takes expiration into account + return getWebViewCookie(request.url.toString())?.let { cookie -> + cookie.contains("cf_clearance").also { solved -> + if (solved) savedCookies[request.url.host] = parseCookieMap(cookie) + } + } ?: false + } + + private suspend fun proceed(request: Request, cookies: Map): Response { + val userAgentMap = WebViewResolver.getWebViewUserAgent()?.let { + mapOf("user-agent" to it) + } ?: emptyMap() + + val headers = + getHeaders(request.headers.toMap() + userAgentMap, cookies + request.cookies) + return app.baseClient.newCall( + request.newBuilder() + .headers(headers) + .build() + ).await() + } + + private suspend fun bypassCloudflare(request: Request): Response? { + val url = request.url.toString() + + // If no cookies then try to get them + // Remove this if statement if cookies expire + if (!trySolveWithSavedCookies(request)) { + Log.d(TAG, "Loading webview to solve cloudflare for ${request.url}") + WebViewResolver( + // Never exit based on url + Regex(".^"), + // Cloudflare needs default user agent + userAgent = null, + // Cannot use okhttp (i think intercepting cookies fails which causes the issues) + useOkhttp = false, + // Match every url for the requestCallBack + additionalUrls = listOf(Regex(".")) + ).resolveUsingWebView( + url + ) { + trySolveWithSavedCookies(request) + } + } + + val cookies = savedCookies[request.url.host] ?: return null + return proceed(request, cookies) + } +} \ No newline at end of file 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 d5271e10..dca3ee00 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/DdosGuardKiller.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/DdosGuardKiller.kt @@ -1,13 +1,10 @@ package com.lagradost.cloudstream3.network import androidx.annotation.AnyThread -import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.app import com.lagradost.nicehttp.Requests.Companion.await -import com.lagradost.nicehttp.getCookies +import com.lagradost.nicehttp.cookies import kotlinx.coroutines.runBlocking -import okhttp3.Headers -import okhttp3.Headers.Companion.toHeaders import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response 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 c961a117..85e9d318 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt @@ -43,10 +43,10 @@ fun Requests.initClient(context: Context): OkHttpClient { return baseClient } -val Request.cookies: Map - get() { - return this.headers.getCookies("Cookie") - } +//val Request.cookies: Map +// get() { +// return this.headers.getCookies("Cookie") +// } private val DEFAULT_HEADERS = mapOf("user-agent" to USER_AGENT) diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt b/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt index 7bd66ce5..538b96f8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt @@ -4,10 +4,12 @@ import android.annotation.SuppressLint import android.net.http.SslError import android.webkit.* import com.lagradost.cloudstream3.AcraApplication +import com.lagradost.cloudstream3.AcraApplication.Companion.context import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.Coroutines.main +import com.lagradost.cloudstream3.utils.Coroutines.mainWork import com.lagradost.nicehttp.requestCreator import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking @@ -15,16 +17,39 @@ import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response import java.net.URI -import java.util.concurrent.TimeUnit /** * When used as Interceptor additionalUrls cannot be returned, use WebViewResolver(...).resolveUsingWebView(...) * @param interceptUrl will stop the WebView when reaching this url. * @param additionalUrls this will make resolveUsingWebView also return all other requests matching the list of Regex. + * @param userAgent if null then will use the default user agent + * @param useOkhttp will try to use the okhttp client as much as possible, but this might cause some requests to fail. Disable for cloudflare. * */ -class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List = emptyList()) : +class WebViewResolver( + val interceptUrl: Regex, + val additionalUrls: List = emptyList(), + val userAgent: String? = USER_AGENT, + val useOkhttp: Boolean = true +) : Interceptor { + companion object { + var webViewUserAgent: String? = null + + @JvmName("getWebViewUserAgent1") + fun getWebViewUserAgent(): String? { + return webViewUserAgent ?: context?.let { ctx -> + runBlocking { + mainWork { + WebView(ctx).settings.userAgentString.also { userAgent -> + webViewUserAgent = userAgent + } + } + } + } + } + } + override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() return runBlocking { @@ -38,7 +63,7 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List = referer: String? = null, method: String = "GET", requestCallBack: (Request) -> Boolean = { false }, - ) : Pair> { + ): Pair> { return resolveUsingWebView( requestCreator(method, url, referer = referer), requestCallBack ) @@ -57,12 +82,15 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List = val headers = request.headers println("Initial web-view request: $url") var webView: WebView? = null + // Extra assurance it exits as it should. + var shouldExit = false fun destroyWebView() { main { webView?.stopLoading() webView?.destroy() webView = null + shouldExit = true println("Destroyed webview") } } @@ -72,7 +100,7 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List = main { // Useful for debugging -// WebView.setWebContentsDebuggingEnabled(true) + WebView.setWebContentsDebuggingEnabled(true) try { webView = WebView( AcraApplication.context @@ -81,9 +109,14 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List = // Bare minimum to bypass captcha settings.javaScriptEnabled = true settings.domStorageEnabled = true - settings.userAgentString = USER_AGENT + + webViewUserAgent = settings.userAgentString + // Don't set user agent, setting user agent will make cloudflare break. + if (userAgent != null) { + settings.userAgentString = userAgent + } // Blocks unnecessary images, remove if captcha fucks. - settings.blockNetworkImage = true +// settings.blockNetworkImage = true } webView?.webViewClient = object : WebViewClient() { @@ -92,11 +125,11 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List = request: WebResourceRequest ): WebResourceResponse? = runBlocking { val webViewUrl = request.url.toString() -// println("Loading WebView URL: $webViewUrl") + println("Loading WebView URL: $webViewUrl") if (interceptUrl.containsMatchIn(webViewUrl)) { fixedRequest = request.toRequest().also { - if (requestCallBack(it)) destroyWebView() + requestCallBack(it) } println("Web-view request finished: $webViewUrl") destroyWebView() @@ -158,22 +191,22 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List = null, null ) - - webViewUrl.contains("recaptcha") -> super.shouldInterceptRequest( + webViewUrl.contains("recaptcha") || webViewUrl.contains("/cdn-cgi/") -> super.shouldInterceptRequest( view, request ) - request.method == "GET" -> app.get( + useOkhttp && request.method == "GET" -> app.get( webViewUrl, headers = request.requestHeaders ).okhttpResponse.toWebResourceResponse() - request.method == "POST" -> app.post( + useOkhttp && request.method == "POST" -> app.post( webViewUrl, headers = request.requestHeaders ).okhttpResponse.toWebResourceResponse() - else -> return@runBlocking super.shouldInterceptRequest( + + else -> super.shouldInterceptRequest( view, request ) @@ -204,7 +237,7 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List = val delayTime = 100L // A bit sloppy, but couldn't find a better way - while (loop < totalTime / delayTime) { + while (loop < totalTime / delayTime && !shouldExit) { if (fixedRequest != null) return fixedRequest to extraRequestList delay(delayTime) loop += 1 @@ -212,30 +245,31 @@ class WebViewResolver(val interceptUrl: Regex, val additionalUrls: List = println("Web-view timeout after ${totalTime / 1000}s") destroyWebView() - return null to extraRequestList + return fixedRequest to extraRequestList } - fun WebResourceRequest.toRequest(): Request { - val webViewUrl = this.url.toString() +} - return requestCreator( - this.method, - webViewUrl, - this.requestHeaders, - ) - } +fun WebResourceRequest.toRequest(): Request { + val webViewUrl = this.url.toString() - fun Response.toWebResourceResponse(): WebResourceResponse { - val contentTypeValue = this.header("Content-Type") - // 1. contentType. 2. charset - val typeRegex = Regex("""(.*);(?:.*charset=(.*)(?:|;)|)""") - return if (contentTypeValue != null) { - val found = typeRegex.find(contentTypeValue) - val contentType = found?.groupValues?.getOrNull(1)?.ifBlank { null } ?: contentTypeValue - val charset = found?.groupValues?.getOrNull(2)?.ifBlank { null } - WebResourceResponse(contentType, charset, this.body?.byteStream()) - } else { - WebResourceResponse("application/octet-stream", null, this.body?.byteStream()) - } + return requestCreator( + this.method, + webViewUrl, + this.requestHeaders, + ) +} + +fun Response.toWebResourceResponse(): WebResourceResponse { + val contentTypeValue = this.header("Content-Type") + // 1. contentType. 2. charset + val typeRegex = Regex("""(.*);(?:.*charset=(.*)(?:|;)|)""") + return if (contentTypeValue != null) { + val found = typeRegex.find(contentTypeValue) + val contentType = found?.groupValues?.getOrNull(1)?.ifBlank { null } ?: contentTypeValue + val charset = found?.groupValues?.getOrNull(2)?.ifBlank { null } + WebResourceResponse(contentType, charset, this.body.byteStream()) + } else { + WebResourceResponse("application/octet-stream", null, this.body.byteStream()) } -} \ No newline at end of file +} 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 cab3b197..96a2222e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -125,7 +125,7 @@ object PluginManager { Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/plugins" // Maps filepath to plugin - private val plugins: MutableMap = + val plugins: MutableMap = LinkedHashMap() // Maps urls to plugin @@ -164,6 +164,18 @@ object PluginManager { var allCurrentOutDatedPlugins: Set = emptySet() + suspend fun loadSinglePlugin(activity: Activity, apiName: String): Boolean { + return (getPluginsOnline().firstOrNull { it.internalName == apiName } + ?: getPluginsLocal().firstOrNull { it.internalName == apiName })?.let { savedData -> + // OnlinePluginData(savedData, onlineData) + loadPlugin( + activity, + File(savedData.filePath), + savedData + ) + } ?: false + } + /** * Needs to be run before other plugin loading because plugin loading can not be overwritten * 1. Gets all online data about the downloaded plugins @@ -376,7 +388,7 @@ object PluginManager { file ?: return false, PluginData(internalName, pluginUrl, true, file.absolutePath, PLUGIN_VERSION_NOT_SET) ) - } catch (e : Exception) { + } catch (e: Exception) { logError(e) return false } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt index cfbd0950..d773b3a2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt @@ -1,16 +1,16 @@ package com.lagradost.cloudstream3.ui import android.os.Bundle -import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient +import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.USER_AGENT +import com.lagradost.cloudstream3.network.WebViewResolver import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo import com.lagradost.cloudstream3.utils.AppUtils.loadRepository import kotlinx.android.synthetic.main.fragment_webview.* @@ -48,7 +48,9 @@ class WebviewFragment : Fragment() { } web_view.settings.javaScriptEnabled = true web_view.settings.domStorageEnabled = true - web_view.settings.userAgentString = USER_AGENT + + WebViewResolver.webViewUserAgent = web_view.settings.userAgentString +// web_view.settings.userAgentString = USER_AGENT web_view.loadUrl(url) } 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 e8bda305..7ea360a8 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 @@ -37,6 +37,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadManager import kotlinx.android.synthetic.main.fragment_downloads.* import kotlinx.android.synthetic.main.stream_input.* +import android.text.format.Formatter.formatShortFileSize const val DOWNLOAD_NAVIGATE_TO = "downloadpage" @@ -44,10 +45,6 @@ const val DOWNLOAD_NAVIGATE_TO = "downloadpage" class DownloadFragment : Fragment() { private lateinit var downloadsViewModel: DownloadViewModel - private fun getBytesAsText(bytes: Long): String { - return "%.1f".format(bytes / 1000000000f) - } - private fun View.setLayoutWidth(weight: Long) { val param = LinearLayout.LayoutParams( 0, @@ -101,7 +98,7 @@ class DownloadFragment : Fragment() { download_free_txt?.text = getString(R.string.storage_size_format).format( getString(R.string.free_storage), - getBytesAsText(it) + formatShortFileSize(view.context, it) ) download_free?.setLayoutWidth(it) } @@ -109,7 +106,7 @@ class DownloadFragment : Fragment() { download_used_txt?.text = getString(R.string.storage_size_format).format( getString(R.string.used_storage), - getBytesAsText(it) + formatShortFileSize(view.context, it) ) download_used?.setLayoutWidth(it) download_storage_appbar?.isVisible = it > 0 @@ -118,7 +115,7 @@ class DownloadFragment : Fragment() { download_app_txt?.text = getString(R.string.storage_size_format).format( getString(R.string.app_storage), - getBytesAsText(it) + formatShortFileSize(view.context, it) ) download_app?.setLayoutWidth(it) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt index 5168409e..fde490e2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.ui.download import android.annotation.SuppressLint +import android.text.format.Formatter.formatShortFileSize import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -113,7 +114,7 @@ class DownloadHeaderAdapter( } title.text = d.name - val mbString = "%.1f".format(card.totalBytes / 1000000f) + val mbString = formatShortFileSize(itemView.context, card.totalBytes) //val isMovie = d.type.isMovieType() if (card.child != null) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/EasyDownloadButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/EasyDownloadButton.kt index 04a20e73..77878432 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/EasyDownloadButton.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/EasyDownloadButton.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.ui.download import android.animation.ObjectAnimator +import android.text.format.Formatter.formatShortFileSize import android.view.View import android.view.animation.DecelerateInterpolator import android.widget.ImageView @@ -171,8 +172,8 @@ class EasyDownloadButton : IDisposable { } textView?.visibility = View.VISIBLE progressBar.visibility = View.VISIBLE - val currentMbString = "%.1f".format(setCurrentBytes / 1000000f) - val totalMbString = "%.1f".format(setTotalBytes / 1000000f) + val currentMbString = formatShortFileSize(textView?.context, setCurrentBytes) + val totalMbString = formatShortFileSize(textView?.context, setTotalBytes) textView?.text = if (isTextPercentage) "%d%%".format(setCurrentBytes * 100L / setTotalBytes) else 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 20f215d2..975545ee 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 @@ -31,8 +31,8 @@ import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent +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 @@ -60,7 +60,6 @@ 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.USER_SELECTED_HOMEPAGE_API import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe @@ -70,6 +69,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount 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 com.lagradost.cloudstream3.widget.CenterZoomLayoutManager import kotlinx.android.synthetic.main.fragment_home.* import kotlinx.android.synthetic.main.fragment_home.home_api_fab @@ -437,11 +437,13 @@ class HomeFragment : Fragment() { override fun onResume() { super.onResume() reloadStored() - afterPluginsLoadedEvent += ::loadHomePage + afterPluginsLoadedEvent += ::firstLoadHomePage + mainPluginsLoadedEvent += ::firstLoadHomePage } override fun onStop() { - afterPluginsLoadedEvent -= ::loadHomePage + afterPluginsLoadedEvent -= ::firstLoadHomePage + mainPluginsLoadedEvent -= ::firstLoadHomePage super.onStop() } @@ -454,6 +456,14 @@ class HomeFragment : Fragment() { homeViewModel.loadStoredData(list) } + private var hasBeenConsumed = false + private fun firstLoadHomePage(successful: Boolean = false) { + // dirty hack to make it only load once + if(hasBeenConsumed) return + hasBeenConsumed = true + loadHomePage(successful) + } + private fun loadHomePage(successful: Boolean = false) { val apiName = context?.getKey(USER_SELECTED_HOMEPAGE_API) @@ -962,13 +972,13 @@ class HomeFragment : Fragment() { reloadStored() loadHomePage() - home_loaded.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { view, _, scrollY, _, oldScrollY -> + 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 (view?.context?.isTvSettings() == false) { + if (v.context?.isTvSettings() == false) { home_api_fab?.extend() // show home_random?.extend() } 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 b56ce338..a31b2ceb 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 @@ -27,6 +27,7 @@ import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI import com.lagradost.cloudstream3.syncproviders.OAuth2API import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe @@ -209,6 +210,7 @@ class SettingsAccount : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpToolbar(R.string.category_account) + setPaddingBottom() } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 638af264..4c532e4c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -21,6 +21,7 @@ import com.lagradost.cloudstream3.ui.home.HomeFragment import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.setImage +import com.lagradost.cloudstream3.utils.UIHelper.toPx import kotlinx.android.synthetic.main.main_settings.* import kotlinx.android.synthetic.main.standard_toolbar.* import java.io.File @@ -40,6 +41,15 @@ class SettingsFragment : Fragment() { } } + /** + * On TV you cannot properly scroll to the bottom of settings, this fixes that. + * */ + fun PreferenceFragmentCompat.setPaddingBottom() { + if (this.context?.isTvSettings() == true) { + listView?.setPadding(0, 0, 0, 100.toPx) + } + } + fun Fragment?.setUpToolbar(title: String) { if (this == null) return settings_toolbar?.apply { 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 50d581ad..389d52c3 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 @@ -25,6 +25,7 @@ import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.network.initClient import com.lagradost.cloudstream3.ui.EasterEggMonke import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog @@ -42,6 +43,7 @@ class SettingsGeneral : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpToolbar(R.string.category_general) + setPaddingBottom() } data class CustomSite( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsLang.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsLang.kt index ccfd55b3..1c77f1d8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsLang.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsLang.kt @@ -12,6 +12,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog @@ -61,6 +62,7 @@ class SettingsLang : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpToolbar(R.string.category_preferred_media_and_lang) + setPaddingBottom() } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 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 72ed9ad9..125fadec 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 @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.ui.settings import android.os.Bundle +import android.text.format.Formatter.formatShortFileSize import android.view.View import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager @@ -8,6 +9,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getFolderSize import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.subtitles.ChromecastSubtitlesFragment import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment @@ -20,6 +22,7 @@ class SettingsPlayer : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpToolbar(R.string.category_player) + setPaddingBottom() } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { hideKeyboard() @@ -164,8 +167,7 @@ class SettingsPlayer : PreferenceFragmentCompat() { fun updateSummery() { try { - pref.summary = - getString(R.string.mb_format).format(getFolderSize(cacheDir) / (1024L * 1024L)) + pref.summary = formatShortFileSize(view?.context, getFolderSize(cacheDir)) } catch (e: Exception) { logError(e) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt index 0dd1dec9..9e85ab94 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt @@ -9,6 +9,7 @@ import com.lagradost.cloudstream3.SearchQuality import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog @@ -19,6 +20,7 @@ class SettingsUI : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpToolbar(R.string.category_ui) + setPaddingBottom() } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { hideKeyboard() 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 ee974502..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 @@ -13,6 +13,7 @@ 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 +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.utils.BackupUtils.backup import com.lagradost.cloudstream3.utils.BackupUtils.restorePrompt @@ -31,6 +32,7 @@ class SettingsUpdates : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setUpToolbar(R.string.category_updates) + setPaddingBottom() } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt index d49ea10c..e46c1508 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt @@ -134,7 +134,8 @@ class PluginAdapter( //} if (data.isDownloaded) { - val plugin = PluginManager.urlPlugins[metadata.url] + // On local plugins page the filepath is provided instead of url. + val plugin = PluginManager.urlPlugins[metadata.url] ?: PluginManager.plugins[metadata.url] if (plugin?.openSettings != null) { itemView.action_settings?.isVisible = true itemView.action_settings.setOnClickListener { 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 27a43d4d..0ee0615a 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 @@ -6,10 +6,22 @@ import android.widget.SearchView import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.lifecycle.map +import com.lagradost.cloudstream3.AcraApplication +import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.getPairList +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar +import com.lagradost.cloudstream3.ui.settings.appLanguages +import com.lagradost.cloudstream3.ui.settings.getCurrentLocale +import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog +import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog +import com.lagradost.cloudstream3.utils.SubtitleHelper +import com.lagradost.cloudstream3.utils.UIHelper.toPx +import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API import kotlinx.android.synthetic.main.fragment_plugins.* const val PLUGINS_BUNDLE_NAME = "name" @@ -32,6 +44,7 @@ class PluginsFragment : Fragment() { // Since the ViewModel is getting reused the tvTypes must be cleared between uses pluginViewModel.tvTypes.clear() + pluginViewModel.languages = listOf() pluginViewModel.search(null) val name = arguments?.getString(PLUGINS_BUNDLE_NAME) @@ -50,6 +63,27 @@ class PluginsFragment : Fragment() { R.id.download_all -> { PluginsViewModel.downloadAll(activity, url, pluginViewModel) } + R.id.lang_filter -> { + val tempLangs = appLanguages.toMutableList() + val languageCodes = mutableListOf("none") + tempLangs.map { (_, _, iso) -> iso } + val languageNames = + mutableListOf(getString(R.string.no_data)) + tempLangs.map { (emoji, name, iso) -> + val flag = + emoji.ifBlank { SubtitleHelper.getFlagFromIso(iso) ?: "ERROR" } + "$flag $name" + } + val selectedList = + pluginViewModel.languages.map { it -> languageCodes.indexOf(it) } + + activity?.showMultiDialog( + languageNames, + selectedList, + getString(R.string.provider_lang_settings), + {}) { newList -> + pluginViewModel.languages = newList.map { it -> languageCodes[it] } + pluginViewModel.updateFilteredPlugins() + } + } else -> {} } return@setOnMenuItemClickListener true @@ -94,6 +128,11 @@ class PluginsFragment : Fragment() { pluginViewModel.handlePluginAction(activity, url, it, isLocal) } + if (context?.isTvSettings() == true) { + // Scrolling down does not reveal the whole RecyclerView on TV, add to bypass that. + plugin_recycler_view?.setPadding(0, 0, 0, 200.toPx) + } + observe(pluginViewModel.filteredPlugins) { (scrollToTop, list) -> (plugin_recycler_view?.adapter as? PluginAdapter?)?.updateList(list) @@ -104,6 +143,7 @@ class PluginsFragment : Fragment() { if (isLocal) { // No download button and no categories on local settings_toolbar?.menu?.findItem(R.id.download_all)?.isVisible = false + settings_toolbar?.menu?.findItem(R.id.lang_filter)?.isVisible = false pluginViewModel.updatePluginListLocal() tv_types_scroll_view?.isVisible = false } else { @@ -123,11 +163,14 @@ class PluginsFragment : Fragment() { home_select_others ) +// val supportedTypes: Array = +// pluginViewModel.filteredPlugins.value!!.second.flatMap { it -> it.plugin.second.tvTypes ?: listOf("Other") }.distinct().toTypedArray() + // Copy pasted code for ((button, validTypes) in pairList) { val validTypesMapped = validTypes.map { it.name } - val isValid = - true //validAPIs.any { api -> validTypes.any { api.supportedTypes.contains(it) } } + val isValid = true + //validTypes.any { it -> supportedTypes.contains(it.name) } button?.isVisible = isValid if (isValid) { fun buttonContains(): Boolean { 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 146f0f6d..0a71c17a 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 @@ -38,6 +38,7 @@ class PluginsViewModel : ViewModel() { var filteredPlugins: LiveData = _filteredPlugins val tvTypes = mutableListOf() + var languages = listOf() private var currentQuery: String? = null companion object { @@ -175,7 +176,7 @@ class PluginsViewModel : ViewModel() { } this.plugins = list - _filteredPlugins.postValue(false to list.filterTvTypes().sortByQuery(currentQuery)) + _filteredPlugins.postValue(false to list.filterTvTypes().filterLang().sortByQuery(currentQuery)) } // Perhaps can be optimized? @@ -187,6 +188,16 @@ class PluginsViewModel : ViewModel() { } } + private fun List.filterLang(): List { + if (languages.isEmpty()) return this + return this.filter { + if (it.plugin.second.language == null) { + return@filter languages.contains("none") + } + languages.contains(it.plugin.second.language) + } + } + private fun List.sortByQuery(query: String?): List { return if (query == null) { // Return list to base state if no query @@ -197,7 +208,7 @@ class PluginsViewModel : ViewModel() { } fun updateFilteredPlugins() { - _filteredPlugins.postValue(false to plugins.filterTvTypes().sortByQuery(currentQuery)) + _filteredPlugins.postValue(false to plugins.filterTvTypes().filterLang().sortByQuery(currentQuery)) } fun updatePluginList(repositoryUrl: String) = viewModelScope.launchSafe { @@ -223,6 +234,6 @@ class PluginsViewModel : ViewModel() { } plugins = downloadedPlugins - _filteredPlugins.postValue(false to downloadedPlugins.filterTvTypes().sortByQuery(currentQuery)) + _filteredPlugins.postValue(false to downloadedPlugins.filterTvTypes().filterLang().sortByQuery(currentQuery)) } } \ No newline at end of file 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 9b75fe14..fa39e374 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppUtils.kt @@ -288,7 +288,7 @@ object AppUtils { return this.packageManager.hasSystemFeature("android.software.webview") } - private fun openWebView(fragment: Fragment?, url: String) { + fun openWebView(fragment: Fragment?, url: String) { if (fragment?.context?.hasWebView() == true) normalSafeApiCall { fragment diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt index 43718e0e..1b4c36f7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/Coroutines.kt @@ -43,6 +43,13 @@ object Coroutines { } } + suspend fun V.mainWork(work: suspend (CoroutineScope.(V) -> T)): T { + val value = this + return withContext(Dispatchers.Main) { + work(value) + } + } + fun runOnMainThread(work: (() -> Unit)) { val mainHandler = Handler(Looper.getMainLooper()) mainHandler.post { diff --git a/app/src/main/res/layout/fragment_plugins.xml b/app/src/main/res/layout/fragment_plugins.xml index e1606e81..54eae80f 100644 --- a/app/src/main/res/layout/fragment_plugins.xml +++ b/app/src/main/res/layout/fragment_plugins.xml @@ -127,6 +127,7 @@ + - bottom_title_key - override_site_key - %d %s | %sMB - %s • %sGB - %sMB / %sMB - %dMB - %s %s - +%d - -%d - %d - %d - %.1f/10.0 - %d %s Ep %d Elenco: %s O episódio %d vai ser lançado em @@ -29,9 +16,7 @@ Episode Poster Main Poster Next Random - @string/play_episode Go back - @string/home_change_provider_img_des Change Provider Preview Background @@ -54,7 +39,6 @@ Sem dados Mais Opções Próximo episódio - @string/synopsis Gêneros Compartilhar Abrir no Navegador @@ -86,7 +70,6 @@ Download Falhado Download Cancelado Download Finalizado - %s - %s Transmitir Erro Carregando Links @@ -236,7 +219,6 @@ Deletar Arquivo Deletar - @string/sort_cancel Pausar Retomar -30 @@ -303,10 +285,6 @@ Etiqueta Dub Etiqueta Sub Título - show_hd_key - show_dub_key - show_sub_key - show_title_key Alternar elementos da interface no pôster Nenhuma Atualização encontrada @@ -350,7 +328,6 @@ Zoom Aviso Legal - 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. @@ -389,10 +366,6 @@ - anilist_key - mal_key - opensubtitles_key - nginx_key senha123 MeuNomeLegal oi@mundo.com @@ -432,7 +405,6 @@ Tudo Max Min - @string/none Contornado Afundado Sombreado diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 645337c3..1703b5d5 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -3,17 +3,6 @@ - %d %s | %sMB - %s • %sGB - %sMB / %sMB - %dMB - %s %s - +%d - -%d - %d - %d - %.1f/10.0 - %d %s Ep %d Hrají: %s @@ -23,9 +12,7 @@ Episode Poster Main Poster Next Random - @string/play_episode Go back - @string/home_change_provider_img_des Change Provider Preview Background @@ -48,7 +35,6 @@ Žádná data Další možnosti Další epizoda - @string/synopsis Žánry Sdílet Otevřít v prohlížeči @@ -80,7 +66,6 @@ Stahování selhalo Stahování zrušeno Stahování dokončeno - %s - %s Chyba při načítání odkazů Interní úložiště @@ -229,7 +214,6 @@ Smazat soubor Smazat - @string/sort_cancel Pozastavit Pokračovat -30 @@ -296,10 +280,6 @@ Štítek dabingu Štítek titulků Název - show_hd_key - show_dub_key - show_sub_key - show_title_key Přepnout prvky UI na plakátu Nenalezeny žádné aktualizace @@ -339,7 +319,6 @@ Přiblížit Odmítnutí odpovědnosti - legal_notice_key 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. @@ -378,10 +357,6 @@ - anilist_key - mal_key - opensubtitles_key - nginx_key heslo123 MojeSuperJmeno ahoj@svete.cz @@ -418,7 +393,6 @@ Vše Max Min - @string/none Ohraničení Potlačené Stín diff --git a/app/src/main/res/values-es/strings-es.xml b/app/src/main/res/values-es/strings-es.xml index fbd73ce8..d845cf82 100644 --- a/app/src/main/res/values-es/strings-es.xml +++ b/app/src/main/res/values-es/strings-es.xml @@ -1,17 +1,6 @@ - %d %s | %sMB - %s • %sGB - %sMB / %sMB - %dMB - %s %s - +%d - -%d - %d - %d - %.1f/10.0 - %d %s Ep %d Reparto: %s El episodio %d se publicará en @@ -26,9 +15,7 @@ Episode Poster Main Poster Next Random - @string/play_episode Go back - @string/home_change_provider_img_des Change Provider Preview Background @@ -50,9 +37,8 @@ Sin datos Más opciones - Siguiente episodio - @string/synopsis - Géneros + Siguiente episodio + Géneros Compartir Abrir en el navegador Omitir carga @@ -82,8 +68,7 @@ Descarga iniciada Descarga fallida Descarga cancelada - Descarga realizada - %s - %s + Descarga realizada Transmitir Error al cargar enlaces @@ -229,9 +214,8 @@ No se encontraron episodios Eliminar archivo - Eliminar - @string/sort_cancel - Pausar + Eliminar + Pausar Reanudar -30 +30 @@ -296,12 +280,8 @@ Etiqueta de calidad Etiqueta de doblaje Etiqueta de subtitulado - Título - show_hd_key - show_dub_key - show_sub_key - show_title_key - Alternar elementos de la interfaz de usuario en el póster + Título + Alternar elementos de la interfaz de usuario en el póster No se encontró ninguna actualización Buscar actualizaciones @@ -343,9 +323,8 @@ Estirar Zoom - Descargo de responsabilidad - legal_notice_key - Any legal issues regarding the content on this application + 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. @@ -382,11 +361,7 @@ Coloca el título debajo del póster - - anilist_key - mal_key - opensubtitles_key - nginx_key + contraseña123 MiNombreDeUsuarioGenial hola@mundo.com @@ -424,9 +399,8 @@ Normal Todo Máximo - Mínimo - @string/none - Contorno + Mínimo + Contorno Deprimido Sombra Elevado diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 46a24f71..7d259b31 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -293,5 +293,4 @@ Image de l\'affiche Connecté %s Définir le statut de visionage - Qu\'est-ce qu\'Nginx ? diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 171f5e1f..4abb6bf9 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -2,17 +2,6 @@ - %d %s | %sMB - %s • %sGB - %sMB / %sMB - %dMB - %s %s - +%d - -%d - %d - %d - %.1f/10.0 - %d %s Ep %d Pemeran: %s @@ -22,9 +11,7 @@ Episode Poster Main Poster Next Random - @string/play_episode Go back - @string/home_change_provider_img_des Change Provider Preview Background @@ -47,7 +34,6 @@ Tidak Ada Data Opsi Lanjutan Episode selanjutnya - @string/synopsis Genre Bagikan Buka Di Browser @@ -79,7 +65,6 @@ Download Gagal Download Dibatalkan Download Selesai - %s - %s Error Memuat Tautan Penyimpanan Internal @@ -225,7 +210,6 @@ Hapus File Hapus - @string/sort_cancel Jeda Lanjutkan -30 @@ -292,10 +276,6 @@ Label dub Label sub Judul - show_hd_key - show_dub_key - show_sub_key - show_title_key Aktifkan atau nonaktifkan elemen UI pada poster Tidak Ada Update Yang Ditemukan @@ -335,7 +315,6 @@ Zoom Peringatan - 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. @@ -373,8 +352,6 @@ - anilist_key - mal_key - %d %s | %sMB - %s • %sGB - %sMB / %sMB - %s %s - +%d - -%d - %d - %d - %.1f/10.0 - %d %s Ep %d Cast: %s L\'episodio %d uscirà in @@ -79,7 +69,6 @@ Download fallito Download cancellato Download completato - %s - %s Stream Errore durante il caricamento dei link @@ -227,7 +216,6 @@ Elimina file Elimina - @string/sort_cancel Pausa Riprendi -30 @@ -390,7 +378,6 @@ Tutto Max Min - @string/none Contorno Bassorilievo Ombra diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 3a083170..4a721ce4 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -1,17 +1,6 @@ - %d %s | %sMB - %s • %sGB - %sMB / %sMB - %dMB - %s %s - +%d - -%d - %d - %d - %.1f/10.0 - %d %s Ep %d Cast: %s Aflevering %d zal worden uitgebracht in @@ -25,9 +14,7 @@ Aflevering Poster Hoofdposter Volgende willekeurig - @string/play_episode Ga terug - @string/home_change_provider_img_des Wijzig provider Voorbeeld Achtergrond @@ -51,7 +38,6 @@ Geen gegevens Meer Opties Volgende aflevering - @string/synopsis Genres Deel Openen in Browser @@ -234,7 +220,6 @@ Verwijder bestand Verwijder - @string/sort_cancel Pauze Hervatten -30 @@ -302,10 +287,6 @@ Dub label Sub label Titel - show_hd_key - show_dub_key - show_sub_key - show_title_key Schakel UI-elementen op poster Geen update gevonden @@ -371,10 +352,6 @@ - anilist_key - mal_key - opensubtitles_key - nginx_key wachtwoord123 MijnCoolGebruikersnaam hello@Wereld.com @@ -414,7 +391,6 @@ Allemaal Max Min - @string/none Overzicht Depressed Schaduw diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index 8a25fe08..fc7cbbe2 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -232,7 +232,6 @@ Foretrukket Videoinnhold 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. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 6ef5f318..08f01cdb 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -430,4 +430,10 @@ 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? + %s (Wyłączone) + Ukryj wybraną jakość wideo w wynikach wyszukiwania diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 12e81004..540e7644 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -1,17 +1,6 @@ - %d %s | %sMB - %s • %sGB - %sMB / %sMB - %dMB - %s %s - +%d - -%d - %d - %d - %.1f/10.0 - %d %s Ep %d Distribuție: %s Episodul %d va fi lansat în @@ -78,7 +67,6 @@ Descărcare eșuată Descărcare anulată Descărcare finalizată - %s - %s Stream Eroare la încărcarea linkurilor @@ -226,7 +214,6 @@ Ștergeți fișierul Ștergeți - @string/sort_cancel Pauză Continuă -30 @@ -405,7 +392,6 @@ Tot Maxim Minim - @string/none Contur Deprimat Umbră diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 0513d122..513ac315 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1,17 +1,6 @@ - %d %s | %sMB - %s • %sGB - %sMB / %sMB - %dMB - %s %s - +%d - -%d - %d - %d - %.1f/10.0 - %d %s Ep %d Cast: %s Bölüm %d şu tarihte yayınlanacak: @@ -25,9 +14,7 @@ Episode Poster Main Poster Next Random - @string/play_episode Go back - @string/home_change_provider_img_des Change Provider Preview Background @@ -51,7 +38,6 @@ Veri yok Daha fazla seçenek Sonraki bölüm - @string/synopsis Türler Paylaş Tarayıcıda aç @@ -84,7 +70,6 @@ İndirme başarısız oldu İndirme iptal edildi İndirme bitti - %s - %s Yayınla Bağlantılar yüklenirken hata oluştu @@ -232,7 +217,6 @@ Dosyayı sil Sil - @string/sort_cancel Durdur Sürdür -30 @@ -300,10 +284,6 @@ Dublaj etiketi Alt yazı etiketi Başlık - show_hd_key - show_dub_key - show_sub_key - show_title_key Poster üzerindeki öğeler Güncelleme bulunamadı @@ -347,7 +327,6 @@ Yakınlaştır 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. @@ -386,10 +365,6 @@ - anilist_key - mal_key - opensubtitles_key - nginx_key şifre123 HavalıKullanıcıAdı hello@world.com @@ -429,7 +404,6 @@ Hepsi Maksimum Minimum - @string/none Dış hat Çökmüş Gölge diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 49631d1f..8454187a 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -3,17 +3,6 @@ - %d %s | %sMB - %s • %sGB - %sMB / %sMB - %dMB - %s %s - +%d - -%d - %d - %d - %.1f/10.0 - %d %s Tập %d Diễn viên: %s Tập %d sẽ ra mắt sau @@ -27,9 +16,7 @@ Episode Poster Main Poster Next Random - @string/play_episode Go back - @string/home_change_provider_img_des Change Provider Preview Background @@ -52,7 +39,6 @@ Không có dữ liệu Thêm tuỳ chọn Tập tiếp theo - @string/synopsis Thể loại Chia sẻ Mở bằng trình duyệt @@ -84,7 +70,6 @@ Tải lỗi Đã hủy Tải thành công - %s - %s Stream Đã có lỗi xảy ra @@ -232,7 +217,6 @@ Xóa Tệp Xóa - @string/sort_cancel Tạm Dừng Tiếp Tục -30 @@ -298,10 +282,6 @@ Nhãn lồng tiếng Nhãn phụ đề Tiêu đề - show_hd_key - show_dub_key - show_sub_key - show_title_key Thay đổi giao diện trên poster Bạn đang dùng phiên bản mới nhất @@ -345,7 +325,6 @@ Phóng to 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. @@ -383,10 +362,6 @@ Đặt tiêu đề dưới poster - anilist_key - mal_key - opensubtitles_key - nginx_key Mật khẩu Tài khoản Email @@ -426,7 +401,6 @@ Tất cả Tối đa Tối thiểu - @string/none Viền Chìm Đổ bóng diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index b178d676..4a4a85be 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -10,9 +10,7 @@ 剧集海报 主海报 随机下一个 - @string/play_episode 返回 - @string/home_change_provider_img_des 更改内容提供者 预览背景 @@ -33,7 +31,6 @@ 无数据 更多选项 下一集 - @string/synopsis 类型 分享 在浏览器中打开 @@ -65,7 +62,6 @@ 下载失败 下载取消 下载完毕 - %s - %s 加载链接时出错 内部存储 @@ -196,7 +192,6 @@ 删除文件 删除 - @string/sort_cancel 暂停 继续 -30 @@ -287,7 +282,6 @@ 缩放 免责声明 - 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. @@ -318,8 +312,6 @@ 应用主题 - anilist_key - mal_key - %d %s | %sMB - %s • %sGB - %sMB / %sMB - %dMB + %d %s | %s + %s • %s + %s / %s %s %s +%d -%d