mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Merge branch 'master' into feature/remote-sync
This commit is contained in:
commit
744d1ebd21
69 changed files with 1583 additions and 1045 deletions
3
.github/workflows/prerelease.yml
vendored
3
.github/workflows/prerelease.yml
vendored
|
@ -43,7 +43,8 @@ jobs:
|
|||
echo "key_pwd=$KEY_PWD" >> $GITHUB_OUTPUT
|
||||
- name: Run Gradle
|
||||
run: |
|
||||
./gradlew assemblePrerelease makeJar androidSourcesJar
|
||||
./gradlew assemblePrerelease build androidSourcesJar
|
||||
./gradlew makeJar # for classes.jar, has to be done after assemblePrerelease
|
||||
env:
|
||||
SIGNING_KEY_ALIAS: "key0"
|
||||
SIGNING_KEY_PASSWORD: ${{ steps.fetch_keystore.outputs.key_pwd }}
|
||||
|
|
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
|
@ -8,6 +8,7 @@
|
|||
<option name="testRunner" value="GRADLE" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="jbr-17" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
|
||||
import org.jetbrains.dokka.gradle.DokkaTask
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.net.URL
|
||||
import java.util.Properties
|
||||
|
@ -7,8 +8,8 @@ import java.io.FileInputStream
|
|||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("com.google.devtools.ksp")
|
||||
id("kotlin-android")
|
||||
id("kotlin-kapt")
|
||||
id("org.jetbrains.dokka")
|
||||
}
|
||||
|
||||
|
@ -43,12 +44,12 @@ android {
|
|||
enable = true
|
||||
}
|
||||
|
||||
// disable this for now
|
||||
//externalNativeBuild {
|
||||
// cmake {
|
||||
// path("CMakeLists.txt")
|
||||
// }
|
||||
//}
|
||||
/* disable this for now
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path("CMakeLists.txt")
|
||||
}
|
||||
}*/
|
||||
|
||||
signingConfigs {
|
||||
create("prerelease") {
|
||||
|
@ -67,10 +68,8 @@ android {
|
|||
defaultConfig {
|
||||
applicationId = "com.lagradost.cloudstream3"
|
||||
minSdk = 21
|
||||
|
||||
// https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading
|
||||
targetSdk = 33 // android 14 is fucked
|
||||
|
||||
targetSdk = 33 /* Android 14 is Fu*ked
|
||||
^ https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading*/
|
||||
versionCode = 62
|
||||
versionName = "4.2.1"
|
||||
|
||||
|
@ -98,8 +97,9 @@ android {
|
|||
)
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
kapt {
|
||||
includeCompileClasspath = true
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
arg("exportSchema", "true")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,6 +132,7 @@ android {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions.add("state")
|
||||
productFlavors {
|
||||
create("stable") {
|
||||
|
@ -148,25 +149,18 @@ android {
|
|||
versionCode = (System.currentTimeMillis() / 60000).toInt()
|
||||
}
|
||||
}
|
||||
//toolchain {
|
||||
// languageVersion.set(JavaLanguageVersion.of(17))
|
||||
// }
|
||||
// jvmToolchain(17)
|
||||
|
||||
compileOptions {
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs = listOf("-Xjvm-default=compatibility")
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError = false
|
||||
checkReleaseBuilds = false
|
||||
}
|
||||
|
||||
namespace = "com.lagradost.cloudstream3"
|
||||
}
|
||||
|
||||
|
@ -175,111 +169,80 @@ repositories {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.google.android.mediahome:video:1.0.0")
|
||||
implementation("androidx.test.ext:junit-ktx:1.1.5")
|
||||
testImplementation("org.json:json:20230618")
|
||||
|
||||
implementation("androidx.core:core-ktx:1.12.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
|
||||
implementation("com.google.android.material:material:1.10.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.4")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.4")
|
||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
|
||||
|
||||
// Testing
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("org.json:json:20230618")
|
||||
androidTestImplementation("androidx.test:core")
|
||||
implementation("androidx.test.ext:junit-ktx:1.1.5")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
androidTestImplementation("androidx.test:core")
|
||||
|
||||
// implementation("io.karn:khttp-android:0.1.2") //okhttp instead
|
||||
// implementation("org.jsoup:jsoup:1.13.1")
|
||||
// DONT UPDATE, WILL CRASH ANDROID TV ????
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1")
|
||||
|
||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||
|
||||
implementation("com.github.bumptech.glide:glide:4.13.1")
|
||||
kapt("com.github.bumptech.glide:compiler:4.13.1")
|
||||
implementation("com.github.bumptech.glide:okhttp3-integration:4.13.0")
|
||||
// Android Core & Lifecycle
|
||||
implementation("androidx.core:core-ktx:1.12.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.5")
|
||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
|
||||
|
||||
// Design & UI
|
||||
implementation("jp.wasabeef:glide-transformations:4.3.0")
|
||||
|
||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||
implementation("com.google.android.material:material:1.10.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
|
||||
// implementation("androidx.leanback:leanback-paging:1.1.0-alpha09")
|
||||
// Glide Module
|
||||
ksp("com.github.bumptech.glide:ksp:4.15.1")
|
||||
implementation("com.github.bumptech.glide:glide:4.15.1")
|
||||
implementation("com.github.bumptech.glide:okhttp3-integration:4.15.1")
|
||||
|
||||
// Media 3
|
||||
implementation("androidx.media3:media3-common:1.1.1")
|
||||
implementation("androidx.media3:media3-exoplayer:1.1.1")
|
||||
implementation("androidx.media3:media3-datasource-okhttp:1.1.1")
|
||||
// For KSP -> Official Annotation Processors are Not Yet Supported for KSP
|
||||
ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
|
||||
implementation("com.google.guava:guava:32.1.2-android")
|
||||
implementation("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
|
||||
|
||||
// Media 3 (ExoPlayer)
|
||||
implementation("androidx.media3:media3-ui:1.1.1")
|
||||
implementation("androidx.media3:media3-session:1.1.1")
|
||||
implementation("androidx.media3:media3-cast:1.1.1")
|
||||
implementation("androidx.media3:media3-common:1.1.1")
|
||||
implementation("androidx.media3:media3-session:1.1.1")
|
||||
implementation("androidx.media3:media3-exoplayer:1.1.1")
|
||||
implementation("com.google.android.mediahome:video:1.0.0")
|
||||
implementation("androidx.media3:media3-exoplayer-hls:1.1.1")
|
||||
implementation("androidx.media3:media3-exoplayer-dash:1.1.1")
|
||||
// Custom ffmpeg extension for audio codecs
|
||||
implementation("com.github.recloudstream:media-ffmpeg:1.1.0")
|
||||
implementation("androidx.media3:media3-datasource-okhttp:1.1.1")
|
||||
|
||||
// Bug reports
|
||||
// PlayBack
|
||||
implementation("com.jaredrummler:colorpicker:1.1.0") // Subtitle Color Picker
|
||||
implementation("com.github.recloudstream:media-ffmpeg:1.1.0") // Custom FF-MPEG Lib for Audio Codecs
|
||||
implementation("com.github.teamnewpipe:NewPipeExtractor:eac850") /* For Trailers
|
||||
^ Update to Latest Commits if Trailers Misbehave, github.com/TeamNewPipe/NewPipeExtractor/commits/dev */
|
||||
implementation("com.github.albfernandez:juniversalchardet:2.4.0") // Subtitle Decoding
|
||||
|
||||
// Crash Reports (AcraApplication.kt)
|
||||
implementation("ch.acra:acra-core:5.11.2")
|
||||
implementation("ch.acra:acra-toast:5.11.2")
|
||||
|
||||
compileOnly("com.google.auto.service:auto-service-annotations:1.1.1")
|
||||
//either for java sources:
|
||||
annotationProcessor("com.google.auto.service:auto-service:1.1.1")
|
||||
//or for kotlin sources (requires kapt gradle plugin):
|
||||
kapt("com.google.auto.service:auto-service:1.1.1")
|
||||
|
||||
// subtitle color picker
|
||||
implementation("com.jaredrummler:colorpicker:1.1.0")
|
||||
|
||||
// run JS
|
||||
// do not upgrade to 1.7.14, since in 1.7.14 Rhino uses the `SourceVersion` class, which is not
|
||||
// available on Android (even when using desugaring), and `NoClassDefFoundError` is thrown
|
||||
implementation("org.mozilla:rhino:1.7.13")
|
||||
|
||||
// TorrentStream
|
||||
// implementation("com.github.TorrentStream:TorrentStream-Android:2.7.0")
|
||||
|
||||
// Downloading
|
||||
implementation("androidx.work:work-runtime:2.8.1")
|
||||
implementation("androidx.work:work-runtime-ktx:2.8.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.4") // http library
|
||||
// To fix SSL fuckery on android 9
|
||||
implementation("org.conscrypt:conscrypt-android:2.5.2")
|
||||
// Util to skip the URI file fuckery 🙏
|
||||
implementation("com.github.LagradOst:SafeFile:0.0.5")
|
||||
|
||||
// API because cba maintaining it myself
|
||||
implementation("com.uwetrottmann.tmdb2:tmdb-java:2.10.0")
|
||||
|
||||
implementation("com.github.discord:OverlappingPanels:0.1.5")
|
||||
// debugImplementation because LeakCanary should only run in debug builds.
|
||||
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.12")
|
||||
|
||||
// for shimmer when loading
|
||||
implementation("com.facebook.shimmer:shimmer:0.5.0")
|
||||
|
||||
// UI Stuff
|
||||
implementation("com.facebook.shimmer:shimmer:0.5.0") // Shimmering Effect (Loading Skeleton)
|
||||
implementation("androidx.palette:palette-ktx:1.0.0") // Palette For Images -> Colors
|
||||
implementation("androidx.tvprovider:tvprovider:1.0.0")
|
||||
implementation("com.github.discord:OverlappingPanels:0.1.5") // Gestures
|
||||
implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0") // SeekBar Preview
|
||||
|
||||
// used for subtitle decoding https://github.com/albfernandez/juniversalchardet
|
||||
implementation("com.github.albfernandez:juniversalchardet:2.4.0")
|
||||
|
||||
// newpipe yt taken from https://github.com/TeamNewPipe/NewPipeExtractor/commits/dev
|
||||
// this should be updated frequently to avoid trailer fu*kery
|
||||
implementation("com.github.teamnewpipe:NewPipeExtractor:917554a")
|
||||
// Extensionns & Other Libs
|
||||
implementation("org.mozilla:rhino:1.7.13") /* run JS
|
||||
^ Don't Bump RhinoJS to 1.7.14, since in 1.7.14 Rhino Uses the `SourceVersion` Class, Which is NOT
|
||||
Available on Android (even with Desugaring) & `NoClassDefFoundError` Occurs. */
|
||||
implementation("me.xdrop:fuzzywuzzy:1.4.0") // Library/Ext Searching with Levenshtein Distance
|
||||
implementation("com.github.LagradOst:SafeFile:0.0.5") // To Prevent the URI File Fu*kery
|
||||
implementation("org.conscrypt:conscrypt-android:2.5.2") // To Fix SSL Fu*kery on Android 9
|
||||
implementation("com.uwetrottmann.tmdb2:tmdb-java:2.10.0") // TMDB API v3 Wrapper Made with RetroFit
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.6")
|
||||
|
||||
// Library/extensions searching with Levenshtein distance
|
||||
implementation("me.xdrop:fuzzywuzzy:1.4.0")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") /* JSON Parser
|
||||
^ Don't Bump Jackson above 2.13.1 , Crashes on Android TV's and FireSticks that have Min API
|
||||
Level 25 or Less. */
|
||||
|
||||
// color palette for images -> colors
|
||||
implementation("androidx.palette:palette-ktx:1.0.0")
|
||||
|
@ -300,22 +263,31 @@ dependencies {
|
|||
group = "org.apache.httpcomponents",
|
||||
)
|
||||
}
|
||||
// seekbar https://github.com/rubensousa/PreviewSeekBar
|
||||
implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0")
|
||||
|
||||
// Downloading & Networking
|
||||
implementation("androidx.work:work-runtime:2.8.1")
|
||||
implementation("androidx.work:work-runtime-ktx:2.8.1")
|
||||
implementation("com.github.Blatzar:NiceHttp:0.4.4") // HTTP Lib
|
||||
}
|
||||
|
||||
|
||||
tasks.register("androidSourcesJar", Jar::class) {
|
||||
archiveClassifier.set("sources")
|
||||
from(android.sourceSets.getByName("main").java.srcDirs) //full sources
|
||||
from(android.sourceSets.getByName("main").java.srcDirs) // Full Sources
|
||||
}
|
||||
|
||||
// this is used by the gradlew plugin
|
||||
// For GradLew Plugin
|
||||
tasks.register("makeJar", Copy::class) {
|
||||
from("build/intermediates/compile_app_classes_jar/prereleaseDebug")
|
||||
into("build")
|
||||
include("classes.jar")
|
||||
dependsOn("build")
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs = listOf("-Xjvm-default=all-compatibility")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<DokkaTask>().configureEach {
|
||||
|
@ -328,6 +300,7 @@ tasks.withType<DokkaTask>().configureEach {
|
|||
|
||||
// URL showing where the source code can be accessed through the web browser
|
||||
remoteUrl.set(URL("https://github.com/recloudstream/cloudstream/tree/master/app/src/main/java"))
|
||||
|
||||
// Suffix which is used to append the line number to the URL. Use #L for GitHub
|
||||
remoteLineSuffix.set("#L")
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<uses-permission android:name="android.permission.INTERNET" /> <!-- unless you only use cs3 as a player for downloaded stuff, you need this -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Downloads -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- Downloads on low api devices -->
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <!-- Plugin API -->
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" /> <!-- Plugin API -->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- some dependency needs this -->
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <!-- Used for player vertical slide -->
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <!-- Used for app update -->
|
||||
|
@ -17,7 +17,11 @@
|
|||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <!-- Required for getting arbitrary Aniyomi packages -->
|
||||
<!-- Required for getting arbitrary Aniyomi packages -->
|
||||
<uses-permission
|
||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<!-- Fixes android tv fuckery -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.touchscreen"
|
||||
|
@ -37,9 +41,11 @@
|
|||
<application
|
||||
android:name=".AcraApplication"
|
||||
android:allowBackup="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:appCategory="video"
|
||||
android:banner="@mipmap/ic_banner"
|
||||
android:fullBackupContent="@xml/backup_descriptor"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
|
@ -47,7 +53,7 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="o">
|
||||
tools:targetApi="tiramisu">
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||
|
@ -161,9 +167,8 @@
|
|||
|
||||
<activity
|
||||
android:name=".ui.account.AccountSelectActivity"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation"
|
||||
android:exported="true"
|
||||
android:resizeableActivity="true">
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden"
|
||||
android:exported="true">
|
||||
<intent-filter android:exported="true">
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
@ -182,8 +187,8 @@
|
|||
<receiver
|
||||
android:name=".receivers.VideoDownloadRestartReceiver"
|
||||
android:enabled="false"
|
||||
android:exported="true">
|
||||
<intent-filter android:exported="true">
|
||||
android:exported="false">
|
||||
<intent-filter android:exported="false">
|
||||
<action android:name="restart_service" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
|
|
@ -8,7 +8,6 @@ import android.content.Intent
|
|||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.google.auto.service.AutoService
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||
|
@ -37,7 +36,6 @@ import java.lang.ref.WeakReference
|
|||
import kotlin.concurrent.thread
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
class CustomReportSender : ReportSender {
|
||||
// Sends all your crashes to google forms
|
||||
override fun send(context: Context, errorContent: CrashReportData) {
|
||||
|
@ -65,7 +63,6 @@ class CustomReportSender : ReportSender {
|
|||
}
|
||||
}
|
||||
|
||||
@AutoService(ReportSenderFactory::class)
|
||||
class CustomSenderFactory : ReportSenderFactory {
|
||||
override fun create(context: Context, config: CoreConfiguration): ReportSender {
|
||||
return CustomReportSender()
|
||||
|
|
|
@ -19,6 +19,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.MainThread
|
||||
|
@ -133,7 +134,6 @@ import com.lagradost.cloudstream3.utils.DataStore.setKey
|
|||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
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
|
||||
|
@ -311,9 +311,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
// kinda shitty solution, but cant com main->home otherwise for popups
|
||||
val bookmarksUpdatedEvent = Event<Boolean>()
|
||||
/**
|
||||
* Used by data store helper to fully reload home when switching accounts
|
||||
* Used by DataStoreHelper to fully reload home when switching accounts
|
||||
*/
|
||||
val reloadHomeEvent = Event<Boolean>()
|
||||
/**
|
||||
* Used by DataStoreHelper to fully reload library when switching accounts
|
||||
*/
|
||||
val reloadLibraryEvent = Event<Boolean>()
|
||||
|
||||
|
||||
/**
|
||||
|
@ -652,34 +656,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
builder.show().setDefaultFocus()
|
||||
}
|
||||
|
||||
private fun backPressed() {
|
||||
this.window?.navigationBarColor =
|
||||
this.colorFromAttribute(R.attr.primaryGrayBackground)
|
||||
this.updateLocale()
|
||||
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 && isTvSettings()) {
|
||||
showConfirmExitDialog()
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
((supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment?)?.childFragmentManager?.primaryNavigationFragment as? IOnBackPressed)?.onBackPressed()
|
||||
?.let { runNormal ->
|
||||
if (runNormal) backPressed()
|
||||
} ?: run {
|
||||
backPressed()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
val broadcastIntent = Intent()
|
||||
broadcastIntent.action = "restart_service"
|
||||
|
@ -1091,6 +1067,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
} catch (_: Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
app.initClient(this)
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
@ -1388,6 +1365,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
this.putString(SearchFragment.SEARCH_QUERY, nextSearchQuery)
|
||||
}
|
||||
}
|
||||
|
||||
if (isTvSettings()) {
|
||||
if (navDestination.matchDestination(R.id.navigation_home)) {
|
||||
attachBackPressedCallback()
|
||||
} else detachBackPressedCallback()
|
||||
}
|
||||
}
|
||||
|
||||
//val navController = findNavController(R.id.nav_host_fragment)
|
||||
|
@ -1601,6 +1584,45 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
// showToast(this, currentFocus.toString(), Toast.LENGTH_LONG)
|
||||
// }
|
||||
// }
|
||||
|
||||
onBackPressedDispatcher.addCallback(
|
||||
this,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
window?.navigationBarColor = colorFromAttribute(R.attr.primaryGrayBackground)
|
||||
updateLocale()
|
||||
|
||||
// If we don't disable we end up in a loop with default behavior calling
|
||||
// this callback as well, so we disable it, run default behavior,
|
||||
// then re-enable this callback so it can be used for next back press.
|
||||
isEnabled = false
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
isEnabled = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private var backPressedCallback: OnBackPressedCallback? = null
|
||||
|
||||
private fun attachBackPressedCallback() {
|
||||
if (backPressedCallback == null) {
|
||||
backPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
showConfirmExitDialog()
|
||||
window?.navigationBarColor =
|
||||
colorFromAttribute(R.attr.primaryGrayBackground)
|
||||
updateLocale()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
backPressedCallback?.isEnabled = true
|
||||
onBackPressedDispatcher.addCallback(this, backPressedCallback ?: return)
|
||||
}
|
||||
|
||||
private fun detachBackPressedCallback() {
|
||||
backPressedCallback?.isEnabled = false
|
||||
}
|
||||
|
||||
suspend fun checkGithubConnectivity(): Boolean {
|
||||
|
|
|
@ -16,13 +16,13 @@ import javax.crypto.Cipher
|
|||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
// No License found in https://github.com/enimax-anime/key
|
||||
// special credits to @enimax for providing key
|
||||
// Code found in https://github.com/theonlymo/keys
|
||||
// special credits to @theonlymo for providing key
|
||||
class Megacloud : Rabbitstream() {
|
||||
override val name = "Megacloud"
|
||||
override val mainUrl = "https://megacloud.tv"
|
||||
override val embed = "embed-2/ajax/e-1"
|
||||
override val key = "https://raw.githubusercontent.com/enimax-anime/key/e6/key.txt"
|
||||
override val key = "https://raw.githubusercontent.com/theonlymo/keys/e1/key"
|
||||
}
|
||||
|
||||
class Dokicloud : Rabbitstream() {
|
||||
|
@ -35,7 +35,7 @@ open class Rabbitstream : ExtractorApi() {
|
|||
override val mainUrl = "https://rabbitstream.net"
|
||||
override val requiresReferer = false
|
||||
open val embed = "ajax/embed-4"
|
||||
open val key = "https://raw.githubusercontent.com/enimax-anime/key/e4/key.txt"
|
||||
open val key = "https://raw.githubusercontent.com/theonlymo/keys/e4/key"
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
|
@ -86,21 +86,23 @@ open class Rabbitstream : ExtractorApi() {
|
|||
|
||||
private suspend fun getRawKey(): String = app.get(key).text
|
||||
|
||||
private fun extractRealKey(originalString: String?, stops: String): Pair<String, String> {
|
||||
val table = parseJson<List<List<Int>>>(stops)
|
||||
val decryptedKey = StringBuilder()
|
||||
var offset = 0
|
||||
var encryptedString = originalString
|
||||
private fun extractRealKey(sources: String, stops: String): Pair<String, String> {
|
||||
val decryptKey = parseJson<List<List<Int>>>(stops)
|
||||
val sourcesArray = sources.toCharArray()
|
||||
|
||||
table.forEach { (start, end) ->
|
||||
decryptedKey.append(encryptedString?.substring(start - offset, end - offset))
|
||||
encryptedString = encryptedString?.substring(
|
||||
0,
|
||||
start - offset
|
||||
) + encryptedString?.substring(end - offset)
|
||||
offset += end - start
|
||||
var extractedKey = ""
|
||||
var currentIndex = 0
|
||||
for (index in decryptKey) {
|
||||
val start = index[0] + currentIndex
|
||||
val end = start + index[1]
|
||||
for (i in start until end) {
|
||||
extractedKey += sourcesArray[i].toString()
|
||||
sourcesArray[i] = ' '
|
||||
}
|
||||
currentIndex += index[1]
|
||||
}
|
||||
return decryptedKey.toString() to encryptedString.toString()
|
||||
|
||||
return extractedKey to sourcesArray.joinToString("")
|
||||
}
|
||||
|
||||
private inline fun <reified T> decryptMapped(input: String, key: String): T? {
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
package com.lagradost.cloudstream3.ui
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.lagradost.cloudstream3.databinding.WhoIsWatchingAccountAddBinding
|
||||
import com.lagradost.cloudstream3.databinding.WhoIsWatchingAccountBinding
|
||||
import com.lagradost.cloudstream3.ui.result.setImage
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
|
||||
class WhoIsWatchingAdapter(
|
||||
private val selectCallBack: (DataStoreHelper.Account) -> Unit = { },
|
||||
private val editCallBack: (DataStoreHelper.Account) -> Unit = { },
|
||||
private val addAccountCallback: () -> Unit = {}
|
||||
) :
|
||||
ListAdapter<DataStoreHelper.Account, WhoIsWatchingAdapter.WhoIsWatchingHolder>(DiffCallback()) {
|
||||
|
||||
companion object {
|
||||
const val FOOTER = 1
|
||||
const val NORMAL = 0
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return currentList.size + 1
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int = when (position) {
|
||||
currentList.size -> FOOTER
|
||||
else -> NORMAL
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WhoIsWatchingHolder =
|
||||
WhoIsWatchingHolder(
|
||||
binding = when (viewType) {
|
||||
NORMAL -> WhoIsWatchingAccountBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
|
||||
FOOTER -> WhoIsWatchingAccountAddBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
|
||||
else -> throw NotImplementedError()
|
||||
},
|
||||
selectCallBack = selectCallBack,
|
||||
addAccountCallback = addAccountCallback,
|
||||
editCallBack = editCallBack,
|
||||
)
|
||||
|
||||
override fun onBindViewHolder(holder: WhoIsWatchingHolder, position: Int) =
|
||||
holder.bind(currentList.getOrNull(position))
|
||||
|
||||
class WhoIsWatchingHolder(
|
||||
val binding: ViewBinding,
|
||||
val selectCallBack: (DataStoreHelper.Account) -> Unit,
|
||||
val addAccountCallback: () -> Unit,
|
||||
val editCallBack: (DataStoreHelper.Account) -> Unit
|
||||
) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(card: DataStoreHelper.Account?) {
|
||||
when (binding) {
|
||||
is WhoIsWatchingAccountBinding -> binding.apply {
|
||||
if (card == null) return@apply
|
||||
outline.isVisible = card.keyIndex == DataStoreHelper.selectedKeyIndex
|
||||
profileText.text = card.name
|
||||
profileImageBackground.setImage(card.image)
|
||||
|
||||
// Handle the lock indicator
|
||||
val isLocked = card.lockPin != null
|
||||
lockIcon.isVisible = isLocked
|
||||
|
||||
root.setOnClickListener {
|
||||
selectCallBack(card)
|
||||
}
|
||||
root.setOnLongClickListener {
|
||||
editCallBack(card)
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
}
|
||||
|
||||
is WhoIsWatchingAccountAddBinding -> binding.apply {
|
||||
root.setOnClickListener {
|
||||
addAccountCallback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DiffCallback : DiffUtil.ItemCallback<DataStoreHelper.Account>() {
|
||||
override fun areItemsTheSame(
|
||||
oldItem: DataStoreHelper.Account,
|
||||
newItem: DataStoreHelper.Account
|
||||
): Boolean = oldItem.keyIndex == newItem.keyIndex
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: DataStoreHelper.Account,
|
||||
newItem: DataStoreHelper.Account
|
||||
): Boolean = oldItem == newItem
|
||||
}
|
||||
}
|
|
@ -2,63 +2,197 @@ package com.lagradost.cloudstream3.ui.account
|
|||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.databinding.AccountListItemAddBinding
|
||||
import com.lagradost.cloudstream3.databinding.AccountListItemBinding
|
||||
import com.lagradost.cloudstream3.databinding.AccountListItemEditBinding
|
||||
import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountEditDialog
|
||||
import com.lagradost.cloudstream3.ui.result.setImage
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
|
||||
class AccountAdapter(
|
||||
private val accounts: List<DataStoreHelper.Account>,
|
||||
private val onItemClick: (DataStoreHelper.Account) -> Unit
|
||||
private val accountSelectCallback: (DataStoreHelper.Account) -> Unit,
|
||||
private val accountCreateCallback: (DataStoreHelper.Account) -> Unit,
|
||||
private val accountEditCallback: (DataStoreHelper.Account) -> Unit,
|
||||
private val accountDeleteCallback: (DataStoreHelper.Account) -> Unit
|
||||
) : RecyclerView.Adapter<AccountAdapter.AccountViewHolder>() {
|
||||
|
||||
inner class AccountViewHolder(private val binding: AccountListItemBinding) :
|
||||
companion object {
|
||||
const val VIEW_TYPE_SELECT_ACCOUNT = 0
|
||||
const val VIEW_TYPE_ADD_ACCOUNT = 1
|
||||
const val VIEW_TYPE_EDIT_ACCOUNT = 2
|
||||
}
|
||||
|
||||
inner class AccountViewHolder(private val binding: ViewBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
fun bind(account: DataStoreHelper.Account) {
|
||||
val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex
|
||||
fun bind(account: DataStoreHelper.Account?) {
|
||||
when (binding) {
|
||||
is AccountListItemBinding -> binding.apply {
|
||||
if (account == null) return@apply
|
||||
|
||||
binding.accountName.text = account.name
|
||||
binding.accountImage.setImage(account.image)
|
||||
binding.lockIcon.isVisible = account.lockPin != null
|
||||
binding.outline.isVisible = isLastUsedAccount
|
||||
val isTv = isTvSettings() || !root.isInTouchMode
|
||||
|
||||
if (isTvSettings()) {
|
||||
binding.root.isFocusableInTouchMode = true
|
||||
if (isLastUsedAccount) {
|
||||
binding.root.requestFocus()
|
||||
val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex
|
||||
|
||||
accountName.text = account.name
|
||||
accountImage.setImage(account.image)
|
||||
lockIcon.isVisible = account.lockPin != null
|
||||
outline.isVisible = !isTv && isLastUsedAccount
|
||||
|
||||
if (isTv) {
|
||||
// For emulator but this is fine on TV also
|
||||
root.isFocusableInTouchMode = true
|
||||
if (isLastUsedAccount) {
|
||||
root.requestFocus()
|
||||
}
|
||||
|
||||
root.foreground = ContextCompat.getDrawable(
|
||||
root.context,
|
||||
R.drawable.outline_drawable
|
||||
)
|
||||
} else {
|
||||
root.setOnLongClickListener {
|
||||
showAccountEditDialog(
|
||||
context = root.context,
|
||||
account = account,
|
||||
isNewAccount = false,
|
||||
accountEditCallback = { account -> accountEditCallback.invoke(account) },
|
||||
accountDeleteCallback = { account -> accountDeleteCallback.invoke(account) }
|
||||
)
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
root.setOnClickListener {
|
||||
accountSelectCallback.invoke(account)
|
||||
}
|
||||
}
|
||||
|
||||
is AccountListItemEditBinding -> binding.apply {
|
||||
if (account == null) return@apply
|
||||
|
||||
val isTv = isTvSettings() || !root.isInTouchMode
|
||||
|
||||
val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex
|
||||
|
||||
accountName.text = account.name
|
||||
accountImage.setImage(
|
||||
account.image,
|
||||
fadeIn = false,
|
||||
radius = 10
|
||||
)
|
||||
lockIcon.isVisible = account.lockPin != null
|
||||
outline.isVisible = !isTv && isLastUsedAccount
|
||||
|
||||
if (isTv) {
|
||||
// For emulator but this is fine on TV also
|
||||
root.isFocusableInTouchMode = true
|
||||
if (isLastUsedAccount) {
|
||||
root.requestFocus()
|
||||
}
|
||||
|
||||
root.foreground = ContextCompat.getDrawable(
|
||||
root.context,
|
||||
R.drawable.outline_drawable
|
||||
)
|
||||
}
|
||||
|
||||
root.setOnClickListener {
|
||||
showAccountEditDialog(
|
||||
context = root.context,
|
||||
account = account,
|
||||
isNewAccount = false,
|
||||
accountEditCallback = { account -> accountEditCallback.invoke(account) },
|
||||
accountDeleteCallback = { account -> accountDeleteCallback.invoke(account) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is AccountListItemAddBinding -> binding.apply {
|
||||
root.setOnClickListener {
|
||||
val remainingImages =
|
||||
DataStoreHelper.profileImages.toSet() - accounts.filter { it.customImage == null }
|
||||
.mapNotNull { DataStoreHelper.profileImages.getOrNull(it.defaultImageIndex) }.toSet()
|
||||
|
||||
val image =
|
||||
DataStoreHelper.profileImages.indexOf(remainingImages.randomOrNull() ?: DataStoreHelper.profileImages.random())
|
||||
val keyIndex = (accounts.maxOfOrNull { it.keyIndex } ?: 0) + 1
|
||||
|
||||
val accountName = root.context.getString(R.string.account)
|
||||
|
||||
showAccountEditDialog(
|
||||
root.context,
|
||||
DataStoreHelper.Account(
|
||||
keyIndex = keyIndex,
|
||||
name = "$accountName $keyIndex",
|
||||
customImage = null,
|
||||
defaultImageIndex = image
|
||||
),
|
||||
isNewAccount = true,
|
||||
accountEditCallback = { account -> accountCreateCallback.invoke(account) },
|
||||
accountDeleteCallback = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.root.setOnClickListener {
|
||||
onItemClick(account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder {
|
||||
val binding = AccountListItemBinding.inflate(
|
||||
LayoutInflater.from(parent.context), parent, false
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder =
|
||||
AccountViewHolder(
|
||||
binding = when (viewType) {
|
||||
VIEW_TYPE_SELECT_ACCOUNT -> {
|
||||
AccountListItemBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
}
|
||||
VIEW_TYPE_ADD_ACCOUNT -> {
|
||||
AccountListItemAddBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
}
|
||||
VIEW_TYPE_EDIT_ACCOUNT -> {
|
||||
AccountListItemEditBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
)
|
||||
}
|
||||
else -> throw IllegalArgumentException("Invalid view type")
|
||||
}
|
||||
)
|
||||
|
||||
if (isTvSettings()) {
|
||||
val layoutParams = binding.root.layoutParams as RecyclerView.LayoutParams
|
||||
val marginInDp = 5 // Set the margin to 5dp
|
||||
val marginInPixels = (marginInDp * parent.resources.displayMetrics.density).toInt()
|
||||
layoutParams.setMargins(marginInPixels, marginInPixels, marginInPixels, marginInPixels)
|
||||
binding.root.layoutParams = layoutParams
|
||||
}
|
||||
|
||||
return AccountViewHolder(binding)
|
||||
override fun onBindViewHolder(holder: AccountViewHolder, position: Int) {
|
||||
holder.bind(accounts.getOrNull(position))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: AccountViewHolder, position: Int) {
|
||||
holder.bind(accounts[position])
|
||||
var viewType = 0
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
if (viewType != 0 && position != accounts.count()) {
|
||||
return viewType
|
||||
}
|
||||
|
||||
return when (position) {
|
||||
accounts.count() -> VIEW_TYPE_ADD_ACCOUNT
|
||||
else -> VIEW_TYPE_SELECT_ACCOUNT
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return accounts.size
|
||||
return accounts.count() + 1
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
package com.lagradost.cloudstream3.ui.account
|
||||
|
||||
import android.content.Context
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.databinding.LockPinDialogBinding
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||
|
||||
object AccountDialog {
|
||||
// TODO add account creation dialog to allow creating accounts directly from AccountSelectActivity
|
||||
|
||||
fun showPinInputDialog(
|
||||
context: Context,
|
||||
currentPin: String?,
|
||||
editAccount: Boolean,
|
||||
callback: (String?) -> Unit
|
||||
) {
|
||||
fun TextView.visibleWithText(@StringRes textRes: Int) {
|
||||
visibility = View.VISIBLE
|
||||
setText(textRes)
|
||||
}
|
||||
|
||||
fun View.isVisible() = visibility == View.VISIBLE
|
||||
|
||||
val binding = LockPinDialogBinding.inflate(LayoutInflater.from(context))
|
||||
|
||||
val isPinSet = currentPin != null
|
||||
val isNewPin = editAccount && !isPinSet
|
||||
val isEditPin = editAccount && isPinSet
|
||||
|
||||
val titleRes = if (isEditPin) R.string.enter_current_pin else R.string.enter_pin
|
||||
|
||||
val dialog = AlertDialog.Builder(context, R.style.AlertDialogCustom)
|
||||
.setView(binding.root)
|
||||
.setTitle(titleRes)
|
||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
||||
callback.invoke(null)
|
||||
}
|
||||
.setOnCancelListener {
|
||||
callback.invoke(null)
|
||||
}
|
||||
.setOnDismissListener {
|
||||
if (binding.pinEditTextError.isVisible()) {
|
||||
callback.invoke(null)
|
||||
}
|
||||
}
|
||||
.create()
|
||||
|
||||
var isPinValid = false
|
||||
|
||||
binding.pinEditText.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
val enteredPin = s.toString()
|
||||
val isEnteredPinValid = enteredPin.length == 4
|
||||
|
||||
if (isEnteredPinValid) {
|
||||
if (isPinSet) {
|
||||
if (enteredPin != currentPin) {
|
||||
binding.pinEditTextError.visibleWithText(R.string.pin_error_incorrect)
|
||||
binding.pinEditText.text = null
|
||||
isPinValid = false
|
||||
} else {
|
||||
binding.pinEditTextError.visibility = View.GONE
|
||||
isPinValid = true
|
||||
|
||||
callback.invoke(enteredPin)
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
} else {
|
||||
binding.pinEditTextError.visibility = View.GONE
|
||||
isPinValid = true
|
||||
}
|
||||
} else if (isNewPin) {
|
||||
binding.pinEditTextError.visibleWithText(R.string.pin_error_length)
|
||||
isPinValid = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {}
|
||||
})
|
||||
|
||||
// Detect IME_ACTION_DONE
|
||||
binding.pinEditText.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE && isPinValid) {
|
||||
val enteredPin = binding.pinEditText.text.toString()
|
||||
callback.invoke(enteredPin)
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
// We don't want to accidentally have the dialog dismiss when clicking outside of it.
|
||||
// That is what the cancel button is for.
|
||||
dialog.setCanceledOnTouchOutside(false)
|
||||
|
||||
dialog.show()
|
||||
|
||||
// Auto focus on PIN input and show keyboard
|
||||
binding.pinEditText.requestFocus()
|
||||
binding.pinEditText.postDelayed({
|
||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.showSoftInput(binding.pinEditText, InputMethodManager.SHOW_IMPLICIT)
|
||||
}, 200)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,356 @@
|
|||
package com.lagradost.cloudstream3.ui.account
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.text.Editable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
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.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity
|
||||
import com.lagradost.cloudstream3.MainActivity
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.databinding.AccountEditDialogBinding
|
||||
import com.lagradost.cloudstream3.databinding.AccountSelectLinearBinding
|
||||
import com.lagradost.cloudstream3.databinding.LockPinDialogBinding
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.observe
|
||||
import com.lagradost.cloudstream3.ui.result.setImage
|
||||
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDefaultAccount
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.showInputMethod
|
||||
|
||||
object AccountHelper {
|
||||
fun showAccountEditDialog(
|
||||
context: Context,
|
||||
account: DataStoreHelper.Account,
|
||||
isNewAccount: Boolean,
|
||||
accountEditCallback: (DataStoreHelper.Account) -> Unit,
|
||||
accountDeleteCallback: (DataStoreHelper.Account) -> Unit
|
||||
) {
|
||||
val binding = AccountEditDialogBinding.inflate(LayoutInflater.from(context), null, false)
|
||||
val builder = AlertDialog.Builder(context, R.style.AlertDialogCustom)
|
||||
.setView(binding.root)
|
||||
|
||||
var currentEditAccount = account
|
||||
val dialog = builder.show()
|
||||
|
||||
if (!isNewAccount) binding.title.setText(R.string.edit_account)
|
||||
|
||||
// Set up the dialog content
|
||||
binding.accountName.text = Editable.Factory.getInstance()?.newEditable(account.name)
|
||||
binding.accountName.doOnTextChanged { text, _, _, _ ->
|
||||
currentEditAccount = currentEditAccount.copy(name = text?.toString() ?: "")
|
||||
}
|
||||
|
||||
binding.deleteBtt.isGone = isNewAccount
|
||||
binding.deleteBtt.setOnClickListener {
|
||||
val dialogClickListener = DialogInterface.OnClickListener { _, which ->
|
||||
when (which) {
|
||||
DialogInterface.BUTTON_POSITIVE -> {
|
||||
accountDeleteCallback.invoke(account)
|
||||
dialog?.dismissSafe()
|
||||
}
|
||||
|
||||
DialogInterface.BUTTON_NEGATIVE -> {
|
||||
dialog?.dismissSafe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
AlertDialog.Builder(context).setTitle(R.string.delete).setMessage(
|
||||
context.getString(R.string.delete_message).format(
|
||||
currentEditAccount.name
|
||||
)
|
||||
)
|
||||
.setPositiveButton(R.string.delete, dialogClickListener)
|
||||
.setNegativeButton(R.string.cancel, dialogClickListener)
|
||||
.show().setDefaultFocus()
|
||||
} catch (t: Throwable) {
|
||||
logError(t)
|
||||
}
|
||||
}
|
||||
|
||||
binding.cancelBtt.setOnClickListener {
|
||||
dialog?.dismissSafe()
|
||||
}
|
||||
|
||||
// Handle the profile picture and its interactions
|
||||
binding.accountImage.setImage(account.image)
|
||||
binding.accountImage.setOnClickListener {
|
||||
// Roll the image forwards once
|
||||
currentEditAccount =
|
||||
currentEditAccount.copy(defaultImageIndex = (currentEditAccount.defaultImageIndex + 1) % DataStoreHelper.profileImages.size)
|
||||
binding.accountImage.setImage(currentEditAccount.image)
|
||||
}
|
||||
|
||||
// Handle applying changes
|
||||
binding.applyBtt.setOnClickListener {
|
||||
if (currentEditAccount.lockPin != null) {
|
||||
// Ask for the current PIN
|
||||
showPinInputDialog(context, currentEditAccount.lockPin, false) { pin ->
|
||||
if (pin == null) return@showPinInputDialog
|
||||
// PIN is correct, proceed to update the account
|
||||
accountEditCallback.invoke(currentEditAccount)
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
} else {
|
||||
// No lock PIN set, proceed to update the account
|
||||
accountEditCallback.invoke(currentEditAccount)
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
}
|
||||
|
||||
// Handle setting or changing the PIN
|
||||
if (currentEditAccount.keyIndex == getDefaultAccount(context).keyIndex) {
|
||||
binding.lockProfileCheckbox.isVisible = false
|
||||
if (currentEditAccount.lockPin != null) {
|
||||
currentEditAccount = currentEditAccount.copy(lockPin = null)
|
||||
}
|
||||
}
|
||||
|
||||
var canSetPin = true
|
||||
|
||||
binding.lockProfileCheckbox.isChecked = currentEditAccount.lockPin != null
|
||||
|
||||
binding.lockProfileCheckbox.setOnCheckedChangeListener { _, isChecked ->
|
||||
if (isChecked) {
|
||||
if (canSetPin) {
|
||||
showPinInputDialog(context, null, true) { pin ->
|
||||
if (pin == null) {
|
||||
binding.lockProfileCheckbox.isChecked = false
|
||||
return@showPinInputDialog
|
||||
}
|
||||
|
||||
currentEditAccount = currentEditAccount.copy(lockPin = pin)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (currentEditAccount.lockPin != null) {
|
||||
// Ask for the current PIN
|
||||
showPinInputDialog(context, currentEditAccount.lockPin, true) { pin ->
|
||||
if (pin == null || pin != currentEditAccount.lockPin) {
|
||||
canSetPin = false
|
||||
binding.lockProfileCheckbox.isChecked = true
|
||||
} else {
|
||||
currentEditAccount = currentEditAccount.copy(lockPin = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canSetPin = true
|
||||
}
|
||||
|
||||
fun showPinInputDialog(
|
||||
context: Context,
|
||||
currentPin: String?,
|
||||
editAccount: Boolean,
|
||||
forStartup: Boolean = false,
|
||||
errorText: String? = null,
|
||||
callback: (String?) -> Unit
|
||||
) {
|
||||
fun TextView.visibleWithText(@StringRes textRes: Int) {
|
||||
isVisible = true
|
||||
setText(textRes)
|
||||
}
|
||||
|
||||
fun TextView.visibleWithText(text: String?) {
|
||||
isVisible = true
|
||||
setText(text)
|
||||
}
|
||||
|
||||
val binding = LockPinDialogBinding.inflate(LayoutInflater.from(context))
|
||||
|
||||
val isPinSet = currentPin != null
|
||||
val isNewPin = editAccount && !isPinSet
|
||||
val isEditPin = editAccount && isPinSet
|
||||
|
||||
val titleRes = if (isEditPin) R.string.enter_current_pin else R.string.enter_pin
|
||||
|
||||
var isPinValid = false
|
||||
|
||||
val builder = AlertDialog.Builder(context, R.style.AlertDialogCustom)
|
||||
.setView(binding.root)
|
||||
.setTitle(titleRes)
|
||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
||||
callback.invoke(null)
|
||||
}
|
||||
.setOnCancelListener {
|
||||
callback.invoke(null)
|
||||
}
|
||||
.setOnDismissListener {
|
||||
if (!isPinValid) {
|
||||
callback.invoke(null)
|
||||
}
|
||||
}
|
||||
|
||||
if (forStartup) {
|
||||
val currentAccount = DataStoreHelper.accounts.firstOrNull {
|
||||
it.keyIndex == DataStoreHelper.selectedKeyIndex
|
||||
}
|
||||
|
||||
builder.setTitle(context.getString(R.string.enter_pin_with_name, currentAccount?.name))
|
||||
builder.setOnDismissListener {
|
||||
if (!isPinValid) {
|
||||
context.getActivity()?.finish()
|
||||
}
|
||||
}
|
||||
// So that if they don't know the PIN for the current account,
|
||||
// they don't get completely locked out
|
||||
builder.setNeutralButton(R.string.use_default_account) { _, _ ->
|
||||
val activity = context.getActivity()
|
||||
if (activity is AccountSelectActivity) {
|
||||
isPinValid = true
|
||||
activity.viewModel.handleAccountSelect(getDefaultAccount(context), activity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isNewPin) {
|
||||
if (errorText != null) binding.pinEditTextError.visibleWithText(errorText)
|
||||
builder.setPositiveButton(R.string.setup_done) { _, _ ->
|
||||
if (!isPinValid) {
|
||||
// If the done button is pressed and there is an error,
|
||||
// ask again, and mention the error that caused this.
|
||||
showPinInputDialog(
|
||||
context = binding.root.context,
|
||||
currentPin = null,
|
||||
editAccount = true,
|
||||
errorText = binding.pinEditTextError.text.toString(),
|
||||
callback = callback
|
||||
)
|
||||
} else {
|
||||
val enteredPin = binding.pinEditText.text.toString()
|
||||
callback.invoke(enteredPin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dialog = builder.create()
|
||||
|
||||
binding.pinEditText.doOnTextChanged { text, _, _, _ ->
|
||||
val enteredPin = text.toString()
|
||||
val isEnteredPinValid = enteredPin.length == 4
|
||||
|
||||
if (isEnteredPinValid) {
|
||||
if (isPinSet) {
|
||||
if (enteredPin != currentPin) {
|
||||
binding.pinEditTextError.visibleWithText(R.string.pin_error_incorrect)
|
||||
binding.pinEditText.text = null
|
||||
isPinValid = false
|
||||
} else {
|
||||
binding.pinEditTextError.isVisible = false
|
||||
isPinValid = true
|
||||
|
||||
callback.invoke(enteredPin)
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
} else {
|
||||
binding.pinEditTextError.isVisible = false
|
||||
isPinValid = true
|
||||
}
|
||||
} else if (isNewPin) {
|
||||
binding.pinEditTextError.visibleWithText(R.string.pin_error_length)
|
||||
isPinValid = false
|
||||
}
|
||||
}
|
||||
|
||||
// Detect IME_ACTION_DONE
|
||||
binding.pinEditText.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE && isPinValid) {
|
||||
val enteredPin = binding.pinEditText.text.toString()
|
||||
callback.invoke(enteredPin)
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
// We don't want to accidentally have the dialog dismiss when clicking outside of it.
|
||||
// That is what the cancel button is for.
|
||||
dialog.setCanceledOnTouchOutside(false)
|
||||
|
||||
dialog.show()
|
||||
|
||||
// Auto focus on PIN input and show keyboard
|
||||
binding.pinEditText.requestFocus()
|
||||
binding.pinEditText.postDelayed({
|
||||
showInputMethod(binding.pinEditText)
|
||||
}, 200)
|
||||
}
|
||||
|
||||
fun Activity?.showAccountSelectLinear() {
|
||||
val activity = this as? MainActivity ?: return
|
||||
val viewModel = ViewModelProvider(activity)[AccountViewModel::class.java]
|
||||
|
||||
val binding: AccountSelectLinearBinding = AccountSelectLinearBinding.inflate(
|
||||
LayoutInflater.from(activity)
|
||||
)
|
||||
|
||||
val builder = BottomSheetDialog(activity)
|
||||
builder.setContentView(binding.root)
|
||||
builder.show()
|
||||
|
||||
binding.manageAccountsButton.setOnClickListener {
|
||||
val accountSelectIntent = Intent(activity, AccountSelectActivity::class.java)
|
||||
accountSelectIntent.putExtra("isEditingFromMainActivity", true)
|
||||
activity.startActivity(accountSelectIntent)
|
||||
builder.dismissSafe()
|
||||
}
|
||||
|
||||
val recyclerView: RecyclerView = binding.accountRecyclerView
|
||||
|
||||
val itemSize = recyclerView.resources.getDimensionPixelSize(
|
||||
R.dimen.account_select_linear_item_size
|
||||
)
|
||||
|
||||
recyclerView.addItemDecoration(AccountSelectLinearItemDecoration(itemSize))
|
||||
|
||||
recyclerView.setLinearListLayout(isHorizontal = true)
|
||||
|
||||
val currentAccount = DataStoreHelper.accounts.firstOrNull {
|
||||
it.keyIndex == DataStoreHelper.selectedKeyIndex
|
||||
} ?: getDefaultAccount(activity)
|
||||
|
||||
// We want to make sure the accounts are up-to-date
|
||||
viewModel.handleAccountSelect(
|
||||
currentAccount,
|
||||
activity,
|
||||
reloadForActivity = true
|
||||
)
|
||||
|
||||
activity.observe(viewModel.accounts) { liveAccounts ->
|
||||
recyclerView.adapter = AccountAdapter(
|
||||
liveAccounts,
|
||||
accountSelectCallback = { account ->
|
||||
viewModel.handleAccountSelect(account, activity)
|
||||
builder.dismissSafe()
|
||||
},
|
||||
accountCreateCallback = { viewModel.handleAccountUpdate(it, activity) },
|
||||
accountEditCallback = { viewModel.handleAccountUpdate(it, activity) },
|
||||
accountDeleteCallback = { viewModel.handleAccountDelete(it, activity) }
|
||||
)
|
||||
|
||||
activity.observe(viewModel.selectedKeyIndex) { selectedKeyIndex ->
|
||||
// Scroll to current account (which is focused by default)
|
||||
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
|
||||
layoutManager.scrollToPositionWithOffset(selectedKeyIndex, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,87 +1,163 @@
|
|||
package com.lagradost.cloudstream3.ui.account
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.lagradost.cloudstream3.CommonActivity
|
||||
import com.lagradost.cloudstream3.CommonActivity.loadThemes
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.MainActivity
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.databinding.ActivityAccountSelectBinding
|
||||
import com.lagradost.cloudstream3.databinding.ActivityAccountSelectTvBinding
|
||||
import com.lagradost.cloudstream3.ui.account.AccountDialog.showPinInputDialog
|
||||
import com.lagradost.cloudstream3.mvvm.observe
|
||||
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
||||
import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_EDIT_ACCOUNT
|
||||
import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_SELECT_ACCOUNT
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAccounts
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.accounts
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.selectedKeyIndex
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setAccount
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||
|
||||
class AccountSelectActivity : AppCompatActivity() {
|
||||
|
||||
lateinit var viewModel: AccountViewModel
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val accounts = getAccounts(this@AccountSelectActivity)
|
||||
|
||||
// Don't show account selection if there is only
|
||||
// one account that exists
|
||||
if (accounts.count() <= 1) {
|
||||
navigateToMainActivity()
|
||||
return
|
||||
}
|
||||
|
||||
CommonActivity.init(this)
|
||||
loadThemes(this)
|
||||
|
||||
window.navigationBarColor = colorFromAttribute(R.attr.primaryBlackBackground)
|
||||
|
||||
val binding = if (isTvSettings()) {
|
||||
ActivityAccountSelectTvBinding.inflate(layoutInflater)
|
||||
} else ActivityAccountSelectBinding.inflate(layoutInflater)
|
||||
// Are we editing and coming from MainActivity?
|
||||
val isEditingFromMainActivity = intent.getBooleanExtra(
|
||||
"isEditingFromMainActivity",
|
||||
false
|
||||
)
|
||||
|
||||
setContentView(binding.root)
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val skipStartup = settingsManager.getBoolean(
|
||||
getString(R.string.skip_startup_account_select_key),
|
||||
false
|
||||
) || accounts.count() <= 1
|
||||
|
||||
val recyclerView: RecyclerView = binding.root.findViewById(R.id.account_recycler_view)
|
||||
viewModel = ViewModelProvider(this)[AccountViewModel::class.java]
|
||||
|
||||
// Don't show account selection if there is only
|
||||
// one account that exists
|
||||
if (!isEditingFromMainActivity && skipStartup) {
|
||||
val currentAccount = accounts.firstOrNull { it.keyIndex == selectedKeyIndex }
|
||||
if (currentAccount?.lockPin != null) {
|
||||
CommonActivity.init(this)
|
||||
viewModel.handleAccountSelect(currentAccount, this, true)
|
||||
observe(viewModel.isAllowedLogin) { isAllowedLogin ->
|
||||
if (isAllowedLogin) {
|
||||
// We are allowed to continue to MainActivity
|
||||
navigateToMainActivity()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (accounts.count() > 1) {
|
||||
showToast(this, getString(
|
||||
R.string.logged_account,
|
||||
currentAccount?.name
|
||||
))
|
||||
}
|
||||
|
||||
val adapter = AccountAdapter(accounts) { selectedAccount ->
|
||||
// Handle the selected account
|
||||
onAccountSelected(selectedAccount)
|
||||
}
|
||||
recyclerView.adapter = adapter
|
||||
|
||||
recyclerView.layoutManager = if (isTvSettings()) {
|
||||
LinearLayoutManager(this)
|
||||
} else GridLayoutManager(this, 2)
|
||||
}
|
||||
|
||||
private fun onAccountSelected(selectedAccount: DataStoreHelper.Account) {
|
||||
if (selectedAccount.lockPin != null) {
|
||||
// The selected account has a PIN set, prompt the user to enter the PIN
|
||||
showPinInputDialog(this@AccountSelectActivity, selectedAccount.lockPin, false) { pin ->
|
||||
if (pin == null) return@showPinInputDialog
|
||||
// Pin is correct, proceed to main activity
|
||||
setAccount(selectedAccount)
|
||||
navigateToMainActivity()
|
||||
}
|
||||
} else {
|
||||
// No PIN set for the selected account, proceed to main activity
|
||||
setAccount(selectedAccount)
|
||||
navigateToMainActivity()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAccount(account: DataStoreHelper.Account) {
|
||||
// Don't reload if it is the same account
|
||||
if (DataStoreHelper.selectedKeyIndex == account.keyIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
DataStoreHelper.selectedKeyIndex = account.keyIndex
|
||||
CommonActivity.init(this)
|
||||
|
||||
MainActivity.bookmarksUpdatedEvent(true)
|
||||
MainActivity.reloadHomeEvent(true)
|
||||
val binding = ActivityAccountSelectBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
val recyclerView: AutofitRecyclerView = binding.accountRecyclerView
|
||||
|
||||
observe(viewModel.accounts) { liveAccounts ->
|
||||
val adapter = AccountAdapter(
|
||||
liveAccounts,
|
||||
// Handle the selected account
|
||||
accountSelectCallback = {
|
||||
viewModel.handleAccountSelect(it, this)
|
||||
observe(viewModel.isAllowedLogin) { isAllowedLogin ->
|
||||
if (isAllowedLogin) {
|
||||
// We are allowed to continue to MainActivity
|
||||
navigateToMainActivity()
|
||||
}
|
||||
}
|
||||
},
|
||||
accountCreateCallback = { viewModel.handleAccountUpdate(it, this) },
|
||||
accountEditCallback = {
|
||||
viewModel.handleAccountUpdate(it, this)
|
||||
|
||||
// We came from MainActivity, return there
|
||||
// and switch to the edited account
|
||||
if (isEditingFromMainActivity) {
|
||||
setAccount(it)
|
||||
navigateToMainActivity()
|
||||
}
|
||||
},
|
||||
accountDeleteCallback = { viewModel.handleAccountDelete(it,this) }
|
||||
)
|
||||
|
||||
recyclerView.adapter = adapter
|
||||
|
||||
if (isTvSettings()) {
|
||||
binding.editAccountButton.setBackgroundResource(
|
||||
R.drawable.player_button_tv_attr_no_bg
|
||||
)
|
||||
}
|
||||
|
||||
observe(viewModel.selectedKeyIndex) { selectedKeyIndex ->
|
||||
// Scroll to current account (which is focused by default)
|
||||
val layoutManager = recyclerView.layoutManager as GridLayoutManager
|
||||
layoutManager.scrollToPositionWithOffset(selectedKeyIndex, 0)
|
||||
}
|
||||
|
||||
observe(viewModel.isEditing) { isEditing ->
|
||||
if (isEditing) {
|
||||
binding.editAccountButton.setImageResource(R.drawable.ic_baseline_close_24)
|
||||
binding.title.setText(R.string.manage_accounts)
|
||||
adapter.viewType = VIEW_TYPE_EDIT_ACCOUNT
|
||||
} else {
|
||||
binding.editAccountButton.setImageResource(R.drawable.ic_baseline_edit_24)
|
||||
binding.title.setText(R.string.select_an_account)
|
||||
adapter.viewType = VIEW_TYPE_SELECT_ACCOUNT
|
||||
}
|
||||
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
if (isEditingFromMainActivity) {
|
||||
viewModel.setIsEditing(true)
|
||||
}
|
||||
|
||||
binding.editAccountButton.setOnClickListener {
|
||||
// We came from MainActivity, return there
|
||||
// and resume its state
|
||||
if (isEditingFromMainActivity) {
|
||||
navigateToMainActivity()
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
viewModel.toggleIsEditing()
|
||||
}
|
||||
|
||||
if (isTvSettings()) {
|
||||
recyclerView.spanCount = if (liveAccounts.count() + 1 <= 6) {
|
||||
liveAccounts.count() + 1
|
||||
} else 6
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToMainActivity() {
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.lagradost.cloudstream3.ui.account
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class AccountSelectLinearItemDecoration(private val size: Int) : RecyclerView.ItemDecoration() {
|
||||
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||
val layoutParams = view.layoutParams as RecyclerView.LayoutParams
|
||||
layoutParams.width = size
|
||||
layoutParams.height = size
|
||||
view.layoutParams = layoutParams
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package com.lagradost.cloudstream3.ui.account
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.context
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys
|
||||
import com.lagradost.cloudstream3.ui.account.AccountHelper.showPinInputDialog
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAccounts
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDefaultAccount
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setAccount
|
||||
|
||||
class AccountViewModel : ViewModel() {
|
||||
private fun getAllAccounts(): List<DataStoreHelper.Account> {
|
||||
return context?.let { getAccounts(it) } ?: DataStoreHelper.accounts.toList()
|
||||
}
|
||||
|
||||
private val _accounts: MutableLiveData<List<DataStoreHelper.Account>> = MutableLiveData(getAllAccounts())
|
||||
val accounts: LiveData<List<DataStoreHelper.Account>> = _accounts
|
||||
|
||||
private val _isEditing = MutableLiveData(false)
|
||||
val isEditing: LiveData<Boolean> = _isEditing
|
||||
|
||||
private val _isAllowedLogin = MutableLiveData(false)
|
||||
val isAllowedLogin: LiveData<Boolean> = _isAllowedLogin
|
||||
|
||||
private val _selectedKeyIndex = MutableLiveData(
|
||||
getAllAccounts().indexOfFirst {
|
||||
it.keyIndex == DataStoreHelper.selectedKeyIndex
|
||||
}
|
||||
)
|
||||
val selectedKeyIndex: LiveData<Int> = _selectedKeyIndex
|
||||
|
||||
fun setIsEditing(value: Boolean) {
|
||||
_isEditing.postValue(value)
|
||||
}
|
||||
|
||||
fun toggleIsEditing() {
|
||||
_isEditing.postValue(!(_isEditing.value ?: false))
|
||||
}
|
||||
|
||||
fun handleAccountUpdate(
|
||||
account: DataStoreHelper.Account,
|
||||
context: Context
|
||||
) {
|
||||
val currentAccounts = getAccounts(context).toMutableList()
|
||||
|
||||
val overrideIndex = currentAccounts.indexOfFirst { it.keyIndex == account.keyIndex }
|
||||
|
||||
if (overrideIndex != -1) {
|
||||
currentAccounts[overrideIndex] = account
|
||||
} else currentAccounts.add(account)
|
||||
|
||||
val currentHomePage = DataStoreHelper.currentHomePage
|
||||
|
||||
setAccount(account)
|
||||
|
||||
DataStoreHelper.currentHomePage = currentHomePage
|
||||
DataStoreHelper.accounts = currentAccounts.toTypedArray()
|
||||
|
||||
_accounts.postValue(getAccounts(context))
|
||||
_selectedKeyIndex.postValue(getAccounts(context).indexOf(account))
|
||||
}
|
||||
|
||||
fun handleAccountDelete(
|
||||
account: DataStoreHelper.Account,
|
||||
context: Context
|
||||
) {
|
||||
removeKeys(account.keyIndex.toString())
|
||||
|
||||
val currentAccounts = getAccounts(context).toMutableList()
|
||||
|
||||
currentAccounts.removeIf { it.keyIndex == account.keyIndex }
|
||||
|
||||
DataStoreHelper.accounts = currentAccounts.toTypedArray()
|
||||
|
||||
if (account.keyIndex == DataStoreHelper.selectedKeyIndex) {
|
||||
setAccount(getDefaultAccount(context))
|
||||
}
|
||||
|
||||
_accounts.postValue(getAccounts(context))
|
||||
_selectedKeyIndex.postValue(getAllAccounts().indexOfFirst {
|
||||
it.keyIndex == DataStoreHelper.selectedKeyIndex
|
||||
})
|
||||
}
|
||||
|
||||
fun handleAccountSelect(
|
||||
account: DataStoreHelper.Account,
|
||||
context: Context,
|
||||
forStartup: Boolean = false,
|
||||
reloadForActivity: Boolean = false
|
||||
) {
|
||||
if (reloadForActivity) {
|
||||
_accounts.postValue(getAccounts(context))
|
||||
_selectedKeyIndex.postValue(getAccounts(context).indexOf(account))
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the selected account has a lock PIN set
|
||||
if (account.lockPin != null) {
|
||||
// The selected account has a PIN set, prompt the user to enter the PIN
|
||||
showPinInputDialog(
|
||||
context,
|
||||
account.lockPin,
|
||||
false,
|
||||
forStartup
|
||||
) { pin ->
|
||||
if (pin == null) return@showPinInputDialog
|
||||
// Pin is correct, proceed
|
||||
_isAllowedLogin.postValue(true)
|
||||
_selectedKeyIndex.postValue(getAccounts(context).indexOf(account))
|
||||
setAccount(account)
|
||||
}
|
||||
} else {
|
||||
// No PIN set for the selected account, proceed
|
||||
_isAllowedLogin.postValue(true)
|
||||
_selectedKeyIndex.postValue(getAccounts(context).indexOf(account))
|
||||
setAccount(account)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -60,7 +60,7 @@ class DownloadChildFragment : Fragment() {
|
|||
}
|
||||
}.sortedBy { it.data.episode + (it.data.season ?: 0) * 100000 }
|
||||
if (eps.isEmpty()) {
|
||||
activity?.onBackPressed()
|
||||
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||
return@main
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ class DownloadChildFragment : Fragment() {
|
|||
val folder = arguments?.getString("folder")
|
||||
val name = arguments?.getString("name")
|
||||
if (folder == null) {
|
||||
activity?.onBackPressed() // TODO FIX
|
||||
activity?.onBackPressedDispatcher?.onBackPressed() // TODO FIX
|
||||
return
|
||||
}
|
||||
fixPaddingStatusbar(binding?.downloadChildRoot)
|
||||
|
@ -87,7 +87,7 @@ class DownloadChildFragment : Fragment() {
|
|||
title = name
|
||||
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
|
||||
setNavigationOnClickListener {
|
||||
activity?.onBackPressed()
|
||||
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ import com.lagradost.cloudstream3.mvvm.observe
|
|||
import com.lagradost.cloudstream3.mvvm.observeNullable
|
||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
|
||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
|
||||
import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountSelectLinear
|
||||
import com.lagradost.cloudstream3.ui.result.txt
|
||||
import com.lagradost.cloudstream3.ui.search.*
|
||||
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
|
||||
|
@ -495,9 +496,10 @@ class HomeFragment : Fragment() {
|
|||
//homeChangeApiLoading.setOnClickListener(apiChangeClickListener)
|
||||
homeApiFab.setOnClickListener(apiChangeClickListener)
|
||||
homeChangeApi.setOnClickListener(apiChangeClickListener)
|
||||
homeSwitchAccount.setOnClickListener { v ->
|
||||
DataStoreHelper.showWhoIsWatching(v?.context ?: return@setOnClickListener)
|
||||
homeSwitchAccount.setOnClickListener {
|
||||
activity?.showAccountSelectLinear()
|
||||
}
|
||||
|
||||
homeRandom.setOnClickListener {
|
||||
if (listHomepageItems.isNotEmpty()) {
|
||||
activity.loadSearchResult(listHomepageItems.random())
|
||||
|
|
|
@ -27,6 +27,7 @@ import com.lagradost.cloudstream3.mvvm.Resource
|
|||
import com.lagradost.cloudstream3.mvvm.debugException
|
||||
import com.lagradost.cloudstream3.mvvm.observe
|
||||
import com.lagradost.cloudstream3.ui.WatchType
|
||||
import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountSelectLinear
|
||||
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.selectHomepage
|
||||
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
||||
import com.lagradost.cloudstream3.ui.result.ResultViewModel2
|
||||
|
@ -477,8 +478,8 @@ class HomeParentItemAdapterPreview(
|
|||
}
|
||||
}
|
||||
|
||||
homeAccount?.setOnClickListener { v ->
|
||||
DataStoreHelper.showWhoIsWatching(v?.context ?: return@setOnClickListener)
|
||||
homeAccount?.setOnClickListener {
|
||||
activity?.showAccountSelectLinear()
|
||||
}
|
||||
|
||||
(binding as? FragmentHomeHeadTvBinding)?.apply {
|
||||
|
|
|
@ -27,6 +27,7 @@ import androidx.core.view.isVisible
|
|||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.lagradost.cloudstream3.APIHolder
|
||||
|
@ -140,6 +141,10 @@ class LibraryFragment : Fragment() {
|
|||
|
||||
binding?.libraryRoot?.findViewById<TextView>(R.id.search_src_text)?.apply {
|
||||
tag = "tv_no_focus_tag"
|
||||
//Expand the Appbar when search bar is focused, fixing scroll up issue
|
||||
setOnFocusChangeListener { _, _ ->
|
||||
binding?.searchBar?.setExpanded(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the color for the search exit icon to the correct theme text color
|
||||
|
@ -346,6 +351,7 @@ class LibraryFragment : Fragment() {
|
|||
binding?.apply {
|
||||
viewpager.offscreenPageLimit = 2
|
||||
viewpager.reduceDragSensitivity()
|
||||
searchBar.setExpanded(true)
|
||||
}
|
||||
|
||||
val startLoading = Runnable {
|
||||
|
@ -445,6 +451,10 @@ class LibraryFragment : Fragment() {
|
|||
val distance = abs(position - currentItem)
|
||||
hideViewpager(distance)
|
||||
}
|
||||
//Expand the appBar on tab focus
|
||||
tab.view.setOnFocusChangeListener { view, b ->
|
||||
binding?.searchBar?.setExpanded(true)
|
||||
}
|
||||
}.attach()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,11 +120,11 @@ class LibraryViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
init {
|
||||
MainActivity.reloadHomeEvent += ::reloadPages
|
||||
MainActivity.reloadLibraryEvent += ::reloadPages
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
MainActivity.reloadHomeEvent -= ::reloadPages
|
||||
MainActivity.reloadLibraryEvent -= ::reloadPages
|
||||
super.onCleared()
|
||||
}
|
||||
}
|
|
@ -1,14 +1,18 @@
|
|||
package com.lagradost.cloudstream3.ui.library
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.doOnAttach
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.OnFlingListener
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.databinding.LibraryViewpagerPageBinding
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||
|
||||
class ViewpagerAdapter(
|
||||
|
@ -67,6 +71,17 @@ class ViewpagerAdapter(
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||
val diff = scrollY - oldScrollY
|
||||
|
||||
//Expand the top Appbar based on scroll direction up/down, simulate phone behavior
|
||||
if (SettingsFragment.isTvSettings()) {
|
||||
binding.root.rootView.findViewById<AppBarLayout>(R.id.search_bar)
|
||||
.apply {
|
||||
if (diff <= 0)
|
||||
setExpanded(true)
|
||||
else
|
||||
setExpanded(false)
|
||||
}
|
||||
}
|
||||
if (diff == 0) return@setOnScrollChangeListener
|
||||
|
||||
scrollCallback.invoke(diff > 0)
|
||||
|
@ -80,8 +95,6 @@ class ViewpagerAdapter(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1014,7 +1014,8 @@ class CS3IPlayer : IPlayer {
|
|||
format.id!!,
|
||||
SubtitleOrigin.EMBEDDED_IN_VIDEO,
|
||||
format.sampleMimeType ?: MimeTypes.APPLICATION_SUBRIP,
|
||||
emptyMap()
|
||||
emptyMap(),
|
||||
format.language
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1254,7 +1255,7 @@ class CS3IPlayer : IPlayer {
|
|||
.setMimeType(sub.mimeType)
|
||||
.setLanguage("_${sub.name}")
|
||||
.setId(sub.getId())
|
||||
.setSelectionFlags(SELECTION_FLAG_DEFAULT)
|
||||
.setSelectionFlags(0)
|
||||
.build()
|
||||
when (sub.origin) {
|
||||
SubtitleOrigin.DOWNLOADED_FILE -> {
|
||||
|
|
|
@ -100,7 +100,8 @@ class DownloadFileGenerator(
|
|||
uri.toString(),
|
||||
SubtitleOrigin.DOWNLOADED_FILE,
|
||||
name.toSubtitleMimeType(),
|
||||
emptyMap()
|
||||
emptyMap(),
|
||||
null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.net.Uri
|
|||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.lagradost.cloudstream3.CommonActivity
|
||||
import com.lagradost.cloudstream3.R
|
||||
|
@ -34,10 +35,6 @@ class DownloadedPlayerActivity : AppCompatActivity() {
|
|||
CommonActivity.onUserLeaveHint(this)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun playLink(url: String) {
|
||||
this.navigate(
|
||||
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
|
||||
|
@ -109,6 +106,15 @@ class DownloadedPlayerActivity : AppCompatActivity() {
|
|||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
onBackPressedDispatcher.addCallback(
|
||||
this,
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
|
|
@ -23,6 +23,7 @@ import androidx.media3.common.Format.NO_VALUE
|
|||
import androidx.media3.common.MimeTypes
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.databinding.DialogOnlineSubtitlesBinding
|
||||
import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding
|
||||
|
@ -39,6 +40,7 @@ import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
|||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
|
||||
import com.lagradost.cloudstream3.ui.result.*
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY
|
||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
|
@ -102,10 +104,33 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
binding?.playerLoadingOverlay?.isVisible = true
|
||||
}
|
||||
|
||||
private fun setSubtitles(sub: SubtitleData?): Boolean {
|
||||
currentSelectedSubtitles = sub
|
||||
//Log.i(TAG, "setSubtitles = $sub")
|
||||
return player.setPreferredSubtitles(sub)
|
||||
private fun setSubtitles(subtitle: SubtitleData?): Boolean {
|
||||
// If subtitle is changed -> Save the language
|
||||
if (subtitle != currentSelectedSubtitles) {
|
||||
val subtitleLanguage639 = if (subtitle == null) {
|
||||
// "" is No Subtitles
|
||||
""
|
||||
} else if (subtitle.languageCode != null) {
|
||||
// Could be "English 4" which is why it is trimmed.
|
||||
val trimmedLanguage = subtitle.languageCode.replace(Regex("\\d"), "").trim()
|
||||
|
||||
languages.firstOrNull { language ->
|
||||
language.languageName.equals(trimmedLanguage, ignoreCase = true) ||
|
||||
language.ISO_639_1 == subtitle.languageCode
|
||||
}?.ISO_639_1
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (subtitleLanguage639 != null) {
|
||||
setKey(SUBTITLE_AUTO_SELECT_KEY, subtitleLanguage639)
|
||||
preferredAutoSelectSubtitles = subtitleLanguage639
|
||||
}
|
||||
}
|
||||
|
||||
currentSelectedSubtitles = subtitle
|
||||
//Log.i(TAG, "setSubtitles = $subtitle")
|
||||
return player.setPreferredSubtitles(subtitle)
|
||||
}
|
||||
|
||||
override fun embeddedSubtitlesFetched(subtitles: List<SubtitleData>) {
|
||||
|
@ -450,7 +475,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
url = url,
|
||||
origin = SubtitleOrigin.URL,
|
||||
mimeType = url.toSubtitleMimeType(),
|
||||
headers = currentSubtitle.headers
|
||||
headers = currentSubtitle.headers,
|
||||
currentSubtitle.lang
|
||||
)
|
||||
runOnMainThread {
|
||||
addAndSelectSubtitles(subtitle)
|
||||
|
@ -538,7 +564,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
uri.toString(),
|
||||
SubtitleOrigin.DOWNLOADED_FILE,
|
||||
name.toSubtitleMimeType(),
|
||||
emptyMap()
|
||||
emptyMap(),
|
||||
null
|
||||
)
|
||||
|
||||
addAndSelectSubtitles(subtitleData)
|
||||
|
@ -949,7 +976,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
|
||||
var maxEpisodeSet: Int? = null
|
||||
var hasRequestedStamps: Boolean = false
|
||||
override fun playerPositionChanged(position: Long, duration : Long) {
|
||||
override fun playerPositionChanged(position: Long, duration: Long) {
|
||||
// Don't save livestream data
|
||||
if ((currentMeta as? ResultEpisode)?.tvType?.isLiveStream() == true) return
|
||||
|
||||
|
@ -1212,7 +1239,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun playerDimensionsLoaded(width: Int, height : Int) {
|
||||
override fun playerDimensionsLoaded(width: Int, height: Int) {
|
||||
setPlayerDimen(width to height)
|
||||
}
|
||||
|
||||
|
|
|
@ -30,13 +30,15 @@ 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 headers if empty it will use the base onlineDataSource headers else only the specified headers
|
||||
* @param languageCode Not guaranteed to follow any standard. Could be something like "English 4" or "en".
|
||||
* */
|
||||
data class SubtitleData(
|
||||
val name: String,
|
||||
val url: String,
|
||||
val origin: SubtitleOrigin,
|
||||
val mimeType: String,
|
||||
val headers: Map<String, String>
|
||||
val headers: Map<String, String>,
|
||||
val languageCode: String?
|
||||
) {
|
||||
/** Internal ID for exoplayer, unique for each link*/
|
||||
fun getId(): String {
|
||||
|
@ -80,7 +82,8 @@ class PlayerSubtitleHelper {
|
|||
url = subtitleFile.url,
|
||||
origin = SubtitleOrigin.URL,
|
||||
mimeType = subtitleFile.url.toSubtitleMimeType(),
|
||||
headers = emptyMap()
|
||||
headers = emptyMap(),
|
||||
languageCode = subtitleFile.lang
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.os.Bundle
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import com.lagradost.cloudstream3.CommonActivity.screenHeight
|
||||
|
@ -15,10 +16,8 @@ import com.lagradost.cloudstream3.R
|
|||
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
|
||||
import com.lagradost.cloudstream3.ui.player.PlayerEventSource
|
||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||
import com.lagradost.cloudstream3.utils.IOnBackPressed
|
||||
|
||||
|
||||
open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed {
|
||||
open class ResultTrailerPlayer : ResultFragmentPhone() {
|
||||
|
||||
override var lockRotation = false
|
||||
override var isFullScreenPlayer = false
|
||||
|
@ -28,7 +27,7 @@ open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed {
|
|||
const val TAG = "RESULT_TRAILER"
|
||||
}
|
||||
|
||||
var playerWidthHeight: Pair<Int, Int>? = null
|
||||
private var playerWidthHeight: Pair<Int, Int>? = null
|
||||
|
||||
override fun nextEpisode() {}
|
||||
|
||||
|
@ -154,6 +153,10 @@ open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed {
|
|||
}
|
||||
fixPlayerSize()
|
||||
uiReset()
|
||||
|
||||
if (isFullScreenPlayer) {
|
||||
attachBackPressedCallback()
|
||||
} else detachBackPressedCallback()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -172,12 +175,26 @@ open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
return if (isFullScreenPlayer) {
|
||||
updateFullscreen(false)
|
||||
false
|
||||
} else {
|
||||
true
|
||||
private var backPressedCallback: OnBackPressedCallback? = null
|
||||
|
||||
private fun attachBackPressedCallback() {
|
||||
if (backPressedCallback == null) {
|
||||
backPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
updateFullscreen(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
backPressedCallback?.isEnabled = true
|
||||
|
||||
activity?.onBackPressedDispatcher?.addCallback(
|
||||
activity ?: return,
|
||||
backPressedCallback ?: return
|
||||
)
|
||||
}
|
||||
|
||||
private fun detachBackPressedCallback() {
|
||||
backPressedCallback?.isEnabled = false
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ 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.setToolBarScrollFlags
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||
import com.lagradost.cloudstream3.ui.settings.helpers.settings.account.InAppAuthDialogBuilder
|
||||
import com.lagradost.cloudstream3.ui.settings.helpers.settings.account.InAppOAuth2DialogBuilder
|
||||
|
@ -130,6 +131,7 @@ class SettingsAccount : PreferenceFragmentCompat() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
setUpToolbar(R.string.category_account)
|
||||
setPaddingBottom()
|
||||
setToolBarScrollFlags()
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
|
|
|
@ -13,10 +13,12 @@ import android.widget.Toast
|
|||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.R
|
||||
|
@ -59,7 +61,24 @@ class SettingsFragment : Fragment() {
|
|||
listView?.setPadding(0, 0, 0, 100.toPx)
|
||||
}
|
||||
}
|
||||
fun PreferenceFragmentCompat.setToolBarScrollFlags() {
|
||||
if (isTvSettings()) {
|
||||
val settingsAppbar = view?.findViewById<MaterialToolbar>(R.id.settings_toolbar)
|
||||
|
||||
settingsAppbar?.updateLayoutParams<AppBarLayout.LayoutParams> {
|
||||
scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL
|
||||
}
|
||||
}
|
||||
}
|
||||
fun Fragment?.setToolBarScrollFlags() {
|
||||
if (isTvSettings()) {
|
||||
val settingsAppbar = this?.view?.findViewById<MaterialToolbar>(R.id.settings_toolbar)
|
||||
|
||||
settingsAppbar?.updateLayoutParams<AppBarLayout.LayoutParams> {
|
||||
scrollFlags = AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL
|
||||
}
|
||||
}
|
||||
}
|
||||
fun Fragment?.setUpToolbar(title: String) {
|
||||
if (this == null) return
|
||||
val settingsToolbar =
|
||||
|
@ -69,7 +88,7 @@ class SettingsFragment : Fragment() {
|
|||
setTitle(title)
|
||||
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
|
||||
setNavigationOnClickListener {
|
||||
activity?.onBackPressed()
|
||||
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||
}
|
||||
}
|
||||
fixPaddingStatusbar(settingsToolbar)
|
||||
|
@ -85,7 +104,7 @@ class SettingsFragment : Fragment() {
|
|||
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
|
||||
children.firstOrNull { it is ImageView }?.tag = getString(R.string.tv_no_focus_tag)
|
||||
setNavigationOnClickListener {
|
||||
activity?.onBackPressed()
|
||||
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||
}
|
||||
}
|
||||
fixPaddingStatusbar(settingsToolbar)
|
||||
|
|
|
@ -29,6 +29,7 @@ import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
|||
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.setToolBarScrollFlags
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getSyncPrefs
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||
|
@ -56,10 +57,10 @@ fun getCurrentLocale(context: Context): String {
|
|||
// https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes leave blank for auto
|
||||
val appLanguages = arrayListOf(
|
||||
/* begin language list */
|
||||
Triple("", "ajp", "ajp"),
|
||||
Triple("", "عربي شامي", "ajp"),
|
||||
Triple("", "አማርኛ", "am"),
|
||||
Triple("", "العربية", "ar"),
|
||||
Triple("", "ars", "ars"),
|
||||
Triple("", "اللهجة النجدية", "ars"),
|
||||
Triple("", "български", "bg"),
|
||||
Triple("", "বাংলা", "bn"),
|
||||
Triple("\uD83C\uDDE7\uD83C\uDDF7", "português brasileiro", "bp"),
|
||||
|
@ -117,6 +118,7 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
setUpToolbar(R.string.category_general)
|
||||
setPaddingBottom()
|
||||
setToolBarScrollFlags()
|
||||
}
|
||||
|
||||
data class CustomSite(
|
||||
|
@ -201,7 +203,6 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
|||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
|
||||
|
||||
fun showAdd() {
|
||||
val providers = synchronized(allProviders) { allProviders.distinctBy { it.javaClass }.sortedBy { it.name } }
|
||||
activity?.showDialog(
|
||||
|
|
|
@ -11,6 +11,7 @@ import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
|||
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.setToolBarScrollFlags
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||
import com.lagradost.cloudstream3.ui.subtitles.ChromecastSubtitlesFragment
|
||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
||||
|
@ -25,6 +26,7 @@ class SettingsPlayer : PreferenceFragmentCompat() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
setUpToolbar(R.string.category_player)
|
||||
setPaddingBottom()
|
||||
setToolBarScrollFlags()
|
||||
}
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
hideKeyboard()
|
||||
|
|
|
@ -18,6 +18,7 @@ import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
|||
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.setToolBarScrollFlags
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getSyncPrefs
|
||||
|
@ -31,6 +32,7 @@ class SettingsProviders : PreferenceFragmentCompat() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
setUpToolbar(R.string.category_providers)
|
||||
setPaddingBottom()
|
||||
setToolBarScrollFlags()
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
|||
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.setToolBarScrollFlags
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getSyncPrefs
|
||||
|
@ -25,6 +26,7 @@ class SettingsUI : PreferenceFragmentCompat() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
setUpToolbar(R.string.category_ui)
|
||||
setPaddingBottom()
|
||||
setToolBarScrollFlags()
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
|
|
|
@ -23,6 +23,7 @@ import com.lagradost.cloudstream3.utils.Scheduler.Companion.attachBackupListener
|
|||
import com.lagradost.cloudstream3.services.BackupWorkManager
|
||||
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.setToolBarScrollFlags
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||
import com.lagradost.cloudstream3.utils.BackupUtils
|
||||
import com.lagradost.cloudstream3.utils.BackupUtils.restorePrompt
|
||||
|
@ -44,6 +45,7 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
setUpToolbar(R.string.category_updates)
|
||||
setPaddingBottom()
|
||||
setToolBarScrollFlags()
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
|||
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
||||
import com.lagradost.cloudstream3.ui.result.setText
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.downloadAllPluginsDialog
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
||||
|
@ -85,7 +86,7 @@ class ExtensionsFragment : Fragment() {
|
|||
//context?.fixPaddingStatusbar(extensions_root)
|
||||
|
||||
setUpToolbar(R.string.extensions)
|
||||
|
||||
setToolBarScrollFlags()
|
||||
|
||||
binding?.repoRecyclerView?.apply {
|
||||
setLinearListLayout(
|
||||
|
|
|
@ -21,7 +21,6 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueT
|
|||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import com.lagradost.cloudstream3.utils.GlideApp
|
||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
|
@ -87,7 +86,7 @@ class PluginAdapter(
|
|||
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
||||
if (holder is PluginViewHolder) {
|
||||
holder.binding.entryIcon.let { pluginIcon ->
|
||||
GlideApp.with(pluginIcon).clear(pluginIcon)
|
||||
com.bumptech.glide.Glide.with(pluginIcon).clear(pluginIcon)
|
||||
}
|
||||
}
|
||||
super.onViewRecycled(holder)
|
||||
|
|
|
@ -18,6 +18,7 @@ import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips
|
|||
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
|
||||
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||
import com.lagradost.cloudstream3.ui.settings.appLanguages
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
|
||||
|
@ -69,10 +70,11 @@ class PluginsFragment : Fragment() {
|
|||
val isLocal = arguments?.getBoolean(PLUGINS_BUNDLE_LOCAL) == true
|
||||
|
||||
if (url == null || name == null) {
|
||||
activity?.onBackPressed()
|
||||
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||
return
|
||||
}
|
||||
|
||||
setToolBarScrollFlags()
|
||||
setUpToolbar(name)
|
||||
binding?.settingsToolbar?.apply {
|
||||
setOnMenuItemClickListener { menuItem ->
|
||||
|
@ -117,7 +119,7 @@ class PluginsFragment : Fragment() {
|
|||
if (searchView?.isIconified == false) {
|
||||
searchView.isIconified = true
|
||||
} else {
|
||||
activity?.onBackPressed()
|
||||
activity?.onBackPressedDispatcher?.onBackPressed()
|
||||
}
|
||||
}
|
||||
searchView?.setOnQueryTextFocusChangeListener { _, hasFocus ->
|
||||
|
|
|
@ -12,6 +12,7 @@ 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.setToolBarScrollFlags
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||
|
||||
|
||||
|
@ -27,6 +28,7 @@ class TestFragment : Fragment() {
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
setUpToolbar(R.string.category_provider_test)
|
||||
setToolBarScrollFlags()
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding?.apply {
|
||||
|
|
|
@ -1,15 +1,7 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.text.Editable
|
||||
import android.view.LayoutInflater
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
|
||||
|
@ -20,21 +12,12 @@ 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.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.databinding.WhoIsWatchingAccountEditBinding
|
||||
import com.lagradost.cloudstream3.databinding.WhoIsWatchingBinding
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.ui.WatchType
|
||||
import com.lagradost.cloudstream3.ui.WhoIsWatchingAdapter
|
||||
import com.lagradost.cloudstream3.ui.account.AccountDialog.showPinInputDialog
|
||||
import com.lagradost.cloudstream3.ui.library.ListSorting
|
||||
import com.lagradost.cloudstream3.ui.result.UiImage
|
||||
import com.lagradost.cloudstream3.ui.result.VideoWatchState
|
||||
import com.lagradost.cloudstream3.ui.result.setImage
|
||||
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
|
@ -75,7 +58,7 @@ class UserPreferenceDelegate<T : Any>(
|
|||
|
||||
object DataStoreHelper {
|
||||
// be aware, don't change the index of these as Account uses the index for the art
|
||||
private val profileImages = arrayOf(
|
||||
val profileImages = arrayOf(
|
||||
R.drawable.profile_bg_dark_blue,
|
||||
R.drawable.profile_bg_blue,
|
||||
R.drawable.profile_bg_orange,
|
||||
|
@ -147,7 +130,7 @@ object DataStoreHelper {
|
|||
}
|
||||
|
||||
const val TAG = "data_store_helper"
|
||||
private var accounts by PreferenceDelegate("$TAG/account", arrayOf<Account>())
|
||||
var accounts by PreferenceDelegate("$TAG/account", arrayOf<Account>())
|
||||
var selectedKeyIndex by PreferenceDelegate("$TAG/account_key_index", 0)
|
||||
val currentAccount: String get() = selectedKeyIndex.toString()
|
||||
|
||||
|
@ -166,156 +149,21 @@ object DataStoreHelper {
|
|||
}
|
||||
}
|
||||
|
||||
private fun setAccount(account: Account, refreshHomePage: Boolean) {
|
||||
fun setAccount(account: Account) {
|
||||
val homepage = currentHomePage
|
||||
|
||||
selectedKeyIndex = account.keyIndex
|
||||
showToast(account.name)
|
||||
showToast(context?.getString(R.string.logged_account, account.name) ?: account.name)
|
||||
MainActivity.bookmarksUpdatedEvent(true)
|
||||
if (refreshHomePage) {
|
||||
MainActivity.reloadLibraryEvent(true)
|
||||
val oldAccount = accounts.find { it.keyIndex == account.keyIndex }
|
||||
if (oldAccount != null && currentHomePage != homepage) {
|
||||
// This is not a new account, and the homepage has changed, reload it
|
||||
MainActivity.reloadHomeEvent(true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun editAccount(context: Context, account: Account, isNewAccount: Boolean) {
|
||||
val binding =
|
||||
WhoIsWatchingAccountEditBinding.inflate(LayoutInflater.from(context), null, false)
|
||||
val builder =
|
||||
AlertDialog.Builder(context, R.style.AlertDialogCustom)
|
||||
.setView(binding.root)
|
||||
|
||||
var currentEditAccount = account
|
||||
val dialog = builder.show()
|
||||
binding.accountName.text = Editable.Factory.getInstance()?.newEditable(account.name)
|
||||
binding.accountName.doOnTextChanged { text, _, _, _ ->
|
||||
currentEditAccount = currentEditAccount.copy(name = text?.toString() ?: "")
|
||||
}
|
||||
|
||||
binding.deleteBtt.isGone = isNewAccount
|
||||
binding.deleteBtt.setOnClickListener {
|
||||
val dialogClickListener =
|
||||
DialogInterface.OnClickListener { _, which ->
|
||||
when (which) {
|
||||
DialogInterface.BUTTON_POSITIVE -> {
|
||||
// remove all keys as well as the account, note that default wont get
|
||||
// deleted from currentAccounts, as it is not part of "accounts",
|
||||
// but the watch keys will
|
||||
removeKeys(account.keyIndex.toString())
|
||||
val currentAccounts = accounts.toMutableList()
|
||||
currentAccounts.removeIf { it.keyIndex == account.keyIndex }
|
||||
accounts = currentAccounts.toTypedArray()
|
||||
|
||||
// update UI
|
||||
setAccount(getDefaultAccount(context), true)
|
||||
dialog?.dismissSafe()
|
||||
}
|
||||
|
||||
DialogInterface.BUTTON_NEGATIVE -> {}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
AlertDialog.Builder(context).setTitle(R.string.delete).setMessage(
|
||||
context.getString(R.string.delete_message).format(
|
||||
currentEditAccount.name
|
||||
)
|
||||
)
|
||||
.setPositiveButton(R.string.delete, dialogClickListener)
|
||||
.setNegativeButton(R.string.cancel, dialogClickListener)
|
||||
.show().setDefaultFocus()
|
||||
} catch (t: Throwable) {
|
||||
logError(t)
|
||||
// ye you somehow fucked up formatting did you?
|
||||
}
|
||||
}
|
||||
|
||||
binding.cancelBtt.setOnClickListener {
|
||||
dialog?.dismissSafe()
|
||||
}
|
||||
|
||||
binding.profilePic.setImage(account.image)
|
||||
binding.profilePic.setOnClickListener {
|
||||
// Roll the image forwards once
|
||||
currentEditAccount =
|
||||
currentEditAccount.copy(defaultImageIndex = (currentEditAccount.defaultImageIndex + 1) % profileImages.size)
|
||||
binding.profilePic.setImage(currentEditAccount.image)
|
||||
}
|
||||
|
||||
binding.applyBtt.setOnClickListener {
|
||||
if (currentEditAccount.lockPin != null) {
|
||||
// Ask for the current PIN
|
||||
showPinInputDialog(context, currentEditAccount.lockPin, false) { pin ->
|
||||
if (pin == null) return@showPinInputDialog
|
||||
// PIN is correct, proceed to update the account
|
||||
performAccountUpdate(currentEditAccount)
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
} else {
|
||||
// No lock PIN set, proceed to update the account
|
||||
performAccountUpdate(currentEditAccount)
|
||||
dialog.dismissSafe()
|
||||
}
|
||||
}
|
||||
|
||||
// Handle setting or changing the PIN
|
||||
|
||||
if (currentEditAccount.keyIndex == getDefaultAccount(context).keyIndex) {
|
||||
binding.lockProfileCheckbox.isVisible = false
|
||||
if (currentEditAccount.lockPin != null) {
|
||||
currentEditAccount = currentEditAccount.copy(lockPin = null)
|
||||
}
|
||||
}
|
||||
|
||||
var canSetPin = true
|
||||
|
||||
binding.lockProfileCheckbox.isChecked = currentEditAccount.lockPin != null
|
||||
|
||||
binding.lockProfileCheckbox.setOnCheckedChangeListener { _, isChecked ->
|
||||
if (isChecked) {
|
||||
if (canSetPin) {
|
||||
showPinInputDialog(context, null, true) { pin ->
|
||||
if (pin == null) {
|
||||
binding.lockProfileCheckbox.isChecked = false
|
||||
return@showPinInputDialog
|
||||
}
|
||||
|
||||
currentEditAccount = currentEditAccount.copy(lockPin = pin)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (currentEditAccount.lockPin != null) {
|
||||
// Ask for the current PIN
|
||||
showPinInputDialog(context, currentEditAccount.lockPin, true) { pin ->
|
||||
if (pin == null || pin != currentEditAccount.lockPin) {
|
||||
canSetPin = false
|
||||
binding.lockProfileCheckbox.isChecked = true
|
||||
} else {
|
||||
currentEditAccount = currentEditAccount.copy(lockPin = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
canSetPin = true
|
||||
}
|
||||
|
||||
private fun performAccountUpdate(account: Account) {
|
||||
val currentAccounts = accounts.toMutableList()
|
||||
|
||||
val overrideIndex = currentAccounts.indexOfFirst { it.keyIndex == account.keyIndex }
|
||||
|
||||
if (overrideIndex != -1) {
|
||||
currentAccounts[overrideIndex] = account
|
||||
} else {
|
||||
currentAccounts.add(account)
|
||||
}
|
||||
|
||||
val currentHomePage = this.currentHomePage
|
||||
setAccount(account, false)
|
||||
this.currentHomePage = currentHomePage
|
||||
accounts = currentAccounts.toTypedArray()
|
||||
}
|
||||
|
||||
private fun getDefaultAccount(context: Context): Account {
|
||||
fun getDefaultAccount(context: Context): Account {
|
||||
return accounts.let { currentAccounts ->
|
||||
currentAccounts.getOrNull(currentAccounts.indexOfFirst { it.keyIndex == 0 }) ?: Account(
|
||||
keyIndex = 0,
|
||||
|
@ -333,71 +181,6 @@ object DataStoreHelper {
|
|||
}
|
||||
}
|
||||
|
||||
fun showWhoIsWatching(context: Context) {
|
||||
val binding: WhoIsWatchingBinding = WhoIsWatchingBinding.inflate(LayoutInflater.from(context))
|
||||
val builder = BottomSheetDialog(context)
|
||||
builder.setContentView(binding.root)
|
||||
|
||||
val showAccount = accounts.toMutableList().apply {
|
||||
val item = getDefaultAccount(context)
|
||||
remove(item)
|
||||
add(0, item)
|
||||
}
|
||||
|
||||
val accountName = context.getString(R.string.account)
|
||||
|
||||
binding.profilesRecyclerview.setLinearListLayout(isHorizontal = true)
|
||||
binding.profilesRecyclerview.adapter = WhoIsWatchingAdapter(
|
||||
selectCallBack = { account ->
|
||||
// Check if the selected account has a lock PIN set
|
||||
if (account.lockPin != null) {
|
||||
// Prompt for the lock pin
|
||||
showPinInputDialog(context, account.lockPin, false) { pin ->
|
||||
if (pin == null) return@showPinInputDialog
|
||||
// Pin is correct, unlock the profile
|
||||
setAccount(account, true)
|
||||
builder.dismissSafe()
|
||||
}
|
||||
} else {
|
||||
// No lock PIN set, directly set the account
|
||||
setAccount(account, true)
|
||||
builder.dismissSafe()
|
||||
}
|
||||
},
|
||||
addAccountCallback = {
|
||||
val currentAccounts = accounts
|
||||
val remainingImages =
|
||||
profileImages.toSet() - currentAccounts.filter { it.customImage == null }
|
||||
.mapNotNull { profileImages.getOrNull(it.defaultImageIndex) }.toSet()
|
||||
val image =
|
||||
profileImages.indexOf(remainingImages.randomOrNull() ?: profileImages.random())
|
||||
val keyIndex = (currentAccounts.maxOfOrNull { it.keyIndex } ?: 0) + 1
|
||||
|
||||
// create a new dummy account
|
||||
editAccount(
|
||||
context,
|
||||
Account(
|
||||
keyIndex = keyIndex,
|
||||
name = "$accountName $keyIndex",
|
||||
customImage = null,
|
||||
defaultImageIndex = image
|
||||
), isNewAccount = true
|
||||
)
|
||||
builder.dismissSafe()
|
||||
},
|
||||
editCallBack = { account ->
|
||||
editAccount(
|
||||
context, account, isNewAccount = false
|
||||
)
|
||||
builder.dismissSafe()
|
||||
}
|
||||
).apply {
|
||||
submitList(showAccount)
|
||||
}
|
||||
|
||||
builder.show()
|
||||
}
|
||||
|
||||
data class PosDur(
|
||||
@JsonProperty("position") val position: Long,
|
||||
@JsonProperty("duration") val duration: Long
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
import android.net.Uri
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.USER_AGENT
|
||||
|
@ -377,7 +378,8 @@ open class ExtractorLink constructor(
|
|||
) : VideoDownloadManager.IDownloadableMinimum {
|
||||
val isM3u8 : Boolean get() = type == ExtractorLinkType.M3U8
|
||||
val isDash : Boolean get() = type == ExtractorLinkType.DASH
|
||||
|
||||
|
||||
@JsonIgnore
|
||||
fun getAllHeaders() : Map<String, String> {
|
||||
if (referer.isBlank()) {
|
||||
return headers
|
||||
|
@ -920,4 +922,4 @@ abstract class ExtractorApi {
|
|||
open fun getExtractorUrl(id: String): String {
|
||||
return id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
interface IOnBackPressed {
|
||||
fun onBackPressed(): Boolean
|
||||
}
|
|
@ -153,9 +153,11 @@ object SubtitleHelper {
|
|||
private val flags = mapOf(
|
||||
"af" to "ZA",
|
||||
"agq" to "CM",
|
||||
"ajp" to "SY",
|
||||
"ak" to "GH",
|
||||
"am" to "ET",
|
||||
"ar" to "AE",
|
||||
"ars" to "SA",
|
||||
"as" to "IN",
|
||||
"asa" to "TZ",
|
||||
"az" to "AZ",
|
||||
|
@ -515,4 +517,4 @@ object SubtitleHelper {
|
|||
Language639("Zhuang", "Saɯ cueŋƅ, Saw cuengh", "za", "zha", "zha", "zha", ""),
|
||||
Language639("Zulu", "isiZulu", "zu", "zul", "zul", "zul", ""),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -301,7 +301,7 @@ object UIHelper {
|
|||
} ?: return false
|
||||
|
||||
return try {
|
||||
var builder = GlideApp.with(this)
|
||||
var builder = com.bumptech.glide.Glide.with(this)
|
||||
.load(glideImage)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL).let { req ->
|
||||
|
@ -368,7 +368,7 @@ object UIHelper {
|
|||
) {
|
||||
if (this == null || url.isNullOrBlank()) return
|
||||
try {
|
||||
val res = GlideApp.with(this)
|
||||
val res = com.bumptech.glide.Glide.with(this)
|
||||
.load(GlideUrl(url) { headers ?: emptyMap() })
|
||||
.apply(bitmapTransform(BlurTransformation(radius, sample)))
|
||||
.transition(
|
||||
|
@ -418,7 +418,7 @@ object UIHelper {
|
|||
}
|
||||
|
||||
fun FragmentActivity.popCurrentPage() {
|
||||
this.onBackPressed()
|
||||
this.onBackPressedDispatcher.onBackPressed()
|
||||
/*val currentFragment = supportFragmentManager.fragments.lastOrNull {
|
||||
it.isVisible
|
||||
} ?: return
|
||||
|
@ -438,7 +438,7 @@ object UIHelper {
|
|||
val currentFragment = supportFragmentManager.fragments.lastOrNull {
|
||||
it.isVisible
|
||||
}
|
||||
?: //this.onBackPressed()
|
||||
?: //this.onBackPressedDispatcher.onBackPressed()
|
||||
return
|
||||
|
||||
/*
|
||||
|
|
|
@ -234,7 +234,7 @@ object VideoDownloadManager {
|
|||
return cachedBitmaps[url]
|
||||
}
|
||||
|
||||
val bitmap = GlideApp.with(this)
|
||||
val bitmap = com.bumptech.glide.Glide.with(this)
|
||||
.asBitmap()
|
||||
.load(GlideUrl(url) { headers ?: emptyMap() })
|
||||
.into(720, 720)
|
||||
|
|
11
app/src/main/res/drawable/ic_baseline_edit_24.xml
Normal file
11
app/src/main/res/drawable/ic_baseline_edit_24.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
|
||||
</vector>
|
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
|
@ -14,13 +15,12 @@
|
|||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text1"
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:text="@string/create_account"
|
||||
|
@ -28,34 +28,8 @@
|
|||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- <com.google.android.material.button.MaterialButton-->
|
||||
<!-- android:nextFocusDown="@id/repo_name_input"-->
|
||||
<!-- android:id="@+id/list_repositories"-->
|
||||
<!-- android:nextFocusLeft="@id/apply_btt"-->
|
||||
<!-- android:nextFocusRight="@id/cancel_btt"-->
|
||||
<!-- style="@style/WhiteButton"-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
<!-- android:layout_gravity="center_vertical"-->
|
||||
<!-- android:text="@string/view_public_repositories_button_short" />-->
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text2"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_gravity="center_vertical"
|
||||
|
||||
android:layout_marginBottom="10dp"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:textSize="15sp"
|
||||
android:visibility="gone"
|
||||
tools:text="Gogoanime" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -89,55 +63,55 @@
|
|||
app:cardCornerRadius="@dimen/rounded_image_radius">
|
||||
|
||||
<ImageView
|
||||
android:foreground="@drawable/outline_drawable_forced_round"
|
||||
android:id="@+id/profile_pic"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_gravity="center"
|
||||
android:id="@+id/account_image"
|
||||
android:src="@drawable/profile_bg_blue"
|
||||
android:focusable="true"
|
||||
android:contentDescription="@string/preview_background_img_des"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/profile_bg_blue" />
|
||||
android:foreground="@drawable/outline_drawable_forced_round"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/apply_btt_holder"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_marginTop="-60dp"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp">
|
||||
android:layout_marginTop="-60dp">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/delete_btt"
|
||||
style="@style/BlackButton"
|
||||
android:text="@string/delete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:nextFocusRight="@id/apply_btt"
|
||||
android:text="@string/delete" />
|
||||
style="@style/BlackButton" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/apply_btt"
|
||||
style="@style/WhiteButton"
|
||||
|
||||
android:text="@string/sort_apply"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:layout_toStartOf="@+id/cancel_btt"
|
||||
android:nextFocusLeft="@id/delete_btt"
|
||||
android:nextFocusRight="@id/cancel_btt"
|
||||
android:text="@string/sort_apply" />
|
||||
style="@style/WhiteButton" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/cancel_btt"
|
||||
style="@style/BlackButton"
|
||||
|
||||
android:text="@string/sort_cancel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:nextFocusLeft="@id/apply_btt"
|
||||
android:text="@string/sort_cancel" />
|
||||
style="@style/BlackButton" />
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
|
@ -9,9 +9,9 @@
|
|||
android:animateLayoutChanges="true"
|
||||
android:backgroundTint="?attr/primaryGrayBackground"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
app:cardCornerRadius="@dimen/rounded_image_radius"
|
||||
android:layout_margin="10dp"
|
||||
android:focusable="true"
|
||||
app:cardCornerRadius="@dimen/rounded_image_radius"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -19,38 +19,38 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/account_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:alpha="0.4"
|
||||
android:contentDescription="@string/profile_background_des"
|
||||
android:scaleType="centerCrop" />
|
||||
<ImageView
|
||||
android:id="@+id/account_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:alpha="0.4"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<View
|
||||
android:id="@+id/outline"
|
||||
tools:visibility="visible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/outline_card"
|
||||
android:visibility="gone" />
|
||||
<View
|
||||
android:id="@+id/outline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/outline_card"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/lock_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_margin="4dp"
|
||||
android:src="@drawable/video_locked"
|
||||
android:visibility="gone" />
|
||||
<ImageView
|
||||
android:id="@+id/lock_icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_margin="4dp"
|
||||
android:src="@drawable/video_locked"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/account_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:textSize="16sp" />
|
||||
<TextView
|
||||
android:id="@+id/account_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:padding="10dp"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
</androidx.cardview.widget.CardView>
|
|
@ -2,16 +2,15 @@
|
|||
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/card_view"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_width="110dp"
|
||||
android:layout_height="110dp"
|
||||
android:animateLayoutChanges="true"
|
||||
android:backgroundTint="?attr/primaryGrayBackground"
|
||||
android:foreground="?attr/selectableItemBackgroundBorderless"
|
||||
app:cardCornerRadius="@dimen/rounded_image_radius"
|
||||
android:layout_margin="5dp"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:layout_margin="10dp"
|
||||
android:focusable="true"
|
||||
app:cardCornerRadius="@dimen/rounded_image_radius"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
|
@ -4,14 +4,14 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/card_view"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_width="110dp"
|
||||
android:layout_height="110dp"
|
||||
android:animateLayoutChanges="true"
|
||||
android:backgroundTint="?attr/primaryGrayBackground"
|
||||
android:foreground="?attr/selectableItemBackgroundBorderless"
|
||||
app:cardCornerRadius="@dimen/rounded_image_radius"
|
||||
android:layout_margin="5dp"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:layout_margin="10dp"
|
||||
android:focusable="true"
|
||||
app:cardCornerRadius="@dimen/rounded_image_radius"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -20,20 +20,19 @@
|
|||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/profile_image_background"
|
||||
android:id="@+id/account_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:alpha="0.4"
|
||||
android:contentDescription="@string/profile_background_des"
|
||||
android:alpha="0.1"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<View
|
||||
android:id="@+id/outline"
|
||||
tools:visibility="visible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/outline_card"
|
||||
android:visibility="gone" />
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/lock_icon"
|
||||
|
@ -42,11 +41,18 @@
|
|||
android:layout_gravity="top|end"
|
||||
android:layout_margin="4dp"
|
||||
android:src="@drawable/video_locked"
|
||||
android:visibility="gone" />
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/pencil_icon"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_gravity="top|start"
|
||||
android:src="@drawable/ic_baseline_edit_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/profile_text"
|
||||
tools:text="@string/mobile_data"
|
||||
android:id="@+id/account_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
44
app/src/main/res/layout/account_select_linear.xml
Normal file
44
app/src/main/res/layout/account_select_linear.xml
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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="match_parent">
|
||||
|
||||
<TextView
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/switch_account"
|
||||
android:textSize="20sp"
|
||||
android:textColor="?attr/textColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/account_recycler_view"
|
||||
android:orientation="horizontal"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/account_list_item" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/manage_accounts_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@string/manage_accounts"
|
||||
android:textSize="16sp"
|
||||
app:icon="@drawable/ic_baseline_edit_24"
|
||||
style="@style/BlackButton" />
|
||||
|
||||
</LinearLayout>
|
|
@ -1,28 +1,49 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="?attr/primaryBlackBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="36dp"
|
||||
android:gravity="center">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/select_an_account"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
<ImageView
|
||||
android:id="@+id/edit_account_button"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_gravity="end|top"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:src="@drawable/ic_baseline_edit_24"
|
||||
android:focusable="true"
|
||||
android:nextFocusDown="@id/account_recycler_view"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/manage_accounts"
|
||||
app:tint="@color/player_on_button_tv_attr" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/account_recycler_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="36dp"
|
||||
android:gravity="center">
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/select_an_account"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
||||
android:id="@+id/account_recycler_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
|
@ -1,27 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:background="?attr/primaryBlackBackground"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="36dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:textAlignment="center"
|
||||
android:text="@string/select_an_account"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/account_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp" />
|
||||
|
||||
</LinearLayout>
|
|
@ -27,7 +27,8 @@
|
|||
android:id="@+id/search_status_bar_padding"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="horizontal"
|
||||
app:layout_scrollFlags="scroll|enterAlways">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/provider_selector"
|
||||
|
@ -108,6 +109,7 @@
|
|||
</androidx.appcompat.widget.SearchView>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/library_tab_layout"
|
||||
style="@style/Theme.Widget.Tabs"
|
||||
|
@ -117,7 +119,7 @@
|
|||
android:nextFocusDown="@id/search_result_root"
|
||||
android:background="?attr/primaryGrayBackground"
|
||||
android:paddingHorizontal="5dp"
|
||||
app:layout_scrollFlags="noScroll"
|
||||
android:focusable="true"
|
||||
app:tabGravity="center"
|
||||
app:tabIndicator="@drawable/indicator_background"
|
||||
app:tabIndicatorColor="?attr/white"
|
||||
|
@ -134,15 +136,15 @@
|
|||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/viewpager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="40dp"
|
||||
android:focusable="true"
|
||||
android:tag="@string/tv_no_focus_tag"
|
||||
tools:listitem="@layout/library_viewpager_page" />
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/viewpager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="40dp"
|
||||
android:focusable="true"
|
||||
android:tag="@string/tv_no_focus_tag"
|
||||
tools:listitem="@layout/library_viewpager_page" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/library_loading_overlay"
|
||||
|
@ -182,7 +184,6 @@
|
|||
tools:listitem="@layout/loading_poster_dynamic" />
|
||||
</com.facebook.shimmer.ShimmerFrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<TextView
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/switch_account"
|
||||
android:textSize="20sp"
|
||||
android:textColor="?attr/textColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:text="@string/history"
|
||||
android:textSize="15sp"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:text="@string/error_bookmarks_text"
|
||||
android:textSize="15sp"
|
||||
android:textColor="?attr/grayTextColor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_rowWeight="1"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:layout_marginTop="10dp"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:id="@+id/profiles_recyclerview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
android:orientation="horizontal"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:itemCount="4"
|
||||
tools:listitem="@layout/who_is_watching_account">
|
||||
|
||||
<requestFocus />
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
</LinearLayout>
|
|
@ -12,8 +12,8 @@
|
|||
<!-- TRANSLATE, BUT DON'T FORGET FORMAT -->
|
||||
<string name="player_speed_text_format" formatted="true">سرعة (%.2fx)</string>
|
||||
<string name="rated_format" formatted="true">تقييم: %.1f</string>
|
||||
<string name="new_update_format" formatted="true">!تم العثور على تحديث جديد
|
||||
\n%s -> %s</string>
|
||||
<string name="new_update_format" formatted="true">يوجد تحديث جديد!
|
||||
\n%1$s -> %2$s</string>
|
||||
<string name="duration_format" formatted="true">%d دقيقة</string>
|
||||
<string name="app_name">CloudStream</string>
|
||||
<string name="play_with_app_name">تشغيل بواسطة CloudStream</string>
|
||||
|
@ -319,7 +319,7 @@
|
|||
<string name="kitsu_account_settings" translatable="false">Kitsu</string>
|
||||
<string name="trakt_account_settings" translatable="false">Trakt</string>
|
||||
-->
|
||||
<string name="login_format" formatted="true">%s %s</string>
|
||||
<string name="login_format" formatted="true">%1$s %2$s</string>
|
||||
<string name="account">حساب</string>
|
||||
<string name="logout">تسجيل الخروج</string>
|
||||
<string name="login">تسجيل الدخول</string>
|
||||
|
@ -418,8 +418,8 @@
|
|||
<string name="plugin_deleted">تم إزالة الإضافة</string>
|
||||
<string name="plugin_load_fail" formatted="true">تعذر التحميل %s</string>
|
||||
<string name="is_adult">18+</string>
|
||||
<string name="batch_download_start_format" formatted="true">بدأ تنزيل %d %s …</string>
|
||||
<string name="batch_download_finish_format" formatted="true">تم التنزيل %d %s</string>
|
||||
<string name="batch_download_start_format" formatted="true">بدأ تنزيل %1$d %2$s…</string>
|
||||
<string name="batch_download_finish_format" formatted="true">تم تنزيل %1$d %2$s</string>
|
||||
<string name="batch_download_nothing_to_download_format" formatted="true">جميع %s محملة بالفعل</string>
|
||||
<string name="batch_download">تحميل مكثف</string>
|
||||
<string name="plugin_singular">إضافة</string>
|
||||
|
@ -461,11 +461,11 @@
|
|||
<string name="history">السجل</string>
|
||||
<string name="enable_skip_op_from_database_des">عرض زر تخطي المقدمة/الخاتمة</string>
|
||||
<string name="cast_format" formatted="true">طاقم العمل: %s</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%d يوم %d ساعة %d دقيقة</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%d ساعة %d دقيقة</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%1$d يوم %2$d ساعة %3$d دقيقة</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%1$d ساعة %2$d دقيقة</string>
|
||||
<string name="filler" formatted="true">الفيلير</string>
|
||||
<string name="action_open_play">فتح(تشغيل)</string>
|
||||
<string name="season_format">%s %d%s</string>
|
||||
<string name="season_format">%1$s %2$d%3$s</string>
|
||||
<string name="plugins_updated" formatted="true">المكونات الإضافية المحدثة %d</string>
|
||||
<string name="player_settings_play_in_vlc">VLC</string>
|
||||
<string name="player_settings_play_in_mpv">MPV</string>
|
||||
|
@ -482,7 +482,7 @@
|
|||
<string name="action_mark_as_watched">علّمه كفيديو تمت مشاهدته</string>
|
||||
<string name="yes">نعم</string>
|
||||
<string name="no">ﻻ</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s الحلقة %d</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s الحلقة %2$d</string>
|
||||
<string name="next_episode_format" formatted="true">سيتم إصدار الحلقة %d في</string>
|
||||
<string name="update_notification_failed">تعذر تثبيت الإصدار الجديد من التطبيق</string>
|
||||
<string name="extension_install_first">تثبيت الإضافة أولا</string>
|
||||
|
@ -493,8 +493,8 @@
|
|||
<string name="update_notification_downloading">تنزيل تحديث التطبيق…</string>
|
||||
<string name="update_notification_installing">تثبيت تحديث التطبيق…</string>
|
||||
<string name="next_episode_time_min_format" formatted="true">%d دقيقة</string>
|
||||
<string name="episodes_range">%d-%d</string>
|
||||
<string name="episode_format" formatted="true">%d %s</string>
|
||||
<string name="episodes_range">%1$d-%2$d</string>
|
||||
<string name="episode_format" formatted="true">%1$d %2$s</string>
|
||||
<string name="confirm_exit_dialog">هل أنت متأكد أنك تريد الخروج؟</string>
|
||||
<string name="automatic_plugin_download_summary">قم بتثبيت جميع المكونات الإضافية التي لم يتم تثبيتها بعد تلقائيا من المستودعات المضافة.</string>
|
||||
<string name="apk_installer_settings">مثبت الحزم</string>
|
||||
|
@ -584,4 +584,30 @@
|
|||
<string name="no_repository_found_error">المستودع لم يتم العثور عليه، تحقق من العنوان اوجرب شبكة افتراضية خاصة(vpn)</string>
|
||||
<string name="already_voted">لقد صوتت بالفعل</string>
|
||||
<string name="backup_frequency">معدل النسخ الإحتياطي</string>
|
||||
<string name="favorite_removed">تمت إزالة %s من المفضلة</string>
|
||||
<string name="favorites_list_name">المفضلة</string>
|
||||
<string name="favorite_added">تمت إضافة %s إلى المفضلة</string>
|
||||
<string name="duplicate_message_multiple" formatted="true">احتمال وجود تكرارات في مكتبتك.
|
||||
\n
|
||||
\n%s
|
||||
\n
|
||||
\nهل تريد الاضافة على اي حال مستبدلاً النسخة الموجودة بالفعل, أم تفضل إلغاء العملية؟</string>
|
||||
<string name="duplicate_title">احتمال أن يكون موجود بالفعل</string>
|
||||
<string name="lock_profile">قفل الحساب</string>
|
||||
<string name="action_add_to_favorites">اضافة الى المفضلة</string>
|
||||
<string name="duplicate_replace_all">تبديل الكل</string>
|
||||
<string name="pin_error_incorrect">رقم PIN غير صحيح. برجاء المحاولة مرة اخرى.</string>
|
||||
<string name="action_unsubscribe">إلغاء الاشتراك</string>
|
||||
<string name="pin_error_length">رقم ال PIN يجب ان يكون 4 ارقام</string>
|
||||
<string name="duplicate_replace">استبدال</string>
|
||||
<string name="duplicate_add">اضافة</string>
|
||||
<string name="action_subscribe">إشترك</string>
|
||||
<string name="action_remove_from_favorites">إزالة من المفضلة</string>
|
||||
<string name="select_an_account">اختار حساب</string>
|
||||
<string name="duplicate_message_single">من الظاهر أن \"%1$s\" موجود بالفعل في مكتبتك.
|
||||
\n
|
||||
\nهل تريد الاضافة على أي حال مستبدلاً القديم أو إلغاء العملية؟</string>
|
||||
<string name="enter_pin">ادخال ال PIN</string>
|
||||
<string name="pin">PIN</string>
|
||||
<string name="enter_current_pin">أدخل ال PIN الحالي</string>
|
||||
</resources>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<resources>
|
||||
<!-- KEYS DON'T TRANSLATE -->
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s Ep %2$d</string>
|
||||
<string name="cast_format" formatted="true">Hrají: %s</string>
|
||||
<!-- IS NOT NEEDED TO TRANSLATE AS THEY ARE ONLY USED FOR SCREEN READERS AND WONT SHOW UP TO NORMAL USERS -->
|
||||
<string name="result_poster_img_des">Plakát</string>
|
||||
|
@ -17,7 +17,7 @@
|
|||
<string name="player_speed_text_format" formatted="true">Rychlost (%.2fx)</string>
|
||||
<string name="rated_format" formatted="true">Hodnocení: %.1f</string>
|
||||
<string name="new_update_format" formatted="true">Nalezena nová aktualizace!
|
||||
\n%s -> %s</string>
|
||||
\n%1$s -> %2$s</string>
|
||||
<string name="filler" formatted="true">Výplň</string>
|
||||
<string name="duration_format" formatted="true">%d min</string>
|
||||
<string name="app_name">CloudStream</string>
|
||||
|
@ -292,7 +292,7 @@
|
|||
<string name="kitsu_account_settings" translatable="false">Kitsu</string>
|
||||
<string name="trakt_account_settings" translatable="false">Trakt</string>
|
||||
-->
|
||||
<string name="login_format" formatted="true">%s %s</string>
|
||||
<string name="login_format" formatted="true">%1$s %2$s</string>
|
||||
<string name="account">účet</string>
|
||||
<string name="logout">Odhlásit se</string>
|
||||
<string name="login">Přihlásit se</string>
|
||||
|
@ -410,17 +410,17 @@
|
|||
<string name="clipboard_too_large">Příliš mnoho textu. Nepodařilo se uložit do schránky.</string>
|
||||
<string name="yes">Ano</string>
|
||||
<string name="browser">Prohlížeč</string>
|
||||
<string name="episodes_range">%d-%d</string>
|
||||
<string name="episodes_range">%1$d-%2$d</string>
|
||||
<string name="library">Knihovna</string>
|
||||
<string name="kitsu_settings">Zobrazit plakáty z Kitsu</string>
|
||||
<string name="automatic_plugin_download">Automaticky stahovat doplňky</string>
|
||||
<string name="redo_setup_process">Znovu provést proces nastavení</string>
|
||||
<string name="apk_installer_settings">Instalátor APK</string>
|
||||
<string name="episode_format" formatted="true">%d %s</string>
|
||||
<string name="episode_format" formatted="true">%1$d %2$s</string>
|
||||
<string name="apk_installer_settings_des">Některé telefony nepodporují nový instalátor balíčků. Pokud se aktualizace nenainstalují, zkuste použít starší možnost.</string>
|
||||
<string name="pref_category_cache">Mezipaměť</string>
|
||||
<string name="next_episode_format" formatted="true">Epizoda %d bude vydána za</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%dh %dm</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%1$dh %2$dm</string>
|
||||
<string name="play_livestream_button">Přehrát přímý přenos</string>
|
||||
<string name="pref_category_extensions">Rozšíření</string>
|
||||
<string name="pref_category_actions">Akce</string>
|
||||
|
@ -436,7 +436,7 @@
|
|||
<string name="preferred_media_subtext">Co chcete vidět</string>
|
||||
<string name="plugin_downloaded">Doplněk stažen</string>
|
||||
<string name="is_adult">18+</string>
|
||||
<string name="batch_download_start_format" formatted="true">Spuštěno stahování %d %s…</string>
|
||||
<string name="batch_download_start_format" formatted="true">Spuštěno stahování %1$d %2$s…</string>
|
||||
<string name="blank_repo_message">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ářů.
|
||||
|
@ -505,12 +505,12 @@
|
|||
<string name="pref_category_app_updates">Aktualizace aplikace</string>
|
||||
<string name="setup_done">Hotovo</string>
|
||||
<string name="extension_types">Podporováno</string>
|
||||
<string name="season_format">%s %d%s</string>
|
||||
<string name="season_format">%1$s %2$d%3$s</string>
|
||||
<string name="live_singular">Živý přenos</string>
|
||||
<string name="nsfw_singular">NSFW</string>
|
||||
<string name="extensions">Rozšíření</string>
|
||||
<string name="play_trailer_button">Přehrát trailer</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%dd %dh %dm</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%1$dd %2$dh %3$dm</string>
|
||||
<string name="view_public_repositories_button">Zobrazit komunitní repozitáře</string>
|
||||
<string name="update_started">Aktualizace zahájena</string>
|
||||
<string name="stream">Stream</string>
|
||||
|
@ -520,7 +520,7 @@
|
|||
<string name="referer">Referent</string>
|
||||
<string name="next">Další</string>
|
||||
<string name="provider_languages_tip">Sledovat videa v těchto jazycích</string>
|
||||
<string name="batch_download_finish_format" formatted="true">Staženo %d %s</string>
|
||||
<string name="batch_download_finish_format" formatted="true">Staženo %1$d %2$s</string>
|
||||
<string name="batch_download_nothing_to_download_format" formatted="true">Všechny %s jsou již staženy</string>
|
||||
<string name="batch_download">Hromadné stahování</string>
|
||||
<string name="plugin_singular">doplněk</string>
|
||||
|
@ -575,4 +575,31 @@
|
|||
<string name="no_plugins_found_error">V repozitáři nebyly nalezeny žádné doplňky</string>
|
||||
<string name="no_repository_found_error">Repozitář nenalezen, zkontrolujte adresu URL a zkuste použít VPN</string>
|
||||
<string name="already_voted">Již jste hlasovali</string>
|
||||
<string name="favorite_removed">%s odebráno z oblíbených</string>
|
||||
<string name="favorites_list_name">Oblíbené</string>
|
||||
<string name="favorite_added">%s přidáno do oblíbených</string>
|
||||
<string name="duplicate_message_multiple" formatted="true">Ve vaší knihovně byl nalezen potenciální duplikát:
|
||||
\n
|
||||
\n%s
|
||||
\n
|
||||
\nChcete přesto přidat tuto položku, nahradit existující nebo zrušit akci\?</string>
|
||||
<string name="backup_frequency">Frekvence záloh</string>
|
||||
<string name="duplicate_title">Nalezena potenciální duplicita</string>
|
||||
<string name="lock_profile">Zamknout profil</string>
|
||||
<string name="action_add_to_favorites">Přidat do oblíbených</string>
|
||||
<string name="duplicate_replace_all">Nahradit vše</string>
|
||||
<string name="pin_error_incorrect">Nesprávný PIN. Zkuste to prosím znovu.</string>
|
||||
<string name="action_unsubscribe">Zrušit odběr</string>
|
||||
<string name="pin_error_length">PIN musí obsahovat 4 znaky</string>
|
||||
<string name="duplicate_replace">Nahradit</string>
|
||||
<string name="duplicate_add">Přidat</string>
|
||||
<string name="action_subscribe">Odebírat</string>
|
||||
<string name="action_remove_from_favorites">Odebrat z oblíbených</string>
|
||||
<string name="select_an_account">Vyberte účet</string>
|
||||
<string name="duplicate_message_single">Vypadá to, že ve vaší knihovně již existuje potenciální duplikát: „%1$s“.
|
||||
\n
|
||||
\nChcete přesto přidat tuto položku, nahradit existující nebo zrušit akci\?</string>
|
||||
<string name="enter_pin">Zadejte PIN</string>
|
||||
<string name="pin">PIN</string>
|
||||
<string name="enter_current_pin">Zadejte současný PIN</string>
|
||||
</resources>
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
<string name="setup_extensions_subtext">Descargue la lista de sitios que quiera utilizar</string>
|
||||
<string name="plugins_downloaded" formatted="true">Descargado:%d</string>
|
||||
<string name="downloaded">Descargado</string>
|
||||
<string name="batch_download_finish_format" formatted="true">Descargado %d %s</string>
|
||||
<string name="batch_download_finish_format" formatted="true">Descargado %1$d %2$s</string>
|
||||
<string name="delete_repository">Borrar repositorio</string>
|
||||
<string name="next_episode_format" formatted="true">El episodio %d se lanzará en</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%dh %dm</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%1$dh %2$dm</string>
|
||||
<string name="next_episode_time_min_format" formatted="true">%dm</string>
|
||||
<string name="result_poster_img_des">Poster</string>
|
||||
<string name="extensions">Extensiones</string>
|
||||
|
@ -97,12 +97,12 @@
|
|||
<string name="search_poster_img_des">Poster</string>
|
||||
<string name="home_next_random_img_des">Siguiente al azar</string>
|
||||
<string name="all_languages_preference">Todos los Idiomas</string>
|
||||
<string name="go_back_img_des">Volver</string>
|
||||
<string name="go_back_img_des">Regresar</string>
|
||||
<string name="home_change_provider_img_des">Cambiar proveedor</string>
|
||||
<string name="preview_background_img_des">Vista previa del fondo</string>
|
||||
<string name="rated_format" formatted="true">Nota:%.1f</string>
|
||||
<string name="new_update_format" formatted="true">Nueva actualización encontrada!
|
||||
\n%s -> %s</string>
|
||||
<string name="new_update_format" formatted="true">¡Nueva actualización encontrada!
|
||||
\n%1$s -> %2$s</string>
|
||||
<string name="download">Descargar</string>
|
||||
<string name="popup_pause_download">Pausar Descarga</string>
|
||||
<string name="subs_font">Formato de fuente</string>
|
||||
|
@ -110,8 +110,8 @@
|
|||
<string name="subs_font_size">Tamaño de Fuente</string>
|
||||
<string name="player_speed_text_format" formatted="true">Velocidad (%.2fx)</string>
|
||||
<string name="skip_loading">Omitir carga</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Ep %d</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%dd %dh %dm</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s Ep. %2$d</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%1$dd %2$dh %3$dm</string>
|
||||
<string name="cast_format" formatted="true">Elenco %s</string>
|
||||
<string name="filler" formatted="true">Relleno</string>
|
||||
<string name="duration_format" formatted="true">%d min</string>
|
||||
|
@ -145,7 +145,7 @@
|
|||
<string name="play_torrent_button">Transmitir Torrent</string>
|
||||
<string name="pick_source">Fuentes</string>
|
||||
<string name="reload_error">Reintentar conexión…</string>
|
||||
<string name="go_back">Volver</string>
|
||||
<string name="go_back">Regresar</string>
|
||||
<string name="downloading">Descargando</string>
|
||||
<string name="download_paused">Descarga pausada</string>
|
||||
<string name="download_started">Descarga iniciada</string>
|
||||
|
@ -218,8 +218,8 @@
|
|||
<string name="play_episode_toast">Reproducir Episodio</string>
|
||||
<string name="episode">Episodio</string>
|
||||
<string name="episodes">Episodios</string>
|
||||
<string name="episodes_range">%d-%d</string>
|
||||
<string name="episode_format" formatted="true">%d %s</string>
|
||||
<string name="episodes_range">%1$d-%2$d</string>
|
||||
<string name="episode_format" formatted="true">%1$d %2$s</string>
|
||||
<string name="episode_short">E</string>
|
||||
<string name="restore_failed_format" formatted="true">Falló la restauración de los datos desde el archivo %s</string>
|
||||
<string name="backup_success">Datos guardados</string>
|
||||
|
@ -233,7 +233,7 @@
|
|||
<string name="advanced_search_des">Mostrar los resultados de la búsqueda por proveedor</string>
|
||||
<string name="bug_report_settings_off">Solo envíar los datos si la App se cierra / falla inesperadamente</string>
|
||||
<string name="bug_report_settings_on">No enviar datos</string>
|
||||
<string name="show_trailers_settings">Mostrar Trailers (avances)</string>
|
||||
<string name="show_trailers_settings">Mostrar los trailers</string>
|
||||
<string name="kitsu_settings">Mostrar pósters de Kitsu</string>
|
||||
<string name="uprereleases_settings">Actualizar a las versiones preliminares</string>
|
||||
<string name="uprereleases_settings_des">Buscar actualizaciones preliminares (beta) en lugar de solo versiones completas (stable releases)</string>
|
||||
|
@ -249,7 +249,7 @@
|
|||
<string name="subs_default_reset_toast">Reiniciar a valores predefinidos</string>
|
||||
<string name="acra_report_toast">Lo sentimos, la aplicación se bloqueó. Se enviará un informe de error anónimo a los desarrolladores</string>
|
||||
<string name="season">Temporada</string>
|
||||
<string name="season_format">%s %d%s</string>
|
||||
<string name="season_format">%1$s %2$d%3$s</string>
|
||||
<string name="no_season">Ninguna Temporada</string>
|
||||
<string name="season_short">T</string>
|
||||
<string name="delete_file">Borrar Archivo</string>
|
||||
|
@ -311,7 +311,7 @@
|
|||
<string name="switch_account">Cambiar cuenta</string>
|
||||
<string name="add_account">Añadir cuenta</string>
|
||||
<string name="upload_sync">Sincronizar</string>
|
||||
<string name="sync_score">Calificación</string>
|
||||
<string name="sync_score">Clasificado</string>
|
||||
<string name="authenticated_user" formatted="true">%s autenticado</string>
|
||||
<string name="authenticated_user_fail" formatted="true">No se pudo autenticar a %s</string>
|
||||
<string name="recommended">Recomendado</string>
|
||||
|
@ -345,7 +345,7 @@
|
|||
<string name="primary_color_settings">Color primario</string>
|
||||
<string name="app_theme_settings">Tema de la aplicación</string>
|
||||
<string name="example_email">hola@mundo.com</string>
|
||||
<string name="login_format" formatted="true">%s %s</string>
|
||||
<string name="login_format" formatted="true">%1$s %2$s</string>
|
||||
<string name="all">Todo</string>
|
||||
<string name="subtitle_offset_hint">1000ms</string>
|
||||
<string name="subtitle_offset_extra_hint_none_format">Sin retraso de subtítulos</string>
|
||||
|
@ -448,7 +448,7 @@
|
|||
<string name="setup_done">Hecho</string>
|
||||
<string name="plugin_loaded">Plugin Cargado</string>
|
||||
<string name="is_adult">18+</string>
|
||||
<string name="batch_download_start_format" formatted="true">Iniciada la descarga %d %s…</string>
|
||||
<string name="batch_download_start_format" formatted="true">Comenzó la descarga de %1$d %2$s…</string>
|
||||
<string name="batch_download">Descarga por lotes</string>
|
||||
<string name="plugin_singular">plugin</string>
|
||||
<string name="plugin">plugins</string>
|
||||
|
@ -552,4 +552,30 @@
|
|||
<string name="no_repository_found_error">Repositorio no encontrado, comprueba la URL y prueba la VPN</string>
|
||||
<string name="already_voted">Ya has votado</string>
|
||||
<string name="backup_frequency">Frecuencia de la copia de seguridad</string>
|
||||
<string name="favorite_removed">%s eliminado de favoritos</string>
|
||||
<string name="favorites_list_name">Favoritos</string>
|
||||
<string name="favorite_added">%s añadido a favoritos</string>
|
||||
<string name="duplicate_message_multiple" formatted="true">Se han encontrado posibles elementos duplicados en su biblioteca:
|
||||
\n
|
||||
\n%s
|
||||
\n
|
||||
\n¿Desea añadir este elemento de todos modos, sustituir los existentes o cancelar la acción\?</string>
|
||||
<string name="duplicate_title">Posible duplicado encontrado</string>
|
||||
<string name="lock_profile">Perfil de bloqueo</string>
|
||||
<string name="action_add_to_favorites">Añadido a favoritos</string>
|
||||
<string name="duplicate_replace_all">Sustituir todo</string>
|
||||
<string name="pin_error_incorrect">PIN incorrecto. Por favor, inténtelo de nuevo.</string>
|
||||
<string name="action_unsubscribe">Cancelar la suscripción</string>
|
||||
<string name="pin_error_length">El PIN debe tener 4 caracteres</string>
|
||||
<string name="duplicate_replace">Sustituir</string>
|
||||
<string name="duplicate_add">Añadir</string>
|
||||
<string name="action_subscribe">Suscríbase</string>
|
||||
<string name="action_remove_from_favorites">Eliminar de favoritos</string>
|
||||
<string name="select_an_account">Seleccione una cuenta</string>
|
||||
<string name="duplicate_message_single">Parece que ya existe un elemento potencialmente duplicado en su biblioteca: \'%s.\'
|
||||
\n
|
||||
\n¿Desea añadir este elemento de todos modos, sustituir el existente o cancelar la acción\?</string>
|
||||
<string name="enter_pin">Introducir el PIN</string>
|
||||
<string name="pin">PIN</string>
|
||||
<string name="enter_current_pin">Introduzca el PIN actual</string>
|
||||
</resources>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<string name="player_speed_text_format" formatted="true">Velocità (%.2fx)</string>
|
||||
<string name="rated_format" formatted="true">Valutato: %.1f</string>
|
||||
<string name="new_update_format" formatted="true">Nuovo aggiornamento trovato!
|
||||
\n%s -> %s</string>
|
||||
\n%1$s -> %2$s</string>
|
||||
<string name="filler" formatted="true">Filler</string>
|
||||
<string name="duration_format" formatted="true">%d min</string>
|
||||
<!-- <string name="app_name">CloudStream</string> -->
|
||||
|
@ -409,8 +409,8 @@
|
|||
<string name="plugin_deleted">Plugin eliminato</string>
|
||||
<string name="plugin_load_fail" formatted="true">Impossibile caricare %s</string>
|
||||
<string name="is_adult">18+</string>
|
||||
<string name="batch_download_start_format" formatted="true">Download iniziato %d %s…</string>
|
||||
<string name="batch_download_finish_format" formatted="true">Scaricato %d %s</string>
|
||||
<string name="batch_download_start_format" formatted="true">Download iniziato %1$d %2$s…</string>
|
||||
<string name="batch_download_finish_format" formatted="true">Scaricato %1$d %2$s</string>
|
||||
<string name="batch_download_nothing_to_download_format" formatted="true">Tutti %s già scaricati</string>
|
||||
<string name="batch_download">Download in blocco</string>
|
||||
<string name="plugin_singular">plugin</string>
|
||||
|
@ -573,4 +573,30 @@
|
|||
<string name="automatic_plugin_download_mode_title">Seleziona la modalità per filtrare il download dei plugin</string>
|
||||
<string name="disable">Disabilita</string>
|
||||
<string name="already_voted">Hai già votato</string>
|
||||
<string name="favorite_removed">%s rimosso dai preferiti</string>
|
||||
<string name="favorites_list_name">Preferiti</string>
|
||||
<string name="favorite_added">%s aggiunto ai preferiti</string>
|
||||
<string name="duplicate_message_multiple" formatted="true">Dei possibili duplicati sono stati trovati nella tua libreria:
|
||||
\n
|
||||
\n%s
|
||||
\n
|
||||
\nVorresti aggiungere l\'oggetto alla libreria comunque, rimpiazzare l\'esistente, o cancellare l\'azione\?</string>
|
||||
<string name="backup_frequency">Frequenza di backup</string>
|
||||
<string name="duplicate_title">Trovato Possibile Duplicato</string>
|
||||
<string name="action_add_to_favorites">Aggiungi ai preferiti</string>
|
||||
<string name="duplicate_replace_all">Rimpiazza tutti</string>
|
||||
<string name="pin_error_incorrect">PIN non corretto. Riprova.</string>
|
||||
<string name="action_unsubscribe">Disiscriviti</string>
|
||||
<string name="pin_error_length">Il PIN deve essere almeno di 4 caratteri</string>
|
||||
<string name="duplicate_replace">Rimpiazza</string>
|
||||
<string name="duplicate_add">Aggiungi</string>
|
||||
<string name="action_subscribe">Iscriviti</string>
|
||||
<string name="action_remove_from_favorites">Rimuovi dai preferiti</string>
|
||||
<string name="select_an_account">Seleziona un Account</string>
|
||||
<string name="duplicate_message_single">Sembra che un oggetto potenziale duplicato sia già presente nella tua libreria: \'%1$s.\'
|
||||
\n
|
||||
\nVorresti aggiungere l\'oggetto lo stesso, rimpiazzare l\'esistente, o cancellare l\'azione\?</string>
|
||||
<string name="enter_pin">Inserisci PIN</string>
|
||||
<string name="pin">PIN</string>
|
||||
<string name="enter_current_pin">Inserisci PIN Corrente</string>
|
||||
</resources>
|
||||
|
|
|
@ -9,16 +9,16 @@
|
|||
<string name="cast_format" formatted="true">Актори: %s</string>
|
||||
<string name="next_episode_format" formatted="true">Епізод %d вийде через</string>
|
||||
<string name="result_poster_img_des">Poster</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%s Еп. %d</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%dд %dгод %dхв</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%dгод %dхв</string>
|
||||
<string name="app_dub_sub_episode_text_format" formatted="true">%1$s Еп. %2$d</string>
|
||||
<string name="next_episode_time_day_format" formatted="true">%1$dд %2$dгод %3$dхв</string>
|
||||
<string name="next_episode_time_hour_format" formatted="true">%1$dгод %2$dхв</string>
|
||||
<string name="next_episode_time_min_format" formatted="true">%dхв</string>
|
||||
<string name="home_main_poster_img_des">Головний постер</string>
|
||||
<string name="home_next_random_img_des">Наступний випадковий</string>
|
||||
<string name="preview_background_img_des">Попередній перегляд фону</string>
|
||||
<string name="player_speed_text_format" formatted="true">Швидкість (%.2fx)</string>
|
||||
<string name="new_update_format" formatted="true">Знайдено нове оновлення!
|
||||
\n%s –> %s</string>
|
||||
\n%1$s –> %2$s</string>
|
||||
<string name="title_search">Пошук</string>
|
||||
<string name="title_downloads">Завантаження</string>
|
||||
<string name="duration_format" formatted="true">%d хв</string>
|
||||
|
@ -112,7 +112,7 @@
|
|||
<string name="popup_play_file">Переглянути файл</string>
|
||||
<string name="home_more_info">Детальніше</string>
|
||||
<string name="filter_bookmarks">Фільтр закладок</string>
|
||||
<string name="sort_clear">Очистити</string>
|
||||
<string name="sort_clear">Очистити</string>
|
||||
<string name="subtitles_settings">Налаштування субтитрів</string>
|
||||
<string name="subs_background_color">Колір фону</string>
|
||||
<string name="subs_subtitle_elevation">Висота субтитрів</string>
|
||||
|
@ -167,7 +167,7 @@
|
|||
<string name="subs_default_reset_toast">Скинути до значення за замовчуванням</string>
|
||||
<string name="no_season">Немає сезону</string>
|
||||
<string name="episodes">епізодів</string>
|
||||
<string name="episode_format" formatted="true">%d %s</string>
|
||||
<string name="episode_format" formatted="true">%1$d %2$s</string>
|
||||
<string name="season_short">С</string>
|
||||
<string name="episode_short">Е</string>
|
||||
<string name="delete_file">Видалити файл</string>
|
||||
|
@ -238,9 +238,9 @@
|
|||
<string name="year">Рік</string>
|
||||
<string name="go_forward_30">+30</string>
|
||||
<string name="acra_report_toast">Вибачте, у застосунку стався збій. Анонімне повідомлення про помилку буде відправлено розробникам</string>
|
||||
<string name="season_format">%s %d%s</string>
|
||||
<string name="season_format">%1$s %2$d%3$s</string>
|
||||
<string name="episode">Епізод</string>
|
||||
<string name="episodes_range">%d-%d</string>
|
||||
<string name="episodes_range">%1$d-%2$d</string>
|
||||
<string name="no_episodes_found">Епізодів не знайдено</string>
|
||||
<string name="pause">Пауза</string>
|
||||
<string name="season">Сезон</string>
|
||||
|
@ -364,7 +364,7 @@
|
|||
<string name="category_ui">Макет</string>
|
||||
<string name="category_providers">Постачальники</string>
|
||||
<string name="example_site_url">example.com</string>
|
||||
<string name="login_format" formatted="true">%s %s</string>
|
||||
<string name="login_format" formatted="true">%2$s %1$s</string>
|
||||
<string name="subtitles_depressed">Депресивний</string>
|
||||
<string name="account">обліковий запис</string>
|
||||
<string name="create_account">Створити</string>
|
||||
|
@ -414,8 +414,8 @@
|
|||
<string name="plugin_loaded">Плагін завантажено</string>
|
||||
<string name="plugin_downloaded">Плагін завантажено</string>
|
||||
<string name="plugin_load_fail" formatted="true">Не вдалося завантажити %s</string>
|
||||
<string name="batch_download_start_format" formatted="true">Почалося завантаження %d %s…</string>
|
||||
<string name="batch_download_finish_format" formatted="true">Завантажено %d %s</string>
|
||||
<string name="batch_download_start_format" formatted="true">Почалося завантаження %1$d %2$s…</string>
|
||||
<string name="batch_download_finish_format" formatted="true">Завантажено %1$d %2$s</string>
|
||||
<string name="batch_download_nothing_to_download_format" formatted="true">Всі %s вже завантажено</string>
|
||||
<string name="batch_download">Завантажити пакети</string>
|
||||
<string name="plugin_singular">плагін</string>
|
||||
|
@ -499,7 +499,7 @@
|
|||
<string name="empty_library_no_accounts_message">Ваша бібліотека порожня :(
|
||||
\nУвійдіть в обліковий запис бібліотеки або додайте фільми до вашої локальної бібліотеки.</string>
|
||||
<string name="sort_alphabetical_z">Алфавітом (від Я до А)</string>
|
||||
<string name="select_library">Виберіть бібліотеку</string>
|
||||
<string name="select_library">Оберіть бібліотеку</string>
|
||||
<string name="open_with">Відкрити</string>
|
||||
<string name="browser">Браузер</string>
|
||||
<string name="empty_library_logged_in_message">Цей список порожній. Спробуйте перейти до іншого.</string>
|
||||
|
@ -546,10 +546,36 @@
|
|||
<string name="qualities">Якості</string>
|
||||
<string name="profile_background_des">Фон профілю</string>
|
||||
<string name="unable_to_inflate">Не вдалося створити UI коректно, це ВАЖЛИВА ПОМИЛКА, про яку слід негайно повідомити %s</string>
|
||||
<string name="automatic_plugin_download_mode_title">Виберіть режим для фільтрації завантаження плагінів</string>
|
||||
<string name="automatic_plugin_download_mode_title">Оберіть режим для фільтрації завантаження плагінів</string>
|
||||
<string name="disable">Вимкнути</string>
|
||||
<string name="no_repository_found_error">Репозиторій не знайдено, перевірте URL-адресу та спробуйте VPN</string>
|
||||
<string name="no_plugins_found_error">Не знайдено жодних плагінів у репозиторії</string>
|
||||
<string name="already_voted">Ви вже проголосували</string>
|
||||
<string name="backup_frequency">Частота резервного копіювання</string>
|
||||
<string name="favorite_removed">%s вилучено з обраного</string>
|
||||
<string name="favorites_list_name">Обране</string>
|
||||
<string name="favorite_added">%s додано до обраного</string>
|
||||
<string name="duplicate_message_multiple" formatted="true">У вашій бібліотеці знайдено потенційні дублікати:
|
||||
\n
|
||||
\n%s
|
||||
\n
|
||||
\nВсе одно хочете додати цей елемент, замінити наявні чи скасувати дію\?</string>
|
||||
<string name="duplicate_title">Знайдено потенційний дублікат</string>
|
||||
<string name="lock_profile">Розблокувати профіль</string>
|
||||
<string name="action_add_to_favorites">Додати до обраного</string>
|
||||
<string name="duplicate_replace_all">Замінити усе</string>
|
||||
<string name="pin_error_incorrect">Неправильний PIN-код. Спробуйте ще раз.</string>
|
||||
<string name="action_unsubscribe">Відписатись</string>
|
||||
<string name="pin_error_length">PIN-код повинен складатися з 4 символів</string>
|
||||
<string name="duplicate_replace">Замінити</string>
|
||||
<string name="duplicate_add">Додати</string>
|
||||
<string name="action_subscribe">Підписатись</string>
|
||||
<string name="action_remove_from_favorites">Вилучити з обраного</string>
|
||||
<string name="select_an_account">Оберіть обліковий запис</string>
|
||||
<string name="duplicate_message_single">Виявилося, що у вашій бібліотеці вже є потенційно повторюваний елемент: \'%1$s.\'
|
||||
\n
|
||||
\nВсе одно хочете додати цей елемент, замінити наявний чи скасувати дію\?</string>
|
||||
<string name="enter_pin">Введіть PIN-код</string>
|
||||
<string name="pin">PIN-код</string>
|
||||
<string name="enter_current_pin">Введіть поточний PIN-код</string>
|
||||
</resources>
|
||||
|
|
|
@ -409,8 +409,8 @@
|
|||
<string name="plugin_deleted">Plugin đã xoá</string>
|
||||
<string name="plugin_load_fail" formatted="true">Không tải được %s</string>
|
||||
<string name="is_adult">18+</string>
|
||||
<string name="batch_download_start_format" formatted="true">Bắt đầu tải %d %s…</string>
|
||||
<string name="batch_download_finish_format" formatted="true">Tải xuống %d %s thành công</string>
|
||||
<string name="batch_download_start_format" formatted="true">Đã bắt đầu tải xuống %1$d %2$s…</string>
|
||||
<string name="batch_download_finish_format" formatted="true">Đã tải xuống %1$d %2$s</string>
|
||||
<string name="batch_download_nothing_to_download_format" formatted="true">Toàn bộ %s đã được tải xuống</string>
|
||||
<string name="batch_download">Tải hàng loạt</string>
|
||||
<string name="plugin_singular">plugin</string>
|
||||
|
@ -566,4 +566,31 @@
|
|||
<string name="no_plugins_found_error">Không tìm thấy plugin</string>
|
||||
<string name="unable_to_inflate">Không thể khởi tạo UI, đây là một LỖI LỚN và cần được báo cáo ngay lập tức tới %s</string>
|
||||
<string name="automatic_plugin_download_mode_title">Chọn chế độ để lọc plugin tải xuống</string>
|
||||
<string name="favorite_removed">%s đã loại bỏ khỏi mục yêu thích</string>
|
||||
<string name="favorites_list_name">Yêu thích</string>
|
||||
<string name="favorite_added">%s đã thêm vào mục yêu thích</string>
|
||||
<string name="duplicate_message_multiple" formatted="true">Các mục có thể trùng lặp đã được tìm thấy trong thư viện của bạn:
|
||||
\n
|
||||
\n%s
|
||||
\n
|
||||
\nBạn vẫn muốn thêm mục này, thay thế những mục hiện có hay hủy hành động\?</string>
|
||||
<string name="backup_frequency">Tần suất sao lưu</string>
|
||||
<string name="duplicate_title">Đã tìm thấy bản sao tiềm năng</string>
|
||||
<string name="lock_profile">Khóa hồ sơ</string>
|
||||
<string name="action_add_to_favorites">Thêm vào mục yêu thích</string>
|
||||
<string name="duplicate_replace_all">Thay thế tất cả</string>
|
||||
<string name="pin_error_incorrect">Mã PIN không chính xác. Vui lòng thử lại.</string>
|
||||
<string name="action_unsubscribe">Hủy đăng ký</string>
|
||||
<string name="pin_error_length">Mã PIN phải có 4 ký tự</string>
|
||||
<string name="duplicate_replace">Thay thế</string>
|
||||
<string name="duplicate_add">Thêm vào</string>
|
||||
<string name="action_subscribe">Đăng ký</string>
|
||||
<string name="action_remove_from_favorites">Loại bỏ khỏi mục yêu thích</string>
|
||||
<string name="select_an_account">Chọn một tài khoản</string>
|
||||
<string name="duplicate_message_single">Có vẻ như một mục có khả năng trùng lặp đã tồn tại trong thư viện của bạn: \'%1$s.\'
|
||||
\n
|
||||
\nBạn vẫn muốn thêm mục này, thay thế mục hiện có hay hủy hành động\?</string>
|
||||
<string name="enter_pin">Nhập PIN</string>
|
||||
<string name="pin">PIN</string>
|
||||
<string name="enter_current_pin">Nhập mã PIN hiện tại</string>
|
||||
</resources>
|
||||
|
|
|
@ -20,4 +20,6 @@
|
|||
|
||||
<dimen name="download_size">50dp</dimen>
|
||||
<dimen name="video_frame_width">1dp</dimen>
|
||||
|
||||
<dimen name="account_select_linear_item_size">100dp</dimen>
|
||||
</resources>
|
|
@ -65,6 +65,7 @@
|
|||
<string name="filter_sub_lang_key" translatable="false">filter_sub_lang_key</string>
|
||||
<string name="pref_filter_search_quality_key" translatable="false">pref_filter_search_quality_key</string>
|
||||
<string name="enable_nsfw_on_providers_key" translatable="false">enable_nsfw_on_providers_key</string>
|
||||
<string name="skip_startup_account_select_key" translatable="false">skip_startup_account_select_key</string>
|
||||
<string name="enable_skip_op_from_database" translatable="false">enable_skip_op_from_database</string>
|
||||
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
|
||||
<string name="extra_info_format" formatted="true" translatable="false">%d %s | %s</string>
|
||||
|
@ -714,9 +715,9 @@
|
|||
<string name="duplicate_add">Add</string>
|
||||
<string name="duplicate_replace">Replace</string>
|
||||
<string name="duplicate_replace_all">Replace All</string>
|
||||
<string name="duplicate_cancel" translatable="false">@string/sort_cancel</string>
|
||||
<string name="duplicate_message_single">
|
||||
It appears that a potentially duplicate item already exists in your library: \'%1$s.\'
|
||||
<string name="duplicate_cancel" translatable="false">@string/cancel</string>
|
||||
<string name="duplicate_message_single" formatted="true">
|
||||
It appears that a potentially duplicate item already exists in your library: \'%s.\'
|
||||
|
||||
\n\nWould you like to add this item anyway, replace the existing one, or cancel the action?
|
||||
</string>
|
||||
|
@ -731,10 +732,16 @@
|
|||
|
||||
<string name="tv_no_focus_tag" translatable="false">tv_no_focus_tag</string>
|
||||
<string name="enter_pin">Enter PIN</string>
|
||||
<string name="enter_pin_with_name" formatted="true">Enter PIN for %s</string>
|
||||
<string name="enter_current_pin">Enter Current PIN</string>
|
||||
<string name="lock_profile">Lock Profile</string>
|
||||
<string name="pin">PIN</string>
|
||||
<string name="pin_error_incorrect">Incorrect PIN. Please try again.</string>
|
||||
<string name="pin_error_length">PIN must be 4 characters</string>
|
||||
<string name="select_an_account">Select an Account</string>
|
||||
<string name="manage_accounts">Manage Accounts</string>
|
||||
<string name="edit_account">Edit account</string>
|
||||
<string name="logged_account" formatted="true">Logged in as %s</string>
|
||||
<string name="skip_startup_account_select_pref">Skip account selection at startup</string>
|
||||
<string name="use_default_account">Use Default Account</string>
|
||||
</resources>
|
||||
|
|
3
app/src/main/res/xml/data_extraction_rules.xml
Normal file
3
app/src/main/res/xml/data_extraction_rules.xml
Normal file
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<data-extraction-rules>
|
||||
</data-extraction-rules>
|
|
@ -2,9 +2,11 @@
|
|||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<PreferenceCategory android:title="@string/settings_category_plugins">
|
||||
<Preference
|
||||
android:key="@string/mal_key"
|
||||
android:icon="@drawable/mal_logo" />
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:icon="@drawable/ic_outline_account_circle_24"
|
||||
android:key="@string/skip_startup_account_select_key"
|
||||
android:title="@string/skip_startup_account_select_pref" />
|
||||
|
||||
<Preference
|
||||
android:key="@string/anilist_key"
|
||||
|
|
|
@ -5,10 +5,9 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
// we stay on low ver because prerelease build gradle is fucked
|
||||
classpath("com.android.tools.build:gradle:7.3.1")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20")
|
||||
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.5.0")
|
||||
classpath("com.android.tools.build:gradle:8.1.3")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20")
|
||||
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.10")
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle.kts files
|
||||
|
@ -22,6 +21,10 @@ allprojects {
|
|||
}
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete::class) {
|
||||
delete(rootProject.buildDir)
|
||||
plugins {
|
||||
id("com.google.devtools.ksp") version "1.9.20-1.0.14" apply false
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
1
fastlane/metadata/android/es-ES/changelogs/2.txt
Normal file
1
fastlane/metadata/android/es-ES/changelogs/2.txt
Normal file
|
@ -0,0 +1 @@
|
|||
- ¡Cambios añadidos!
|
10
fastlane/metadata/android/es-ES/full_description.txt
Normal file
10
fastlane/metadata/android/es-ES/full_description.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
CloudStream-3 te permite ver y descargar películas, series de TV y anime.
|
||||
|
||||
La aplicación viene sin ningún tipo de anuncios y análisis y
|
||||
soporta múltiples tráilers y páginas de películas, y más, por ejemplo
|
||||
|
||||
Marcadores
|
||||
|
||||
Descargas de subtítulos
|
||||
|
||||
Compatible con Chromecast
|
1
fastlane/metadata/android/es-ES/short_description.txt
Normal file
1
fastlane/metadata/android/es-ES/short_description.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Vea y descargue películas, series de televisión y anime.
|
1
fastlane/metadata/android/es-ES/title.txt
Normal file
1
fastlane/metadata/android/es-ES/title.txt
Normal file
|
@ -0,0 +1 @@
|
|||
CloudStream
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
|||
#Fri Apr 30 17:11:15 CEST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue