From 9d733eed40c4731e96ef39595f55dc8783be5395 Mon Sep 17 00:00:00 2001
From: antonydp <38143733+antonydp@users.noreply.github.com>
Date: Mon, 31 Oct 2022 09:27:27 +0100
Subject: [PATCH] update
---
.github/workflows/issue_action.yml | 4 +-
.github/workflows/prerelease.yml | 4 +-
.github/workflows/pull_request.yml | 4 +-
.idea/jarRepositories.xml | 5 +
README.md | 1 +
app/build.gradle | 233 -----
app/build.gradle.kts | 250 +++++
app/proguard-rules.pro | 2 +-
.../cloudstream3/ExampleInstrumentedTest.kt | 4 +-
app/src/main/AndroidManifest.xml | 5 +-
.../com/lagradost/cloudstream3/MainAPI.kt | 2 +-
.../lagradost/cloudstream3/MainActivity.kt | 2 +-
.../lagradost/cloudstream3/ParCollections.kt | 24 +-
.../cloudstream3/extractors/Fastream.kt | 6 +-
.../cloudstream3/extractors/Gdriveplayer.kt | 55 +-
.../cloudstream3/extractors/Moviehab.kt | 40 +
.../cloudstream3/extractors/Pelisplus.kt | 10 +-
.../cloudstream3/extractors/SpeedoStream.kt | 6 +-
.../cloudstream3/extractors/StreamSB.kt | 20 +-
.../extractors/VidSrcExtractor.kt | 14 +-
.../cloudstream3/extractors/Vidstream.kt | 10 +-
.../cloudstream3/extractors/XStreamCdn.kt | 64 +-
.../cloudstream3/extractors/Zplayer.kt | 4 +-
.../extractors/helper/AsianEmbedHelper.kt | 4 +-
.../metaproviders/CrossTmdbProvider.kt | 6 +-
.../metaproviders/MultiAnimeProvider.kt | 2 +-
.../cloudstream3/network/WebViewResolver.kt | 37 +-
.../cloudstream3/plugins/PluginManager.kt | 21 +-
.../cloudstream3/plugins/RepositoryManager.kt | 4 +-
.../cloudstream3/ui/APIRepository.kt | 36 +-
.../lagradost/cloudstream3/ui/WatchType.kt | 13 +-
.../cloudstream3/ui/home/HomeFragment.kt | 302 ++++---
.../cloudstream3/ui/home/HomeScrollAdapter.kt | 60 ++
.../ui/home/HomeScrollTransformer.kt | 13 +
.../cloudstream3/ui/home/HomeViewModel.kt | 80 +-
.../cloudstream3/ui/player/CS3IPlayer.kt | 16 +-
.../cloudstream3/ui/player/GeneratorPlayer.kt | 2 +-
.../cloudstream3/ui/player/LinkGenerator.kt | 4 +-
.../cloudstream3/ui/result/ResultFragment.kt | 35 +-
.../ui/result/ResultFragmentPhone.kt | 18 +-
.../ui/result/ResultViewModel2.kt | 49 +-
.../cloudstream3/ui/result/SyncViewModel.kt | 4 +-
.../cloudstream3/ui/search/SearchFragment.kt | 275 +++---
.../cloudstream3/ui/search/SearchViewModel.kt | 6 +-
.../ui/settings/SettingsGeneral.kt | 1 +
.../extensions/ExtensionsViewModel.kt | 4 +-
.../ui/settings/extensions/PluginsFragment.kt | 59 +-
.../settings/extensions/PluginsViewModel.kt | 4 +-
.../cloudstream3/utils/BackupUtils.kt | 4 +-
.../cloudstream3/utils/ExtractorApi.kt | 6 +
app/src/main/res/color/chip_color.xml | 5 +
app/src/main/res/color/chip_color_text.xml | 5 +
app/src/main/res/drawable/home_alt.xml | 9 +
.../main/res/drawable/ic_baseline_add_24.xml | 13 +-
.../drawable/ic_baseline_arrow_back_24.xml | 13 +-
.../drawable/ic_baseline_arrow_forward_24.xml | 13 +-
.../res/drawable/ic_baseline_bookmark_24.xml | 11 +-
.../ic_baseline_delete_outline_24.xml | 16 +-
.../drawable/ic_baseline_filter_list_24.xml | 13 +-
.../ic_baseline_keyboard_arrow_down_24.xml | 13 +-
.../main/res/drawable/ic_baseline_tune_24.xml | 11 +-
app/src/main/res/drawable/search_icon.xml | 30 +-
app/src/main/res/drawable/settings_alt.xml | 9 +
.../main/res/drawable/storage_bar_left.xml | 7 +
.../res/drawable/storage_bar_left_box.xml | 5 +
app/src/main/res/drawable/storage_bar_mid.xml | 4 +
.../main/res/drawable/storage_bar_mid_box.xml | 5 +
.../main/res/drawable/storage_bar_right.xml | 7 +
.../res/drawable/storage_bar_right_box.xml | 5 +
app/src/main/res/layout/activity_main.xml | 118 +--
.../main/res/layout/fragment_downloads.xml | 257 +++---
.../main/res/layout/fragment_extensions.xml | 116 +--
app/src/main/res/layout/fragment_home.xml | 852 ++++++++++--------
app/src/main/res/layout/fragment_home_tv.xml | 80 +-
app/src/main/res/layout/fragment_plugins.xml | 103 +--
app/src/main/res/layout/fragment_result.xml | 9 +-
.../main/res/layout/fragment_result_tv.xml | 2 +-
app/src/main/res/layout/fragment_search.xml | 81 +-
.../main/res/layout/home_select_mainpage.xml | 156 +---
app/src/main/res/layout/tvtypes_chips.xml | 68 ++
.../main/res/layout/tvtypes_chips_scroll.xml | 10 +
app/src/main/res/menu/bottom_nav_menu.xml | 4 +-
app/src/main/res/values-bg/strings.xml | 533 +++++++++++
app/src/main/res/values/colors.xml | 3 +
app/src/main/res/values/dimens.xml | 3 +
app/src/main/res/values/styles.xml | 43 +-
.../lagradost/cloudstream3/ProviderTests.kt | 4 +-
build.gradle | 27 -
build.gradle.kts | 26 +
gradle/wrapper/gradle-wrapper.properties | 2 +-
settings.gradle | 2 -
settings.gradle.kts | 3 +
92 files changed, 2715 insertions(+), 1815 deletions(-)
delete mode 100644 app/build.gradle
create mode 100644 app/build.gradle.kts
create mode 100644 app/src/main/java/com/lagradost/cloudstream3/extractors/Moviehab.kt
create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt
create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollTransformer.kt
create mode 100644 app/src/main/res/color/chip_color.xml
create mode 100644 app/src/main/res/color/chip_color_text.xml
create mode 100644 app/src/main/res/drawable/home_alt.xml
create mode 100644 app/src/main/res/drawable/settings_alt.xml
create mode 100644 app/src/main/res/drawable/storage_bar_left.xml
create mode 100644 app/src/main/res/drawable/storage_bar_left_box.xml
create mode 100644 app/src/main/res/drawable/storage_bar_mid.xml
create mode 100644 app/src/main/res/drawable/storage_bar_mid_box.xml
create mode 100644 app/src/main/res/drawable/storage_bar_right.xml
create mode 100644 app/src/main/res/drawable/storage_bar_right_box.xml
create mode 100644 app/src/main/res/layout/tvtypes_chips.xml
create mode 100644 app/src/main/res/layout/tvtypes_chips_scroll.xml
create mode 100644 app/src/main/res/values-bg/strings.xml
delete mode 100644 build.gradle
create mode 100644 build.gradle.kts
delete mode 100644 settings.gradle
create mode 100644 settings.gradle.kts
diff --git a/.github/workflows/issue_action.yml b/.github/workflows/issue_action.yml
index 79e7766c..28b737b3 100644
--- a/.github/workflows/issue_action.yml
+++ b/.github/workflows/issue_action.yml
@@ -18,7 +18,7 @@ jobs:
uses: actions-cool/issues-similarity-analysis@v1
with:
token: ${{ steps.generate_token.outputs.token }}
- filter-threshold: 0.5
+ filter-threshold: 0.60
title-excludes: ''
comment-title: |
### Your issue looks similar to these issues:
@@ -41,7 +41,7 @@ jobs:
wget --output-document check_issue.py "https://raw.githubusercontent.com/recloudstream/.github/master/.github/check_issue.py"
pip3 install httpx
RES="$(python3 ./check_issue.py)"
- echo "::set-output name=name::${RES}"
+ echo "name=${RES}" >> $GITHUB_OUTPUT
- name: Comment if issue mentions a provider
if: steps.provider_check.outputs.name != 'none'
uses: actions-cool/issues-helper@v3
diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml
index 37161d6b..4ce7dba1 100644
--- a/.github/workflows/prerelease.yml
+++ b/.github/workflows/prerelease.yml
@@ -40,7 +40,7 @@ jobs:
curl -H "Authorization: token ${{ steps.generate_token.outputs.token }}" -o "keystore_password.txt" "https://raw.githubusercontent.com/recloudstream/secrets/master/keystore_password.txt"
KEY_PWD="$(cat keystore_password.txt)"
echo "::add-mask::${KEY_PWD}"
- echo "::set-output name=key_pwd::$KEY_PWD"
+ echo "key_pwd=$KEY_PWD" >> $GITHUB_OUTPUT
- name: Run Gradle
run: |
./gradlew assemblePrerelease makeJar androidSourcesJar
@@ -56,6 +56,6 @@ jobs:
prerelease: true
title: "Pre-release Build"
files: |
- app/build/outputs/apk/prerelease/*.apk
+ app/build/outputs/apk/prerelease/release/*.apk
app/build/libs/app-sources.jar
app/build/classes.jar
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 1a4db134..36199cd6 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -15,9 +15,9 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Run Gradle
- run: ./gradlew assembleDebug
+ run: ./gradlew assemblePrereleaseDebug
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: pull-request-build
- path: "app/build/outputs/apk/debug/*.apk"
+ path: "app/build/outputs/apk/prerelease/debug/*.apk"
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index 652d9f3f..333d4937 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -31,5 +31,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index e7221440..5e961c61 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,7 @@
***The list of supported languages:***
* 🇱🇧 Arabic
+* 🇧🇬 Bulgarian
* 🇭🇷 Croatian
* 🇨🇿 Czech
* 🇳🇱 Dutch
diff --git a/app/build.gradle b/app/build.gradle
deleted file mode 100644
index cdc5e652..00000000
--- a/app/build.gradle
+++ /dev/null
@@ -1,233 +0,0 @@
-plugins {
- id 'com.android.application'
- id 'kotlin-android'
- id 'kotlin-kapt'
- id 'kotlin-android-extensions'
- id 'org.jetbrains.dokka'
-}
-
-def tmpFilePath = System.getProperty("user.home") + "/work/_temp/keystore/"
-def allFilesFromDir = new File(tmpFilePath).listFiles()
-def prereleaseStoreFile = null
-if (allFilesFromDir != null) {
- prereleaseStoreFile = allFilesFromDir.first()
-}
-
-android {
- testOptions {
- unitTests.returnDefaultValues = true
- }
- signingConfigs {
- prerelease {
- if (prereleaseStoreFile != null) {
- storeFile = file(prereleaseStoreFile)
- storePassword System.getenv("SIGNING_STORE_PASSWORD")
- keyAlias System.getenv("SIGNING_KEY_ALIAS")
- keyPassword System.getenv("SIGNING_KEY_PASSWORD")
- }
- }
- }
- compileSdkVersion 31
- buildToolsVersion "30.0.3"
-
- defaultConfig {
- applicationId "com.lagradost.cloudstream3"
- minSdkVersion 21
- targetSdkVersion 30
-
- versionCode 51
- versionName "3.1.5"
-
- resValue "string", "app_version",
- "${defaultConfig.versionName}${versionNameSuffix ?: ""}"
-
- resValue "string", "commit_hash",
- ("git rev-parse --short HEAD".execute().text.trim() ?: "")
-
- resValue "bool", "is_prerelease", "false"
-
- buildConfigField("String", "BUILDDATE", "new java.text.SimpleDateFormat(\"yyyy-MM-dd HH:mm\").format(new java.util.Date(" + System.currentTimeMillis() + "L));")
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-
- kapt {
- includeCompileClasspath = true
- }
- }
-
- buildTypes {
- // release {
- // debuggable false
- // minifyEnabled false
- // shrinkResources false
- // proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- // resValue "bool", "is_prerelease", "false"
- // }
- prerelease {
- applicationIdSuffix ".prerelease"
- buildConfigField("boolean", "BETA", "true")
- signingConfig signingConfigs.prerelease
- versionNameSuffix '-PRE'
- debuggable false
- minifyEnabled false
- shrinkResources false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- resValue "bool", "is_prerelease", "true"
- }
- debug {
- debuggable true
- applicationIdSuffix ".debug"
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- resValue "bool", "is_prerelease", "true"
- }
- }
- compileOptions {
- coreLibraryDesugaringEnabled true
-
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = '1.8'
- freeCompilerArgs = ['-Xjvm-default=compatibility']
- }
- lintOptions {
- checkReleaseBuilds false
- abortOnError false
- }
-}
-
-repositories {
- maven { url 'https://jitpack.io' }
-}
-
-dependencies {
- implementation 'com.google.android.mediahome:video:1.0.0'
- implementation 'androidx.test.ext:junit-ktx:1.1.3'
- testImplementation 'org.json:json:20180813'
-
- implementation 'androidx.core:core-ktx:1.8.0'
- implementation 'androidx.appcompat:appcompat:1.4.2' // need target 32 for 1.5.0
-
- // dont change this to 1.6.0 it looks ugly af
- implementation 'com.google.android.material:material:1.5.0'
- implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- implementation 'androidx.navigation:navigation-fragment-ktx:2.5.1'
- implementation 'androidx.navigation:navigation-ui-ktx:2.5.1'
- implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
- implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
- testImplementation 'junit:junit:4.13.2'
- androidTestImplementation 'androidx.test.ext:junit:1.1.3'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
-
- //implementation "io.karn:khttp-android:0.1.2" //okhttp instead
-// implementation 'org.jsoup:jsoup:1.13.1'
- implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1"
-
- implementation "androidx.preference:preference-ktx:1.2.0"
-
- 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'
-
- implementation 'jp.wasabeef:glide-transformations:4.3.0'
-
- implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
-
- // implementation "androidx.leanback:leanback-paging:1.1.0-alpha09"
-
- // Exoplayer
- implementation 'com.google.android.exoplayer:exoplayer:2.16.1'
- implementation 'com.google.android.exoplayer:extension-cast:2.16.1'
- implementation "com.google.android.exoplayer:extension-mediasession:2.16.1"
- implementation 'com.google.android.exoplayer:extension-okhttp:2.16.1'
-
- //implementation "com.google.android.exoplayer:extension-leanback:2.14.0"
-
- // Bug reports
- implementation "ch.acra:acra-core:5.8.4"
- implementation "ch.acra:acra-toast:5.8.4"
-
- compileOnly "com.google.auto.service:auto-service-annotations:1.0"
- //either for java sources:
- annotationProcessor "com.google.auto.service:auto-service:1.0"
- //or for kotlin sources (requires kapt gradle plugin):
- kapt "com.google.auto.service:auto-service:1.0"
-
- // subtitle color picker
- implementation 'com.jaredrummler:colorpicker:1.1.0'
-
- //run JS
- implementation 'org.mozilla:rhino:1.7.14'
-
- // TorrentStream
- //implementation 'com.github.TorrentStream:TorrentStream-Android:2.7.0'
-
- // Downloading
- implementation "androidx.work:work-runtime:2.7.1"
- implementation "androidx.work:work-runtime-ktx:2.7.1"
-
- // Networking
-// implementation "com.squareup.okhttp3:okhttp:4.9.2"
-// implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1"
- implementation 'com.github.Blatzar:NiceHttp:0.3.3'
-
- // Util to skip the URI file fuckery 🙏
- implementation "com.github.tachiyomiorg:unifile:17bec43"
-
- // API because cba maintaining it myself
- implementation "com.uwetrottmann.tmdb2:tmdb-java:2.6.0"
-
- implementation 'com.github.discord:OverlappingPanels:0.1.3'
- // debugImplementation because LeakCanary should only run in debug builds.
- // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
-
- // for shimmer when loading
- implementation 'com.facebook.shimmer:shimmer:0.5.0'
-
- implementation "androidx.tvprovider:tvprovider:1.0.0"
-
- // used for subtitle decoding https://github.com/albfernandez/juniversalchardet
- implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
-
- // slow af yt
- //implementation 'com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT'
-
- // newpipe yt
- implementation 'com.github.TeamNewPipe:NewPipeExtractor:dev-SNAPSHOT'
- coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
-
- // Library/extensions searching with Levenshtein distance
- implementation 'me.xdrop:fuzzywuzzy:1.4.0'
-}
-
-task androidSourcesJar(type: Jar) {
- getArchiveClassifier().set('sources')
- from android.sourceSets.main.java.srcDirs//full sources
-}
-
-// this is used by the gradlew plugin
-task makeJar(type: Copy) {
- from('build/intermediates/compile_app_classes_jar/debug')
- into('build')
- include('classes.jar')
- dependsOn('build')
-}
-
-dokkaHtml {
- moduleName.set("Cloudstream")
- dokkaSourceSets {
- main {
- sourceLink {
- // Unix based directory relative path to the root of the project (where you execute gradle respectively).
- localDirectory.set(file("src/main/java"))
-
- // URL showing where the source code can be accessed through the web browser
- remoteUrl.set(new 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")
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 00000000..42ca93fd
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,250 @@
+import com.android.build.gradle.api.BaseVariantOutput
+import org.jetbrains.dokka.gradle.DokkaTask
+import java.io.ByteArrayOutputStream
+import java.net.URL
+
+plugins {
+ id("com.android.application")
+ id("kotlin-android")
+ id("kotlin-kapt")
+ id("kotlin-android-extensions")
+ id("org.jetbrains.dokka")
+}
+
+val tmpFilePath = System.getProperty("user.home") + "/work/_temp/keystore/"
+val prereleaseStoreFile: File? = File(tmpFilePath).listFiles()?.first()
+
+fun String.execute() = ByteArrayOutputStream().use { baot ->
+ if (project.exec {
+ workingDir = projectDir
+ commandLine = this@execute.split(Regex("\\s"))
+ standardOutput = baot
+ }.exitValue == 0)
+ String(baot.toByteArray()).trim()
+ else null
+}
+
+android {
+ testOptions {
+ unitTests.isReturnDefaultValues = true
+ }
+ signingConfigs {
+ create("prerelease") {
+ if (prereleaseStoreFile != null) {
+ storeFile = file(prereleaseStoreFile)
+ storePassword = System.getenv("SIGNING_STORE_PASSWORD")
+ keyAlias = System.getenv("SIGNING_KEY_ALIAS")
+ keyPassword = System.getenv("SIGNING_KEY_PASSWORD")
+ }
+ }
+ }
+
+ compileSdk = 31
+ buildToolsVersion = "30.0.3"
+
+ defaultConfig {
+ applicationId = "com.lagradost.cloudstream3"
+ minSdk = 21
+ targetSdk = 30
+
+ versionCode = 52
+ versionName = "3.1.6"
+
+ resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
+
+ resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "")
+
+ resValue("bool", "is_prerelease", "false")
+
+ buildConfigField(
+ "String",
+ "BUILDDATE",
+ "new java.text.SimpleDateFormat(\"yyyy-MM-dd HH:mm\").format(new java.util.Date(" + System.currentTimeMillis() + "L));"
+ )
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ kapt {
+ includeCompileClasspath = true
+ }
+ }
+
+ buildTypes {
+ release {
+ isDebuggable = false
+ isMinifyEnabled = false
+ isShrinkResources = false
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
+ }
+ debug {
+ isDebuggable = true
+ applicationIdSuffix = ".debug"
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
+ }
+ }
+ flavorDimensions.add("state")
+ productFlavors {
+ create("stable") {
+ dimension = "state"
+ resValue("bool", "is_prerelease", "false")
+ }
+ create("prerelease") {
+ dimension = "state"
+ resValue("bool", "is_prerelease", "true")
+ buildConfigField("boolean", "BETA", "true")
+ applicationIdSuffix = ".prerelease"
+ signingConfig = signingConfigs.getByName("prerelease")
+ versionNameSuffix = "-PRE"
+ versionCode = (System.currentTimeMillis() / 60000).toInt()
+ }
+ }
+ 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"
+}
+
+repositories {
+ maven("https://jitpack.io")
+}
+
+dependencies {
+ implementation("com.google.android.mediahome:video:1.0.0")
+ implementation("androidx.test.ext:junit-ktx:1.1.3")
+ testImplementation("org.json:json:20180813")
+
+ implementation("androidx.core:core-ktx:1.8.0")
+ implementation("androidx.appcompat:appcompat:1.4.2") // need target 32 for 1.5.0
+
+ // dont change this to 1.6.0 it looks ugly af
+ implementation("com.google.android.material:material:1.5.0")
+ implementation("androidx.constraintlayout:constraintlayout:2.1.4")
+ implementation("androidx.navigation:navigation-fragment-ktx:2.5.1")
+ implementation("androidx.navigation:navigation-ui-ktx:2.5.1")
+ implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1")
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.3")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
+
+ //implementation("io.karn:khttp-android:0.1.2") //okhttp instead
+// implementation("org.jsoup:jsoup:1.13.1")
+ implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1")
+
+ implementation("androidx.preference:preference-ktx:1.2.0")
+
+ 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")
+
+ implementation("jp.wasabeef:glide-transformations:4.3.0")
+
+ implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
+
+ // implementation("androidx.leanback:leanback-paging:1.1.0-alpha09")
+
+ // Exoplayer
+ implementation("com.google.android.exoplayer:exoplayer:2.16.1")
+ implementation("com.google.android.exoplayer:extension-cast:2.16.1")
+ implementation("com.google.android.exoplayer:extension-mediasession:2.16.1")
+ implementation("com.google.android.exoplayer:extension-okhttp:2.16.1")
+
+ //implementation("com.google.android.exoplayer:extension-leanback:2.14.0")
+
+ // Bug reports
+ implementation("ch.acra:acra-core:5.8.4")
+ implementation("ch.acra:acra-toast:5.8.4")
+
+ compileOnly("com.google.auto.service:auto-service-annotations:1.0")
+ //either for java sources:
+ annotationProcessor("com.google.auto.service:auto-service:1.0")
+ //or for kotlin sources (requires kapt gradle plugin):
+ kapt("com.google.auto.service:auto-service:1.0")
+
+ // subtitle color picker
+ implementation("com.jaredrummler:colorpicker:1.1.0")
+
+ //run JS
+ implementation("org.mozilla:rhino:1.7.14")
+
+ // TorrentStream
+ //implementation("com.github.TorrentStream:TorrentStream-Android:2.7.0")
+
+ // Downloading
+ implementation("androidx.work:work-runtime:2.7.1")
+ implementation("androidx.work:work-runtime-ktx:2.7.1")
+
+ // Networking
+// implementation("com.squareup.okhttp3:okhttp:4.9.2")
+// implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1")
+ implementation("com.github.Blatzar:NiceHttp:0.3.3")
+
+ // Util to skip the URI file fuckery 🙏
+ implementation("com.github.tachiyomiorg:unifile:17bec43")
+
+ // API because cba maintaining it myself
+ implementation("com.uwetrottmann.tmdb2:tmdb-java:2.6.0")
+
+ implementation("com.github.discord:OverlappingPanels:0.1.3")
+ // debugImplementation because LeakCanary should only run in debug builds.
+ // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
+
+ // for shimmer when loading
+ implementation("com.facebook.shimmer:shimmer:0.5.0")
+
+ implementation("androidx.tvprovider:tvprovider:1.0.0")
+
+ // used for subtitle decoding https://github.com/albfernandez/juniversalchardet
+ implementation("com.github.albfernandez:juniversalchardet:2.4.0")
+
+ // slow af yt
+ //implementation("com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT")
+
+ // newpipe yt
+ implementation("com.github.TeamNewPipe:NewPipeExtractor:dev-SNAPSHOT")
+ coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
+
+ // Library/extensions searching with Levenshtein distance
+ implementation("me.xdrop:fuzzywuzzy:1.4.0")
+}
+
+tasks.register("androidSourcesJar", Jar::class) {
+ archiveClassifier.set("sources")
+ from(android.sourceSets.getByName("main").java.srcDirs) //full sources
+}
+
+// this is used by the gradlew plugin
+tasks.register("makeJar", Copy::class) {
+ from("build/intermediates/compile_app_classes_jar/prereleaseDebug")
+ into("build")
+ include("classes.jar")
+ dependsOn("build")
+}
+
+tasks.withType().configureEach {
+ moduleName.set("Cloudstream")
+ dokkaSourceSets {
+ named("main") {
+ sourceLink {
+ // Unix based directory relative path to the root of the project (where you execute gradle respectively).
+ localDirectory.set(file("src/main/java"))
+
+ // 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")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 481bb434..ff59496d 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
+# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt
index 201ddea3..2a3e13e5 100644
--- a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt
@@ -180,7 +180,7 @@ class ExampleInstrumentedTest {
@Test
fun providerCorrectHomepage() {
runBlocking {
- getAllProviders().apmap { api ->
+ getAllProviders().amap { api ->
if (api.hasMainPage) {
try {
val homepage = api.getMainPage()
@@ -217,7 +217,7 @@ class ExampleInstrumentedTest {
runBlocking {
val invalidProvider = ArrayList>()
val providers = getAllProviders()
- providers.apmap { api ->
+ providers.amap { api ->
try {
println("Trying $api")
if (testSingleProviderApi(api)) {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 216a5f21..47676059 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,7 +1,6 @@
+ xmlns:tools="http://schemas.android.com/tools">
@@ -27,6 +26,7 @@
+
+ /*val trailers = trailerUrls.filter { it.isNotBlank() }.amap { trailerUrl ->
val links = arrayListOf()
val subs = arrayListOf()
if (!loadExtractor(
diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
index a281b0a4..5e876ab4 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
@@ -600,7 +600,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
api.init()
}
- inAppAuths.apmap { api ->
+ inAppAuths.amap { api ->
try {
api.initialize()
} catch (e: Exception) {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt b/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt
index badb6631..46955427 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt
@@ -1,8 +1,7 @@
package com.lagradost.cloudstream3
import com.lagradost.cloudstream3.mvvm.logError
-import kotlinx.coroutines.async
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.*
//https://stackoverflow.com/questions/34697828/parallel-operations-on-kotlin-collections
/*
@@ -26,10 +25,25 @@ fun Iterable.pmap(
return ArrayList(destination)
}*/
+
+@OptIn(DelicateCoroutinesApi::class)
+suspend fun Map.amap(f: suspend (Map.Entry) -> R): List =
+ with(CoroutineScope(GlobalScope.coroutineContext)) {
+ map { async { f(it) } }.map { it.await() }
+ }
+
fun Map.apmap(f: suspend (Map.Entry) -> R): List = runBlocking {
map { async { f(it) } }.map { it.await() }
}
+
+@OptIn(DelicateCoroutinesApi::class)
+suspend fun List.amap(f: suspend (A) -> B): List =
+ with(CoroutineScope(GlobalScope.coroutineContext)) {
+ map { async { f(it) } }.map { it.await() }
+ }
+
+
fun List.apmap(f: suspend (A) -> B): List = runBlocking {
map { async { f(it) } }.map { it.await() }
}
@@ -38,6 +52,12 @@ fun List.apmapIndexed(f: suspend (index: Int, A) -> B): List = runB
mapIndexed { index, a -> async { f(index, a) } }.map { it.await() }
}
+@OptIn(DelicateCoroutinesApi::class)
+suspend fun List.amapIndexed(f: suspend (index: Int, A) -> B): List =
+ with(CoroutineScope(GlobalScope.coroutineContext)) {
+ mapIndexed { index, a -> async { f(index, a) } }.map { it.await() }
+ }
+
// run code in parallel
/*fun argpmap(
vararg transforms: () -> R,
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt
index 16b109be..0d5a5c78 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt
@@ -1,6 +1,6 @@
package com.lagradost.cloudstream3.extractors
-import com.lagradost.cloudstream3.apmap
+import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
@@ -21,10 +21,10 @@ class Fastream: ExtractorApi() {
Pair("file_code",id),
Pair("auto","1")
)).document
- response.select("script").apmap { script ->
+ response.select("script").amap { script ->
if (script.data().contains("sources")) {
val m3u8regex = Regex("((https:|http:)\\/\\/.*\\.m3u8)")
- val m3u8 = m3u8regex.find(script.data())?.value ?: return@apmap
+ val m3u8 = m3u8regex.find(script.data())?.value ?: return@amap
generateM3u8(
name,
m3u8,
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt
index dfccc118..df9c74a4 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt
@@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.extractors
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.*
+import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import org.jsoup.nodes.Element
import java.security.DigestException
import java.security.MessageDigest
@@ -10,43 +11,47 @@ import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
+class DatabaseGdrive2 : Gdriveplayer() {
+ override var mainUrl = "https://databasegdriveplayer.co"
+}
+
class DatabaseGdrive : Gdriveplayer() {
override var mainUrl = "https://series.databasegdriveplayer.co"
}
-class Gdriveplayerapi: Gdriveplayer() {
+class Gdriveplayerapi : Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayerapi.com"
}
-class Gdriveplayerapp: Gdriveplayer() {
+class Gdriveplayerapp : Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.app"
}
-class Gdriveplayerfun: Gdriveplayer() {
+class Gdriveplayerfun : Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.fun"
}
-class Gdriveplayerio: Gdriveplayer() {
+class Gdriveplayerio : Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.io"
}
-class Gdriveplayerme: Gdriveplayer() {
+class Gdriveplayerme : Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.me"
}
-class Gdriveplayerbiz: Gdriveplayer() {
+class Gdriveplayerbiz : Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.biz"
}
-class Gdriveplayerorg: Gdriveplayer() {
+class Gdriveplayerorg : Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.org"
}
-class Gdriveplayerus: Gdriveplayer() {
+class Gdriveplayerus : Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.us"
}
-class Gdriveplayerco: Gdriveplayer() {
+class Gdriveplayerco : Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.co"
}
@@ -136,6 +141,10 @@ open class Gdriveplayer : ExtractorApi() {
return find(str)?.groupValues?.getOrNull(1)
}
+ private fun String.addMarks(str: String): String {
+ return this.replace(Regex("\"?$str\"?"), "\"$str\"")
+ }
+
override suspend fun getUrl(
url: String,
referer: String?,
@@ -145,18 +154,19 @@ open class Gdriveplayer : ExtractorApi() {
val document = app.get(url).document
val eval = unpackJs(document)?.replace("\\", "") ?: return
- val data = AppUtils.tryParseJson(Regex("data='(\\S+?)'").first(eval)) ?: return
+ val data = tryParseJson(Regex("data='(\\S+?)'").first(eval)) ?: return
val password = Regex("null,['|\"](\\w+)['|\"]").first(eval)
?.split(Regex("\\D+"))
?.joinToString("") {
Char(it.toInt()).toString()
}.let { Regex("var pass = \"(\\S+?)\"").first(it ?: return)?.toByteArray() }
?: throw ErrorLoadingException("can't find password")
- val decryptedData =
- cryptoAESHandler(data, password, false)?.let { getAndUnpack(it) }?.replace("\\", "")
- ?.substringAfter("sources:[")?.substringBefore("],")
+ val decryptedData = cryptoAESHandler(data, password, false)?.let { getAndUnpack(it) }?.replace("\\", "")
- Regex("\"file\":\"(\\S+?)\".*?res=(\\d+)").findAll(decryptedData ?: return).map {
+ val sourceData = decryptedData?.substringAfter("sources:[")?.substringBefore("],")
+ val subData = decryptedData?.substringAfter("tracks:[")?.substringBefore("],")
+
+ Regex("\"file\":\"(\\S+?)\".*?res=(\\d+)").findAll(sourceData ?: return).map {
it.groupValues[1] to it.groupValues[2]
}.toList().distinctBy { it.second }.map { (link, quality) ->
callback.invoke(
@@ -171,6 +181,17 @@ open class Gdriveplayer : ExtractorApi() {
)
}
+ subData?.addMarks("file")?.addMarks("kind")?.addMarks("label").let { dataSub ->
+ tryParseJson>("[$dataSub]")?.map { sub ->
+ subtitleCallback.invoke(
+ SubtitleFile(
+ sub.label,
+ httpsify(sub.file)
+ )
+ )
+ }
+ }
+
}
data class AesData(
@@ -179,4 +200,10 @@ open class Gdriveplayer : ExtractorApi() {
@JsonProperty("s") val s: String
)
+ data class Tracks(
+ @JsonProperty("file") val file: String,
+ @JsonProperty("kind") val kind: String,
+ @JsonProperty("label") val label: String
+ )
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Moviehab.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Moviehab.kt
new file mode 100644
index 00000000..e2eb7bf0
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Moviehab.kt
@@ -0,0 +1,40 @@
+package com.lagradost.cloudstream3.extractors
+
+import com.lagradost.cloudstream3.SubtitleFile
+import com.lagradost.cloudstream3.app
+import com.lagradost.cloudstream3.utils.ExtractorApi
+import com.lagradost.cloudstream3.utils.ExtractorLink
+import com.lagradost.cloudstream3.utils.M3u8Helper
+
+class Moviehab : ExtractorApi() {
+ override var name = "Moviehab"
+ override var mainUrl = "https://play.moviehab.com"
+ override val requiresReferer = false
+
+ override suspend fun getUrl(
+ url: String,
+ referer: String?,
+ subtitleCallback: (SubtitleFile) -> Unit,
+ callback: (ExtractorLink) -> Unit
+ ) {
+ val res = app.get(url)
+ res.document.select("video#player").let {
+ //should redirect first for making it works
+ val link = app.get("$mainUrl/${it.select("source").attr("src")}", referer = url).url
+ M3u8Helper.generateM3u8(
+ this.name,
+ link,
+ url
+ ).forEach(callback)
+
+ Regex("src[\"|'],\\s[\"|'](\\S+)[\"|']\\)").find(res.text)?.groupValues?.get(1).let {sub ->
+ subtitleCallback.invoke(
+ SubtitleFile(
+ it.select("track").attr("label"),
+ "$mainUrl/$sub"
+ )
+ )
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt
index cc743d5e..de469b22 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt
@@ -1,7 +1,7 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
-import com.lagradost.cloudstream3.apmap
+import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.utils.ExtractorLink
@@ -35,7 +35,7 @@ class Pelisplus(val mainUrl: String) {
callback: (ExtractorLink) -> Unit
): Boolean {
try {
- normalApis.apmap { api ->
+ normalApis.amap { api ->
val url = api.getExtractorUrl(id)
api.getSafeUrl(url, subtitleCallback = subtitleCallback, callback = callback)
}
@@ -51,8 +51,8 @@ class Pelisplus(val mainUrl: String) {
val qualityRegex = Regex("(\\d+)P")
//a[download]
- pageDoc.select(".dowload > a")?.apmap { element ->
- val href = element.attr("href") ?: return@apmap
+ pageDoc.select(".dowload > a")?.amap { element ->
+ val href = element.attr("href") ?: return@amap
val qual = if (element.text()
.contains("HDP")
) "1080" else qualityRegex.find(element.text())?.destructured?.component1()
@@ -84,7 +84,7 @@ class Pelisplus(val mainUrl: String) {
//val name = element.text()
// Matches vidstream links with extractors
- extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api ->
+ extractorApis.filter { !it.requiresReferer || !isCasting }.amap { api ->
if (link.startsWith(api.mainUrl)) {
api.getSafeUrl(link, extractorUrl, subtitleCallback, callback)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt
index 6153a7c1..8ef6c463 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt
@@ -7,7 +7,11 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
-class SpeedoStream : ExtractorApi() {
+class SpeedoStream1 : SpeedoStream() {
+ override val mainUrl = "https://speedostream.nl"
+}
+
+open class SpeedoStream : ExtractorApi() {
override val name = "SpeedoStream"
override val mainUrl = "https://speedostream.com"
override val requiresReferer = true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt
index 30a0496d..913b410b 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt
@@ -7,6 +7,11 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
+class Sbspeed : StreamSB() {
+ override var name = "Sbspeed"
+ override var mainUrl = "https://sbspeed.com"
+}
+
class Streamsss : StreamSB() {
override var mainUrl = "https://streamsss.net"
}
@@ -93,15 +98,15 @@ open class StreamSB : ExtractorApi() {
}
data class Subs (
- @JsonProperty("file") val file: String,
- @JsonProperty("label") val label: String,
+ @JsonProperty("file") val file: String? = null,
+ @JsonProperty("label") val label: String? = null,
)
data class StreamData (
@JsonProperty("file") val file: String,
@JsonProperty("cdn_img") val cdnImg: String,
@JsonProperty("hash") val hash: String,
- @JsonProperty("subs") val subs: List?,
+ @JsonProperty("subs") val subs: ArrayList? = arrayListOf(),
@JsonProperty("length") val length: String,
@JsonProperty("id") val id: String,
@JsonProperty("title") val title: String,
@@ -141,5 +146,14 @@ open class StreamSB : ExtractorApi() {
url,
headers = headers
).forEach(callback)
+
+ mapped.streamData.subs?.map {sub ->
+ subtitleCallback.invoke(
+ SubtitleFile(
+ sub.label.toString(),
+ sub.file ?: return@map null,
+ )
+ )
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt
index 63634704..b910f9dd 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt
@@ -1,7 +1,7 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
-import com.lagradost.cloudstream3.apmap
+import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.*
import kotlinx.coroutines.delay
@@ -69,12 +69,12 @@ open class VidSrcExtractor : ExtractorApi() {
} else ""
}
- serverslist.apmap { server ->
+ serverslist.amap { server ->
val linkfixed = server.replace("https://vidsrc.xyz/", "https://embedsito.com/")
if (linkfixed.contains("/pro")) {
val srcresponse = app.get(server, referer = absoluteUrl).text
val m3u8Regex = Regex("((https:|http:)//.*\\.m3u8)")
- val srcm3u8 = m3u8Regex.find(srcresponse)?.value ?: return@apmap
+ val srcm3u8 = m3u8Regex.find(srcresponse)?.value ?: return@amap
val passRegex = Regex("""['"](.*set_pass[^"']*)""")
val pass = passRegex.find(srcresponse)?.groupValues?.get(1)?.replace(
Regex("""^//"""), "https://"
@@ -85,18 +85,12 @@ open class VidSrcExtractor : ExtractorApi() {
this.name,
this.name,
srcm3u8,
- this.mainUrl,
+ "https://vidsrc.stream/",
Qualities.Unknown.value,
extractorData = pass,
isM3u8 = true
)
)
-
-// M3u8Helper.generateM3u8(
-// name,
-// srcm3u8,
-// absoluteUrl
-// ).forEach(callback)
} else {
loadExtractor(linkfixed, url, subtitleCallback, callback)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt
index 1d853b2d..7eb7fbac 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt
@@ -1,7 +1,7 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
-import com.lagradost.cloudstream3.apmap
+import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.argamap
import com.lagradost.cloudstream3.utils.ExtractorLink
@@ -37,7 +37,7 @@ class Vidstream(val mainUrl: String) {
val extractorUrl = getExtractorUrl(id)
argamap(
{
- normalApis.apmap { api ->
+ normalApis.amap { api ->
val url = api.getExtractorUrl(id)
api.getSafeUrl(
url,
@@ -55,8 +55,8 @@ class Vidstream(val mainUrl: String) {
val qualityRegex = Regex("(\\d+)P")
//a[download]
- pageDoc.select(".dowload > a")?.apmap { element ->
- val href = element.attr("href") ?: return@apmap
+ pageDoc.select(".dowload > a")?.amap { element ->
+ val href = element.attr("href") ?: return@amap
val qual = if (element.text()
.contains("HDP")
) "1080" else qualityRegex.find(element.text())?.destructured?.component1()
@@ -87,7 +87,7 @@ class Vidstream(val mainUrl: String) {
//val name = element.text()
// Matches vidstream links with extractors
- extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api ->
+ extractorApis.filter { !it.requiresReferer || !isCasting }.amap { api ->
if (link.startsWith(api.mainUrl)) {
api.getSafeUrl(link, extractorUrl, subtitleCallback, callback)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt
index 9e3585ae..15ff0436 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt
@@ -1,12 +1,23 @@
package com.lagradost.cloudstream3.extractors
import com.fasterxml.jackson.annotation.JsonProperty
+import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.utils.AppUtils
+import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName
+class Cdnplayer: XStreamCdn() {
+ override val name: String = "Cdnplayer"
+ override val mainUrl: String = "https://cdnplayer.online"
+}
+
+class Kotakajair: XStreamCdn() {
+ override val name: String = "Kotakajair"
+ override val mainUrl: String = "https://kotakajair.xyz"
+}
+
class FEnet: XStreamCdn() {
override val name: String = "FEnet"
override val mainUrl: String = "https://fembed.net"
@@ -59,44 +70,67 @@ open class XStreamCdn : ExtractorApi() {
//val type: String // Mp4
)
+ private data class Player(
+ @JsonProperty("poster_file") val poster_file: String? = null,
+ )
+
private data class ResponseJson(
@JsonProperty("success") val success: Boolean,
- @JsonProperty("data") val data: List?
+ @JsonProperty("player") val player: Player? = null,
+ @JsonProperty("data") val data: List?,
+ @JsonProperty("captions") val captions: List?,
+ )
+
+ private data class Captions(
+ @JsonProperty("id") val id: String,
+ @JsonProperty("hash") val hash: String,
+ @JsonProperty("language") val language: String,
+ @JsonProperty("extension") val extension: String
)
override fun getExtractorUrl(id: String): String {
return "$domainUrl/api/source/$id"
}
- override suspend fun getUrl(url: String, referer: String?): List {
+ override suspend fun getUrl(
+ url: String,
+ referer: String?,
+ subtitleCallback: (SubtitleFile) -> Unit,
+ callback: (ExtractorLink) -> Unit
+ ) {
val headers = mapOf(
"Referer" to url,
"User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0",
)
val id = url.trimEnd('/').split("/").last()
val newUrl = "https://${domainUrl}/api/source/${id}"
- val extractedLinksList: MutableList = mutableListOf()
- with(app.post(newUrl, headers = headers)) {
- if (this.code != 200) return listOf()
- val text = this.text
- if (text.isEmpty()) return listOf()
- if (text == """{"success":false,"data":"Video not found or has been removed"}""") return listOf()
- AppUtils.parseJson(text)?.let {
+ app.post(newUrl, headers = headers).let { res ->
+ val sources = tryParseJson(res.text)
+ sources?.let {
if (it.success && it.data != null) {
- it.data.forEach { data ->
- extractedLinksList.add(
+ it.data.map { source ->
+ callback.invoke(
ExtractorLink(
name,
name = name,
- data.file,
+ source.file,
url,
- getQualityFromName(data.label),
+ getQualityFromName(source.label),
)
)
}
}
}
+
+ val userData = sources?.player?.poster_file?.split("/")?.get(2)
+ sources?.captions?.map {
+ subtitleCallback.invoke(
+ SubtitleFile(
+ it?.language.toString(),
+ "$mainUrl/asset/userdata/$userData/caption/${it?.hash}/${it?.id}.${it?.extension}"
+ )
+ )
+ }
}
- return extractedLinksList
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Zplayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Zplayer.kt
index 6108d2c5..d17b427d 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Zplayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Zplayer.kt
@@ -1,6 +1,6 @@
package com.lagradost.cloudstream3.extractors
-import com.lagradost.cloudstream3.apmap
+import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
@@ -36,7 +36,7 @@ open class ZplayerV2 : ExtractorApi() {
val m3u8regex = Regex("((https:|http:)\\/\\/.*\\.m3u8)")
m3u8regex.findAll(testdata).map {
it.value
- }.toList().apmap { urlm3u8 ->
+ }.toList().amap { urlm3u8 ->
if (urlm3u8.contains("m3u8")) {
val testurl = app.get(urlm3u8, headers = mapOf("Referer" to url)).text
if (testurl.contains("EXTM3U")) {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt
index e70a9474..0b401c06 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt
@@ -2,7 +2,7 @@ package com.lagradost.cloudstream3.extractors.helper
import android.util.Log
import com.lagradost.cloudstream3.SubtitleFile
-import com.lagradost.cloudstream3.apmap
+import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.loadExtractor
@@ -18,7 +18,7 @@ class AsianEmbedHelper {
val doc = app.get(url).document
val links = doc.select("div#list-server-more > ul > li.linkserver")
if (!links.isNullOrEmpty()) {
- links.apmap {
+ links.amap {
val datavid = it.attr("data-video") ?: ""
//Log.i("AsianEmbed", "Result => (datavid) ${datavid}")
if (datavid.isNotBlank()) {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt
index b01d188c..07aa904e 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt
@@ -39,7 +39,7 @@ class CrossTmdbProvider : TmdbProvider() {
): Boolean {
tryParseJson(data)?.let { metaData ->
if (!metaData.isSuccess) return false
- metaData.movies?.apmap { (apiName, data) ->
+ metaData.movies?.amap { (apiName, data) ->
getApiFromNameNull(apiName)?.let {
try {
it.loadLinks(data, isCasting, subtitleCallback, callback)
@@ -64,10 +64,10 @@ class CrossTmdbProvider : TmdbProvider() {
val matchName = filterName(this.name)
when (this) {
is MovieLoadResponse -> {
- val data = validApis.apmap { api ->
+ val data = validApis.amap { api ->
try {
if (api.supportedTypes.contains(TvType.Movie)) { //|| api.supportedTypes.contains(TvType.AnimeMovie)
- return@apmap api.search(this.name)?.first {
+ return@amap api.search(this.name)?.first {
if (filterName(it.name).equals(
matchName,
ignoreCase = true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt
index 0ab44b68..e8ac1876 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt
@@ -45,7 +45,7 @@ class MultiAnimeProvider : MainAPI() {
override suspend fun load(url: String): LoadResponse? {
return syncApi.getResult(url)?.let { res ->
- val data = SyncUtil.getUrlsFromId(res.id, syncUtilType).apmap { url ->
+ val data = SyncUtil.getUrlsFromId(res.id, syncUtilType).amap { url ->
validApis.firstOrNull { api -> url.startsWith(api.mainUrl) }?.load(url)
}.filterNotNull()
diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt b/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt
index 7b4343d9..9171aed9 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt
@@ -7,7 +7,9 @@ import com.lagradost.cloudstream3.AcraApplication
import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.app
+import com.lagradost.cloudstream3.mvvm.debugException
import com.lagradost.cloudstream3.mvvm.logError
+import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.Coroutines.mainWork
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
@@ -65,9 +67,15 @@ class WebViewResolver(
method: String = "GET",
requestCallBack: (Request) -> Boolean = { false },
): Pair> {
- return resolveUsingWebView(
- requestCreator(method, url, referer = referer), requestCallBack
- )
+ return try {
+ resolveUsingWebView(
+ requestCreator(method, url, referer = referer), requestCallBack
+ )
+ } catch (e: java.lang.IllegalArgumentException) {
+ logError(e)
+ debugException { "ILLEGAL URL IN resolveUsingWebView!" }
+ return null to emptyList()
+ }
}
/**
@@ -129,7 +137,7 @@ class WebViewResolver(
println("Loading WebView URL: $webViewUrl")
if (interceptUrl.containsMatchIn(webViewUrl)) {
- fixedRequest = request.toRequest().also {
+ fixedRequest = request.toRequest()?.also {
requestCallBack(it)
}
println("Web-view request finished: $webViewUrl")
@@ -138,9 +146,9 @@ class WebViewResolver(
}
if (additionalUrls.any { it.containsMatchIn(webViewUrl) }) {
- extraRequestList.add(request.toRequest().also {
+ request.toRequest()?.also {
if (requestCallBack(it)) destroyWebView()
- })
+ }?.let(extraRequestList::add)
}
// Suppress image requests as we don't display them anywhere
@@ -251,14 +259,19 @@ class WebViewResolver(
}
-fun WebResourceRequest.toRequest(): Request {
+fun WebResourceRequest.toRequest(): Request? {
val webViewUrl = this.url.toString()
- return requestCreator(
- this.method,
- webViewUrl,
- this.requestHeaders,
- )
+ // If invalid url then it can crash with
+ // java.lang.IllegalArgumentException: Expected URL scheme 'http' or 'https' but was 'data'
+ // At Request.Builder().url(addParamsToUrl(url, params))
+ return normalSafeApiCall {
+ requestCreator(
+ this.method,
+ webViewUrl,
+ this.requestHeaders,
+ )
+ }
}
fun Response.toWebResourceResponse(): WebResourceResponse {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt
index 35e041be..c0f35601 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt
@@ -217,18 +217,17 @@ object PluginManager {
* 3. If outdated download and load the plugin
* 4. Else load the plugin normally
**/
- fun updateAllOnlinePluginsAndLoadThem(activity: Activity) {
+ fun updateAllOnlinePluginsAndLoadThem(activity: Activity) = ioSafe {
// Load all plugins as fast as possible!
loadAllOnlinePlugins(activity)
- ioSafe {
afterPluginsLoadedEvent.invoke(true)
- }
+
val urls = (getKey>(REPOSITORIES_KEY)
?: emptyArray()) + PREBUILT_REPOSITORIES
- val onlinePlugins = urls.toList().apmap {
+ val onlinePlugins = urls.toList().amap {
getRepoPlugins(it.url)?.toList() ?: emptyList()
}.flatten().distinctBy { it.second.url }
@@ -249,7 +248,7 @@ object PluginManager {
val updatedPlugins = mutableListOf()
- outdatedPlugins.apmap { pluginData ->
+ outdatedPlugins.amap { pluginData ->
if (pluginData.isDisabled) {
//updatedPlugins.add(activity.getString(R.string.single_plugin_disabled, pluginData.onlineData.second.name))
unloadPlugin(pluginData.savedData.filePath)
@@ -270,9 +269,9 @@ object PluginManager {
createNotification(activity, updatedPlugins)
}
- ioSafe {
+ // ioSafe {
afterPluginsLoadedEvent.invoke(true)
- }
+ // }
Log.i(TAG, "Plugin update done!")
}
@@ -280,9 +279,9 @@ object PluginManager {
/**
* Use updateAllOnlinePluginsAndLoadThem
* */
- fun loadAllOnlinePlugins(activity: Activity) {
+ fun loadAllOnlinePlugins(activity: Activity) = ioSafe {
// Load all plugins as fast as possible!
- (getPluginsOnline()).toList().apmap { pluginData ->
+ (getPluginsOnline()).toList().amap { pluginData ->
loadPlugin(
activity,
File(pluginData.filePath),
@@ -291,7 +290,7 @@ object PluginManager {
}
}
- fun loadAllLocalPlugins(activity: Activity) {
+ fun loadAllLocalPlugins(activity: Activity) = ioSafe {
val dir = File(LOCAL_PLUGINS_PATH)
removeKey(PLUGINS_KEY_LOCAL)
@@ -299,7 +298,7 @@ object PluginManager {
val res = dir.mkdirs()
if (!res) {
Log.w(TAG, "Failed to create local directories")
- return
+ return@ioSafe
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt
index e3203787..2564abd0 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt
@@ -4,7 +4,7 @@ import android.content.Context
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
-import com.lagradost.cloudstream3.apmap
+import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
@@ -95,7 +95,7 @@ object RepositoryManager {
* */
suspend fun getRepoPlugins(repositoryUrl: String): List>? {
val repo = parseRepository(repositoryUrl) ?: return null
- return repo.pluginLists.apmap { url ->
+ return repo.pluginLists.amap { url ->
parsePlugins(url).map {
repositoryUrl to it
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt
index 0e5e544b..ef50019c 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt
@@ -6,6 +6,10 @@ import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.utils.ExtractorLink
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.GlobalScope.coroutineContext
+import kotlinx.coroutines.async
import kotlinx.coroutines.delay
class APIRepository(val api: MainAPI) {
@@ -26,6 +30,8 @@ class APIRepository(val api: MainAPI) {
fun isInvalidData(data: String): Boolean {
return data.isEmpty() || data == "[]" || data == "about:blank"
}
+
+ private val cacheHash: HashMap, LoadResponse> = hashMapOf()
}
val hasMainPage = api.hasMainPage
@@ -39,7 +45,13 @@ class APIRepository(val api: MainAPI) {
suspend fun load(url: String): Resource {
return safeApiCall {
if (isInvalidData(url)) throw ErrorLoadingException()
- api.load(api.fixUrl(url)) ?: throw ErrorLoadingException()
+ val fixedUrl = api.fixUrl(url)
+ val key = Pair(api.name, url)
+ cacheHash[key] ?: api.load(fixedUrl)?.also {
+ // we cache 20 responses because ppl often go back to the same shit + 20 because I dont want to cause too much memory leak
+ if (cacheHash.size > 20) cacheHash.remove(cacheHash.keys.random())
+ cacheHash[key] = it
+ } ?: throw ErrorLoadingException()
}
}
@@ -70,12 +82,18 @@ class APIRepository(val api: MainAPI) {
delay(delta)
}
+ @OptIn(DelicateCoroutinesApi::class)
suspend fun getMainPage(page: Int, nameIndex: Int? = null): Resource> {
return safeApiCall {
api.lastHomepageRequest = unixTimeMS
nameIndex?.let { api.mainPage.getOrNull(it) }?.let { data ->
- listOf(api.getMainPage(page, MainPageRequest(data.name, data.data, data.horizontalImages)))
+ listOf(
+ api.getMainPage(
+ page,
+ MainPageRequest(data.name, data.data, data.horizontalImages)
+ )
+ )
} ?: run {
if (api.sequentialMainPage) {
var first = true
@@ -90,11 +108,15 @@ class APIRepository(val api: MainAPI) {
)
}
} else {
- api.mainPage.apmap { data ->
- api.getMainPage(
- page,
- MainPageRequest(data.name, data.data, data.horizontalImages)
- )
+ with(CoroutineScope(coroutineContext)) {
+ api.mainPage.map { data ->
+ async {
+ api.getMainPage(
+ page,
+ MainPageRequest(data.name, data.data, data.horizontalImages)
+ )
+ }
+ }.map { it.await() }
}
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/WatchType.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/WatchType.kt
index 417b4408..eb4eb666 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/WatchType.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/WatchType.kt
@@ -5,13 +5,12 @@ import androidx.annotation.StringRes
import com.lagradost.cloudstream3.R
enum class WatchType(val internalId: Int, @StringRes val stringRes: Int, @DrawableRes val iconRes: Int) {
- // FIX ICONS
- WATCHING(0, R.string.type_watching, R.drawable.ic_baseline_remove_red_eye_24),
- COMPLETED(1, R.string.type_completed, R.drawable.ic_baseline_check_24),
- ONHOLD(2, R.string.type_on_hold, R.drawable.ic_baseline_pause_24),
- DROPPED(3, R.string.type_dropped, R.drawable.ic_baseline_close_24),
- PLANTOWATCH(4, R.string.type_plan_to_watch, R.drawable.ic_baseline_close_24),
- NONE(5, R.string.type_none, R.drawable.ic_baseline_remove_red_eye_24);
+ WATCHING(0, R.string.type_watching, R.drawable.ic_baseline_bookmark_24),
+ COMPLETED(1, R.string.type_completed, R.drawable.ic_baseline_bookmark_24),
+ ONHOLD(2, R.string.type_on_hold, R.drawable.ic_baseline_bookmark_24),
+ DROPPED(3, R.string.type_dropped, R.drawable.ic_baseline_bookmark_24),
+ PLANTOWATCH(4, R.string.type_plan_to_watch, R.drawable.ic_baseline_bookmark_24),
+ NONE(5, R.string.type_none, R.drawable.ic_baseline_add_24);
companion object {
fun fromInternalId(id: Int?) = values().find { value -> value.internalId == id } ?: NONE
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt
index 0d571b76..9792fa43 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt
@@ -14,6 +14,7 @@ import android.view.ViewGroup
import android.widget.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SearchView
+import androidx.core.content.ContextCompat.getDrawable
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
@@ -21,16 +22,18 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.LinearSnapHelper
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
+import com.google.android.material.chip.Chip
+import com.google.android.material.chip.ChipGroup
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
+import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.MainActivity.Companion.mainPluginsLoadedEvent
@@ -43,6 +46,7 @@ import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
+import com.lagradost.cloudstream3.ui.result.ResultViewModel2.Companion.updateWatchStatus
import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.search.*
@@ -50,6 +54,7 @@ import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallba
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable
+import com.lagradost.cloudstream3.utils.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.AppUtils.setMaxViewPoolSize
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
@@ -61,11 +66,12 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.deleteAllResumeStateIds
import com.lagradost.cloudstream3.utils.DataStoreHelper.removeLastWatched
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState
import com.lagradost.cloudstream3.utils.Event
+import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
-import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView
+import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.UIHelper.setImage
@@ -82,14 +88,12 @@ import kotlinx.android.synthetic.main.fragment_home.home_loading
import kotlinx.android.synthetic.main.fragment_home.home_loading_error
import kotlinx.android.synthetic.main.fragment_home.home_loading_shimmer
import kotlinx.android.synthetic.main.fragment_home.home_loading_statusbar
-import kotlinx.android.synthetic.main.fragment_home.home_main_poster_recyclerview
import kotlinx.android.synthetic.main.fragment_home.home_master_recycler
import kotlinx.android.synthetic.main.fragment_home.home_plan_to_watch_btt
import kotlinx.android.synthetic.main.fragment_home.home_provider_meta_info
import kotlinx.android.synthetic.main.fragment_home.home_provider_name
import kotlinx.android.synthetic.main.fragment_home.home_reload_connection_open_in_browser
import kotlinx.android.synthetic.main.fragment_home.home_reload_connectionerror
-import kotlinx.android.synthetic.main.fragment_home.home_statusbar
import kotlinx.android.synthetic.main.fragment_home.home_type_completed_btt
import kotlinx.android.synthetic.main.fragment_home.home_type_dropped_btt
import kotlinx.android.synthetic.main.fragment_home.home_type_on_hold_btt
@@ -99,9 +103,13 @@ import kotlinx.android.synthetic.main.fragment_home.home_watch_holder
import kotlinx.android.synthetic.main.fragment_home.home_watch_parent_item_title
import kotlinx.android.synthetic.main.fragment_home.result_error_text
import kotlinx.android.synthetic.main.fragment_home_tv.*
+import kotlinx.android.synthetic.main.fragment_search.*
import kotlinx.android.synthetic.main.home_episodes_expanded.*
+import kotlinx.android.synthetic.main.tvtypes_chips.*
+import kotlinx.android.synthetic.main.tvtypes_chips.view.*
import java.util.*
+
const val HOME_BOOKMARK_VALUE_LIST = "home_bookmarked_last_list"
const val HOME_PREF_HOMEPAGE = "home_pref_homepage"
@@ -247,16 +255,16 @@ class HomeFragment : Fragment() {
}
fun getPairList(
- anime: MaterialButton?,
- cartoons: MaterialButton?,
- tvs: MaterialButton?,
- docs: MaterialButton?,
- movies: MaterialButton?,
- asian: MaterialButton?,
- livestream: MaterialButton?,
- nsfw: MaterialButton?,
- others: MaterialButton?,
- ): List>> {
+ anime: Chip?,
+ cartoons: Chip?,
+ tvs: Chip?,
+ docs: Chip?,
+ movies: Chip?,
+ asian: Chip?,
+ livestream: Chip?,
+ nsfw: Chip?,
+ others: Chip?,
+ ): List>> {
// This list should be same order as home screen to aid navigation
return listOf(
Pair(movies, listOf(TvType.Movie, TvType.Torrent)),
@@ -271,6 +279,59 @@ class HomeFragment : Fragment() {
)
}
+ private fun getPairList(header: ChipGroup) = getPairList(
+ header.home_select_anime,
+ header.home_select_cartoons,
+ header.home_select_tv_series,
+ header.home_select_documentaries,
+ header.home_select_movies,
+ header.home_select_asian,
+ header.home_select_livestreams,
+ header.home_select_nsfw,
+ header.home_select_others
+ )
+
+ fun validateChips(header: ChipGroup?, validTypes: List) {
+ if (header == null) return
+ val pairList = getPairList(header)
+ for ((button, types) in pairList) {
+ val isValid = validTypes.any { types.contains(it) }
+ button?.isVisible = isValid
+ }
+ }
+
+ fun updateChips(header: ChipGroup?, selectedTypes: List) {
+ if (header == null) return
+ val pairList = getPairList(header)
+ for ((button, types) in pairList) {
+ button?.isChecked =
+ button?.isVisible == true && selectedTypes.any { types.contains(it) }
+ }
+ }
+
+ fun bindChips(
+ header: ChipGroup?,
+ selectedTypes: List,
+ validTypes: List,
+ callback: (List) -> Unit
+ ) {
+ if (header == null) return
+ val pairList = getPairList(header)
+ for ((button, types) in pairList) {
+ val isValid = validTypes.any { types.contains(it) }
+ button?.isVisible = isValid
+ button?.isChecked = isValid && selectedTypes.any { types.contains(it) }
+ button?.setOnCheckedChangeListener { _, _ ->
+ val list = ArrayList()
+ for ((sbutton, vvalidTypes) in pairList) {
+ if (sbutton?.isChecked == true)
+ list.addAll(vvalidTypes)
+ }
+ callback(list)
+ }
+ }
+ }
+
fun Context.selectHomepage(selectedApiName: String?, callback: (String) -> Unit) {
val validAPIs = filterProviderByPreferredMedia().toMutableList()
@@ -296,21 +357,9 @@ class HomeFragment : Fragment() {
?.toMutableList()
?: mutableListOf(TvType.Movie, TvType.TvSeries)
- val anime = dialog.findViewById(R.id.home_select_anime)
- val cartoons = dialog.findViewById(R.id.home_select_cartoons)
- val tvs = dialog.findViewById(R.id.home_select_tv_series)
- val docs = dialog.findViewById(R.id.home_select_documentaries)
- val movies = dialog.findViewById(R.id.home_select_movies)
- val asian = dialog.findViewById(R.id.home_select_asian)
- val livestream = dialog.findViewById(R.id.home_select_livestreams)
- val nsfw = dialog.findViewById(R.id.home_select_nsfw)
- val others = dialog.findViewById(R.id.home_select_others)
val cancelBtt = dialog.findViewById(R.id.cancel_btt)
val applyBtt = dialog.findViewById(R.id.apply_btt)
- val pairList =
- getPairList(anime, cartoons, tvs, docs, movies, asian, livestream, nsfw, others)
-
cancelBtt?.setOnClickListener {
dialog.dismissSafe()
}
@@ -355,52 +404,14 @@ class HomeFragment : Fragment() {
arrayAdapter.notifyDataSetChanged()
}
- /**
- * Since fire tv is fucked we need to manually define the focus layout.
- * Since visible buttons are only known in runtime this is required.
- **/
- var lastButton: MaterialButton? = null
-
- for ((button, validTypes) in pairList) {
- val isValid =
- validAPIs.any { api -> validTypes.any { api.supportedTypes.contains(it) } }
- button?.isVisible = isValid
- if (isValid) {
-
- // Set focus navigation
- button?.let { currentButton ->
- lastButton?.nextFocusRightId = currentButton.id
- lastButton?.id?.let { currentButton.nextFocusLeftId = it }
- lastButton = currentButton
- }
-
- fun buttonContains(): Boolean {
- return preSelectedTypes.any { validTypes.contains(it) }
- }
-
- button?.isSelected = buttonContains()
- button?.setOnClickListener {
- preSelectedTypes.clear()
- preSelectedTypes.addAll(validTypes)
- for ((otherButton, _) in pairList) {
- otherButton?.isSelected = false
- }
- button.isSelected = true
- updateList()
- }
-
- button?.setOnLongClickListener {
- if (!buttonContains()) {
- button.isSelected = true
- preSelectedTypes.addAll(validTypes)
- } else {
- button.isSelected = false
- preSelectedTypes.removeAll(validTypes)
- }
- updateList()
- return@setOnLongClickListener true
- }
- }
+ bindChips(
+ dialog.home_select_group,
+ preSelectedTypes,
+ validAPIs.flatMap { it.supportedTypes }.distinct()
+ ) { list ->
+ preSelectedTypes.clear()
+ preSelectedTypes.addAll(list)
+ updateList()
}
updateList()
}
@@ -422,7 +433,6 @@ class HomeFragment : Fragment() {
}
private fun toggleMainVisibility(visible: Boolean) {
- home_main_holder?.isVisible = visible
home_main_poster_recyclerview?.isVisible = visible
}
@@ -531,6 +541,86 @@ class HomeFragment : Fragment() {
home_random?.visibility = View.GONE
}
+ observe(homeViewModel.preview) { preview ->
+ // Always reset the padding, otherwise the will move lower and lower
+ home_watch_holder?.setPadding(0, 0, 0, 0)
+ when (preview) {
+ is Resource.Success -> {
+ home_preview?.isVisible = true
+ (home_preview_viewpager?.adapter as? HomeScrollAdapter)?.apply {
+ setItems(preview.value)
+ // home_preview_viewpager?.setCurrentItem(1000, false)
+ }
+
+ //.also {
+ //home_preview_viewpager?.adapter =
+ //}
+ }
+ else -> {
+ home_preview?.isVisible = false
+ context?.fixPaddingStatusbar(home_watch_holder)
+ }
+ }
+ }
+
+ val searchText =
+ home_search?.findViewById(androidx.appcompat.R.id.search_src_text)
+ searchText?.context?.getResourceColor(R.attr.white)?.let { color ->
+ searchText.setTextColor(color)
+ searchText.setHintTextColor(color)
+ }
+
+ home_preview_viewpager?.apply {
+ setPageTransformer(false, HomeScrollTransformer())
+ adapter = HomeScrollAdapter { load ->
+ load.apply {
+ home_preview_tags?.text = tags?.joinToString(" • ") ?: ""
+ home_preview_tags?.isGone = tags.isNullOrEmpty()
+ home_preview_image?.setImage(posterUrl, posterHeaders)
+ home_preview_title?.text = name
+ home_preview_play?.setOnClickListener {
+ activity?.loadResult(url, apiName, START_ACTION_RESUME_LATEST)
+ //activity.loadSearchResult(url, START_ACTION_RESUME_LATEST)
+ }
+ home_preview_info?.setOnClickListener {
+ activity?.loadResult(url, apiName)
+ //activity.loadSearchResult(random)
+ }
+ // very ugly code, but I dont care
+ val watchType = DataStoreHelper.getResultWatchState(load.getId())
+ home_preview_bookmark?.setText(watchType.stringRes)
+ home_preview_bookmark?.setCompoundDrawablesWithIntrinsicBounds(
+ null,
+ getDrawable(home_preview_bookmark.context, watchType.iconRes),
+ null,
+ null
+ )
+ home_preview_bookmark?.setOnClickListener { fab ->
+ activity?.showBottomDialog(
+ WatchType.values().map { fab.context.getString(it.stringRes) }
+ .toList(),
+ DataStoreHelper.getResultWatchState(load.getId()).ordinal,
+ fab.context.getString(R.string.action_add_to_bookmarks),
+ showApply = false,
+ {}) {
+ val newValue = WatchType.values()[it]
+ home_preview_bookmark?.setCompoundDrawablesWithIntrinsicBounds(
+ null,
+ getDrawable(home_preview_bookmark.context, newValue.iconRes),
+ null,
+ null
+ )
+ home_preview_bookmark?.setText(newValue.stringRes)
+
+ updateWatchStatus(load, newValue)
+ reloadStored()
+ }
+ }
+ }
+
+ }
+ }
+
observe(homeViewModel.apiName) { apiName ->
currentApiName = apiName
// setKey(USER_SELECTED_HOMEPAGE_API, apiName)
@@ -563,17 +653,17 @@ class HomeFragment : Fragment() {
HomeChildItemAdapter(
mutableListOf(),
R.layout.home_result_big_grid,
- nextFocusUp = home_main_poster_recyclerview.nextFocusUpId,
- nextFocusDown = home_main_poster_recyclerview.nextFocusDownId
+ nextFocusUp = home_main_poster_recyclerview?.nextFocusUpId,
+ nextFocusDown = home_main_poster_recyclerview?.nextFocusDownId
) { callback ->
homeHandleSearch(callback)
}
- home_main_poster_recyclerview.setLinearListLayout()
+ home_main_poster_recyclerview?.setLinearListLayout()
observe(homeViewModel.randomItems) { items ->
if (items.isNullOrEmpty()) {
toggleMainVisibility(false)
} else {
- val tempAdapter = home_main_poster_recyclerview.adapter as HomeChildItemAdapter?
+ val tempAdapter = home_main_poster_recyclerview?.adapter as? HomeChildItemAdapter?
// no need to reload if it has the same data
if (tempAdapter != null && tempAdapter.cardList == items) {
toggleMainVisibility(true)
@@ -695,14 +785,32 @@ class HomeFragment : Fragment() {
Pair(home_type_on_hold_btt, WatchType.ONHOLD),
Pair(home_plan_to_watch_btt, WatchType.PLANTOWATCH),
)
+ val currentSet = getKey(HOME_BOOKMARK_VALUE_LIST)
+ ?.map { WatchType.fromInternalId(it) }?.toSet() ?: emptySet()
+
+ for ((chip, watch) in toggleList) {
+ chip.isChecked = currentSet.contains(watch)
+ chip?.setOnCheckedChangeListener { _, isChecked ->
+ if (isChecked) {
+ homeViewModel.loadStoredData(
+ setOf(watch)
+ // If we filter all buttons then two can be checked at the same time
+ // Revert this if you want to go back to multi selection
+// toggleList.filter { it.first?.isChecked == true }.map { it.second }.toSet()
+ )
+ }
+ // Else if all are unchecked -> Do not load data
+ else if (toggleList.all { it.first?.isChecked != true }) {
+ homeViewModel.loadStoredData(emptySet())
+ }
+ }
+ /*chip?.setOnClickListener {
+
- for (item in toggleList) {
- val watch = item.second
- item.first?.setOnClickListener {
homeViewModel.loadStoredData(EnumSet.of(watch))
}
- item.first?.setOnLongClickListener { itemView ->
+ chip?.setOnLongClickListener { itemView ->
val list = EnumSet.noneOf(WatchType::class.java)
itemView.context.getKey(HOME_BOOKMARK_VALUE_LIST)
?.map { WatchType.fromInternalId(it) }?.let {
@@ -716,7 +824,7 @@ class HomeFragment : Fragment() {
}
homeViewModel.loadStoredData(list)
return@setOnLongClickListener true
- }
+ }*/
}
observe(homeViewModel.availableWatchStatusTypes) { availableWatchStatusTypes ->
@@ -938,7 +1046,8 @@ class HomeFragment : Fragment() {
}
}
- context?.fixPaddingStatusbarView(home_statusbar)
+ //context?.fixPaddingStatusbarView(home_statusbar)
+ context?.fixPaddingStatusbar(home_padding)
context?.fixPaddingStatusbar(home_loading_statusbar)
home_master_recycler.adapter =
@@ -959,33 +1068,6 @@ class HomeFragment : Fragment() {
}
} // GridLayoutManager(context, 1).also { it.supportsPredictiveItemAnimations() }
- if (!isTvSettings()) {
- LinearSnapHelper().attachToRecyclerView(home_main_poster_recyclerview) // snap
- val centerLayoutManager =
- CenterZoomLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
- centerLayoutManager.setOnSizeListener { index ->
- (home_main_poster_recyclerview?.adapter as HomeChildItemAdapter?)?.cardList?.get(
- index
- )?.let { random ->
- home_main_play?.setOnClickListener {
- activity.loadSearchResult(random, START_ACTION_RESUME_LATEST)
- }
- home_main_info?.setOnClickListener {
- activity.loadSearchResult(random)
- }
-
- home_main_text?.text =
- random.name + if (random is AnimeSearchResponse && !random.dubStatus.isNullOrEmpty()) {
- random.dubStatus?.joinToString(
- prefix = " • ",
- separator = " | "
- ) { it.name }
- } else ""
- }
- }
- home_main_poster_recyclerview?.layoutManager = centerLayoutManager // scale
- }
-
reloadStored()
loadHomePage()
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt
new file mode 100644
index 00000000..b36d7582
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt
@@ -0,0 +1,60 @@
+package com.lagradost.cloudstream3.ui.home
+
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import androidx.viewpager.widget.PagerAdapter
+import com.lagradost.cloudstream3.LoadResponse
+import com.lagradost.cloudstream3.utils.UIHelper.setImage
+
+
+class HomeScrollAdapter(private val onPrimaryCallback: (LoadResponse) -> Unit) : PagerAdapter() {
+ private var items: List = listOf()
+
+ fun setItems(newItems: List) {
+ items = newItems
+
+ notifyDataSetChanged()
+ }
+
+ override fun getCount(): Int {
+ return Int.MAX_VALUE//items.size
+ }
+
+ override fun getItemPosition(`object`: Any): Int {
+ return POSITION_NONE//super.getItemPosition(`object`)
+ }
+
+ private fun getItemAtPosition(idx: Int): LoadResponse {
+ return items[idx % items.size]
+ }
+
+ override fun setPrimaryItem(container: ViewGroup, position: Int, `object`: Any) {
+ super.setPrimaryItem(container, position, `object`)
+ onPrimaryCallback.invoke(getItemAtPosition(position))
+ }
+
+ override fun instantiateItem(container: ViewGroup, position: Int): Any {
+ val image = ImageView(container.context)
+ val item = getItemAtPosition(position)
+ image.scaleType = ImageView.ScaleType.CENTER_CROP
+ image.setImage(item.posterUrl ?: item.backgroundPosterUrl, item.posterHeaders)
+
+ // val itemView: View = mLayoutInflater.inflate(R.layout.pager_item, container, false)
+
+ // val imageView: ImageView = itemView.findViewById(R.id.imageView) as ImageView
+ // imageView.setImageResource(mResources.get(position))
+
+ container.addView(image)
+
+ return image
+ }
+
+ override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
+ container.removeView(`object` as View)
+ }
+
+ override fun isViewFromObject(view: View, `object`: Any): Boolean {
+ return view === `object`
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollTransformer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollTransformer.kt
new file mode 100644
index 00000000..04b6964b
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollTransformer.kt
@@ -0,0 +1,13 @@
+package com.lagradost.cloudstream3.ui.home
+
+import android.view.View
+import androidx.viewpager.widget.ViewPager
+
+class HomeScrollTransformer : ViewPager.PageTransformer {
+ override fun transformPage(page: View, position: Float) {
+ page.setPadding(
+ maxOf(0, (-position * page.width / 2).toInt()), 0,
+ maxOf(0, (position * page.width / 2).toInt()), 0
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt
index 30fd45c1..43d849cf 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt
@@ -13,6 +13,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.HomePageList
+import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.mvvm.*
@@ -20,6 +21,8 @@ import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
import com.lagradost.cloudstream3.ui.WatchType
+import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
+import com.lagradost.cloudstream3.utils.Coroutines.ioWork
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds
@@ -32,7 +35,7 @@ import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import java.util.*
import kotlin.collections.set
@@ -51,14 +54,16 @@ class HomeViewModel : ViewModel() {
}
private val _availableWatchStatusTypes =
- MutableLiveData, EnumSet>>()
- val availableWatchStatusTypes: LiveData, EnumSet>> =
+ MutableLiveData, Set>>()
+ val availableWatchStatusTypes: LiveData, Set>> =
_availableWatchStatusTypes
private val _bookmarks = MutableLiveData>>()
val bookmarks: LiveData>> = _bookmarks
private val _resumeWatching = MutableLiveData>()
+ private val _preview = MutableLiveData>>()
val resumeWatching: LiveData> = _resumeWatching
+ val preview: LiveData>> = _preview
fun loadResumeWatching() = viewModelScope.launchSafe {
val resumeWatching = withContext(Dispatchers.IO) {
@@ -96,7 +101,7 @@ class HomeViewModel : ViewModel() {
}
}
- fun loadStoredData(preferredWatchStatus: EnumSet?) = viewModelScope.launchSafe {
+ fun loadStoredData(preferredWatchStatus: Set?) = viewModelScope.launchSafe {
val watchStatusIds = withContext(Dispatchers.IO) {
getAllWatchStateIds()?.map { id ->
Pair(id, getResultWatchState(id))
@@ -104,7 +109,7 @@ class HomeViewModel : ViewModel() {
}?.distinctBy { it.first } ?: return@launchSafe
val length = WatchType.values().size
- val currentWatchTypes = EnumSet.noneOf(WatchType::class.java)
+ val currentWatchTypes = mutableSetOf()
for (watch in watchStatusIds) {
currentWatchTypes.add(watch.second)
@@ -207,7 +212,8 @@ class HomeViewModel : ViewModel() {
expandAndReturn(name)
}
- private fun load(api: MainAPI?) = viewModelScope.launchSafe {
+
+ private fun load(api: MainAPI?) = ioSafe {
repo = if (api != null) {
APIRepository(api)
} else {
@@ -219,6 +225,7 @@ class HomeViewModel : ViewModel() {
if (repo?.hasMainPage == true) {
_page.postValue(Resource.Loading())
+ _preview.postValue(Resource.Loading())
when (val data = repo?.getMainPage(1, null)) {
is Resource.Success -> {
@@ -232,8 +239,66 @@ class HomeViewModel : ViewModel() {
ExpandableHomepageList(filteredList, 1, home.hasNext)
}
}
- _page.postValue(Resource.Success(expandable))
+
val items = data.value.mapNotNull { it?.items }.flatten()
+ val responses = ioWork {
+ items.flatMap { it.list }.shuffled().take(6).map { searchResponse ->
+ async { repo?.load(searchResponse.url) }
+ }.map { it.await() }.mapNotNull { if (it != null && it is Resource.Success) it.value else null } }
+ //.amap { searchResponse ->
+ // repo?.load(searchResponse.url)
+ ///}
+
+ //.map { searchResponse ->
+ // async { repo?.load(searchResponse.url) }
+ // }.map { it.await() }
+
+
+ if (responses.isEmpty()) {
+ _preview.postValue(
+ Resource.Failure(
+ false,
+ null,
+ null,
+ "No homepage responses"
+ )
+ )
+ } else {
+ _preview.postValue(Resource.Success(responses))
+ }
+
+ /*
+ items.randomOrNull()?.list?.randomOrNull()?.url?.let { url ->
+ // backup request in case first fails
+ var first = repo?.load(url)
+ if(first == null ||first is Resource.Failure) {
+ first = repo?.load(items.random().list.random().url)
+ }
+ first?.let {
+ _preview.postValue(it)
+ } ?: run {
+ _preview.postValue(
+ Resource.Failure(
+ false,
+ null,
+ null,
+ "No repo found, this should never happen"
+ )
+ )
+ }
+ } ?: run {
+ _preview.postValue(
+ Resource.Failure(
+ false,
+ null,
+ null,
+ "No homepage items"
+ )
+ )
+ }*/
+
+ _page.postValue(Resource.Success(expandable))
+
//val home = data.value
if (items.isNotEmpty()) {
@@ -263,6 +328,7 @@ class HomeViewModel : ViewModel() {
}
} else {
_page.postValue(Resource.Success(emptyMap()))
+ _preview.postValue(Resource.Failure(false, null, null, "No homepage"))
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt
index ea466120..a838c85c 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt
@@ -17,10 +17,7 @@ import com.google.android.exoplayer2.text.TextRenderer
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.trackselection.TrackSelector
import com.google.android.exoplayer2.ui.SubtitleView
-import com.google.android.exoplayer2.upstream.DataSource
-import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
-import com.google.android.exoplayer2.upstream.HttpDataSource
+import com.google.android.exoplayer2.upstream.*
import com.google.android.exoplayer2.upstream.cache.CacheDataSource
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
import com.google.android.exoplayer2.upstream.cache.SimpleCache
@@ -880,7 +877,16 @@ class CS3IPlayer : IPlayer {
}
override fun onPlayerError(error: PlaybackException) {
- playerError?.invoke(error)
+ // If the Network fails then ignore the exception if the duration is set.
+ // This is to switch mirrors automatically if the stream has not been fetched, but
+ // allow playing the buffer without internet as then the duration is fetched.
+ if (error.errorCode == PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED
+ && exoPlayer?.duration != C.TIME_UNSET
+ ) {
+ exoPlayer?.prepare()
+ } else {
+ playerError?.invoke(error)
+ }
super.onPlayerError(error)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
index e20a6c7b..816b1298 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt
@@ -344,7 +344,7 @@ class GeneratorPlayer : FullScreenPlayer() {
seasonNumber = currentTempMeta.season,
lang = currentLanguageTwoLetters.ifBlank { null }
)
- val results = providers.apmap {
+ val results = providers.amap {
try {
it.search(search)
} catch (e: Exception) {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt
index 2c8a5372..1f242481 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt
@@ -1,6 +1,6 @@
package com.lagradost.cloudstream3.ui.player
-import com.lagradost.cloudstream3.apmap
+import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.utils.*
import java.net.URI
@@ -46,7 +46,7 @@ class LinkGenerator(
subtitleCallback: (SubtitleData) -> Unit,
offset: Int
): Boolean {
- links.apmap { link ->
+ links.amap { link ->
if (!extract || !loadExtractor(link, referer, {
subtitleCallback(PlayerSubtitleHelper.getSubtitleData(it))
}) {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt
index a173f1c1..db71b714 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt
@@ -22,7 +22,7 @@ import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import com.discord.panels.OverlappingPanelsLayout
-import com.google.android.material.button.MaterialButton
+import com.google.android.material.chip.Chip
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
@@ -99,6 +99,7 @@ import kotlinx.android.synthetic.main.result_sync.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
+import com.google.android.material.chip.ChipDrawable
const val START_ACTION_RESUME_LATEST = 1
const val START_ACTION_LOAD_EP = 2
@@ -926,18 +927,38 @@ open class ResultFragment : ResultTrailerPlayer() {
val tags = d.tags
result_tag_holder?.isVisible = tags.isNotEmpty()
- if (tags.isNotEmpty()) {
- //result_tag_holder?.visibility = VISIBLE
- val isOnTv = isTrueTvSettings()
- for ((index, tag) in tags.withIndex()) {
+ result_tag?.apply {
+ tags.forEach { tag ->
+ val chip = Chip(context)
+ val chipDrawable = ChipDrawable.createFromAttributes(
+ context,
+ null,
+ 0,
+ R.style.ChipFilled
+ )
+ chip.setChipDrawable(chipDrawable)
+ chip.text = tag
+ chip.isChecked = false
+ chip.isCheckable = false
+ chip.isFocusable = false
+ chip.isClickable = false
+ addView(chip)
+ }
+ }
+ // if (tags.isNotEmpty()) {
+ //result_tag_holder?.visibility = VISIBLE
+ //val isOnTv = isTrueTvSettings()
+
+
+ /*for ((index, tag) in tags.withIndex()) {
val viewBtt = layoutInflater.inflate(R.layout.result_tag, null)
val btt = viewBtt.findViewById(R.id.result_tag_card)
btt.text = tag
btt.isFocusable = !isOnTv
btt.isClickable = !isOnTv
result_tag?.addView(viewBtt, index)
- }
- }
+ }*/
+ //}
}
is Resource.Failure -> {
result_error_text.text = storedData?.url?.plus("\n") + data.errorString
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt
index 1cd3641d..813872c9 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt
@@ -27,8 +27,10 @@ import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.ExtractorLink
+import com.lagradost.cloudstream3.utils.SingleSelectionHelper
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
+import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIcons
@@ -127,6 +129,8 @@ class ResultFragmentPhone : ResultFragment() {
down.nextFocusUpId = upper.id
}
+ var selectSeason : String? = null
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val apiName = arguments?.getString(API_NAME_BUNDLE) ?: return
@@ -297,6 +301,7 @@ class ResultFragmentPhone : ResultFragment() {
observe(viewModel.selectedSeason) { text ->
result_season_button.setText(text)
+ selectSeason = (if (text is Some.Success) text.value else null)?.asStringNull(result_season_button?.context)
// If the season button is visible the result season button will be next focus down
if (result_season_button?.isVisible == true)
if (result_resume_parent?.isVisible == true)
@@ -366,17 +371,24 @@ class ResultFragmentPhone : ResultFragment() {
observe(viewModel.seasonSelections) { seasonList ->
result_season_button?.setOnClickListener { view ->
+
view?.context?.let { ctx ->
val names = seasonList
.mapNotNull { (text, r) ->
r to (text?.asStringNull(ctx) ?: return@mapNotNull null)
}
- view.popupMenuNoIconsAndNoStringRes(names.mapIndexed { index, (_, name) ->
- index to name
- }) {
+ activity?.showDialog(names.map { it.second },names.indexOfFirst { it.second == selectSeason },"",false,{}) { itemId->
viewModel.changeSeason(names[itemId].first)
}
+
+
+
+ //view.popupMenuNoIconsAndNoStringRes(names.mapIndexed { index, (_, name) ->
+ // index to name
+ //}) {
+ // viewModel.changeSeason(names[itemId].first)
+ //}
}
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt
index 906b652d..f424989e 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt
@@ -412,6 +412,29 @@ class ResultViewModel2 : ViewModel() {
return this?.firstOrNull { it.season == season }
}
+ fun updateWatchStatus(currentResponse : LoadResponse, status: WatchType) {
+ val currentId = currentResponse.getId()
+ val resultPage = currentResponse
+
+ DataStoreHelper.setResultWatchState(currentId, status.internalId)
+ val current = DataStoreHelper.getBookmarkedData(currentId)
+ val currentTime = System.currentTimeMillis()
+ DataStoreHelper.setBookmarkedData(
+ currentId,
+ DataStoreHelper.BookmarkedData(
+ currentId,
+ current?.bookmarkedTime ?: currentTime,
+ currentTime,
+ resultPage.name,
+ resultPage.url,
+ resultPage.apiName,
+ resultPage.type,
+ resultPage.posterUrl,
+ resultPage.year
+ )
+ )
+ }
+
private fun filterName(name: String?): String? {
if (name == null) return null
Regex("[eE]pisode [0-9]*(.*)").find(name)?.groupValues?.get(1)?.let {
@@ -764,28 +787,10 @@ class ResultViewModel2 : ViewModel() {
private val _selectPopup: MutableLiveData> = MutableLiveData(Some.None)
val selectPopup: LiveData> get() = _selectPopup
- fun updateWatchStatus(status: WatchType) {
- val currentId = currentId ?: return
- val resultPage = currentResponse ?: return
- _watchStatus.postValue(status)
- DataStoreHelper.setResultWatchState(currentId, status.internalId)
- val current = DataStoreHelper.getBookmarkedData(currentId)
- val currentTime = System.currentTimeMillis()
- DataStoreHelper.setBookmarkedData(
- currentId,
- DataStoreHelper.BookmarkedData(
- currentId,
- current?.bookmarkedTime ?: currentTime,
- currentTime,
- resultPage.name,
- resultPage.url,
- resultPage.apiName,
- resultPage.type,
- resultPage.posterUrl,
- resultPage.year
- )
- )
+ fun updateWatchStatus(status: WatchType) {
+ updateWatchStatus(currentResponse ?: return,status)
+ _watchStatus.postValue(status)
}
private fun startChromecast(
@@ -1967,7 +1972,7 @@ class ResultViewModel2 : ViewModel() {
): List =
coroutineScope {
var currentCount = 0
- return@coroutineScope loadResponse.trailers.apmap { trailerData ->
+ return@coroutineScope loadResponse.trailers.amap { trailerData ->
try {
val links = arrayListOf()
val subs = arrayListOf()
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt
index 513d68e8..91415d26 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt
@@ -4,7 +4,7 @@ import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import com.lagradost.cloudstream3.apmap
+import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
@@ -197,7 +197,7 @@ class SyncViewModel : ViewModel() {
/// modifies the current sync data, return null if you don't want to change it
private fun modifyData(update: ((SyncAPI.SyncStatus) -> (SyncAPI.SyncStatus?))) =
ioSafe {
- syncs.apmap { (prefix, id) ->
+ syncs.amap { (prefix, id) ->
repos.firstOrNull { it.idPrefix == prefix }?.let { repo ->
if (repo.hasAccount()) {
val result = repo.getStatus(id)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt
index 5f108cd1..a4b2be8f 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt
@@ -26,15 +26,19 @@ import com.lagradost.cloudstream3.APIHolder.filterSearchResultByFilmQuality
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.APIHolder.getApiSettings
+import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
+import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.home.HomeFragment
+import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.currentSpan
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageList
+import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.updateChips
import com.lagradost.cloudstream3.ui.home.ParentItemAdapter
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.utils.Coroutines.main
@@ -46,6 +50,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import kotlinx.android.synthetic.main.fragment_search.*
+import kotlinx.android.synthetic.main.tvtypes_chips.*
import java.util.concurrent.locks.ReentrantLock
const val SEARCH_PREF_TAGS = "search_pref_tags"
@@ -112,83 +117,62 @@ class SearchFragment : Fragment() {
var selectedSearchTypes = mutableListOf()
var selectedApis = mutableSetOf()
+ /**
+ * Will filter all providers by preferred media and selectedSearchTypes.
+ * If that results in no available providers then only filter
+ * providers by preferred media
+ **/
fun search(query: String?) {
if (query == null) return
- context?.getApiSettings()?.let { settings ->
+
+ context?.let { ctx ->
+ val default = enumValues().sorted().filter { it != TvType.NSFW }
+ .map { it.ordinal.toString() }.toSet()
+ val preferredTypes = PreferenceManager.getDefaultSharedPreferences(ctx)
+ .getStringSet(this.getString(R.string.prefer_media_type_key), default)
+ ?.mapNotNull { it.toIntOrNull() ?: return@mapNotNull null } ?: default
+
+ val settings = ctx.getApiSettings()
+
+ val notFilteredBySelectedTypes = selectedApis.filter { name ->
+ settings.contains(name)
+ }.map { name ->
+ name to getApiFromNameNull(name)?.supportedTypes
+ }.filter { (_, types) ->
+ types?.any { preferredTypes.contains(it.ordinal) } == true
+ }
+
searchViewModel.searchAndCancel(
query = query,
- providersActive = selectedApis.filter { name ->
- settings.contains(name) && getApiFromNameNull(name)?.supportedTypes?.any {
- selectedSearchTypes.contains(
- it
- )
- } == true
- }.toSet()
+ providersActive = notFilteredBySelectedTypes.filter { (_, types) ->
+ types?.any { selectedSearchTypes.contains(it) } == true
+ }.ifEmpty { notFilteredBySelectedTypes }.map { it.first }.toSet()
)
}
}
// Null if defined as a variable
// This needs to be run after view created
- private fun getPairList(): List>> {
- return HomeFragment.getPairList(
- search_select_anime,
- search_select_cartoons,
- search_select_tv_series,
- search_select_documentaries,
- search_select_movies,
- search_select_asian,
- search_select_livestreams,
- search_select_nsfw,
- search_select_others
- )
- }
private fun reloadRepos(success: Boolean = false) = main {
- val pairList = getPairList()
-
searchViewModel.reloadRepos()
context?.filterProviderByPreferredMedia()?.let { validAPIs ->
- for ((button, validTypes) in pairList) {
- val isValid =
- validAPIs.any { api -> validTypes.any { api.supportedTypes.contains(it) } }
- button?.isVisible = isValid
- if (isValid) {
- fun buttonContains(): Boolean {
- return selectedSearchTypes.any { validTypes.contains(it) }
- }
-
- button?.isSelected = buttonContains()
- button?.setOnClickListener {
- val last = selectedSearchTypes.toSet()
- selectedSearchTypes.clear()
- selectedSearchTypes.addAll(validTypes)
- for ((otherButton, _) in pairList) {
- otherButton?.isSelected = false
- }
- it?.context?.setKey(SEARCH_PREF_TAGS, selectedSearchTypes)
- it?.isSelected = true
- if (last != selectedSearchTypes.toSet()) // if you click the same button again the it does nothing
- search(main_search?.query?.toString())
- }
-
- button?.setOnLongClickListener {
- if (!buttonContains()) {
- it?.isSelected = true
- selectedSearchTypes.addAll(validTypes)
- } else {
- it?.isSelected = false
- selectedSearchTypes.removeAll(validTypes)
- }
- it?.context?.setKey(SEARCH_PREF_TAGS, selectedSearchTypes)
- search(main_search?.query?.toString())
- return@setOnLongClickListener true
- }
+ bindChips(
+ home_select_group,
+ selectedSearchTypes,
+ validAPIs.flatMap { api -> api.supportedTypes }.distinct()
+ ) { list ->
+ if (selectedSearchTypes.toSet() != list.toSet()) {
+ setKey(SEARCH_PREF_TAGS, selectedSearchTypes)
+ selectedSearchTypes.clear()
+ selectedSearchTypes.addAll(list)
+ search(main_search?.query?.toString())
}
}
}
}
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -239,31 +223,67 @@ class SearchFragment : Fragment() {
builder.let { dialog ->
val isMultiLang = ctx.getApiProviderLangSettings().size > 1
- val anime = dialog.findViewById(R.id.home_select_anime)
- val cartoons = dialog.findViewById(R.id.home_select_cartoons)
- val tvs = dialog.findViewById(R.id.home_select_tv_series)
- val docs = dialog.findViewById(R.id.home_select_documentaries)
- val movies = dialog.findViewById(R.id.home_select_movies)
- val asian = dialog.findViewById(R.id.home_select_asian)
- val livestream =
- dialog.findViewById(R.id.home_select_livestreams)
- val nsfw = dialog.findViewById(R.id.home_select_nsfw)
- val other = dialog.findViewById(R.id.home_select_others)
val cancelBtt = dialog.findViewById(R.id.cancel_btt)
val applyBtt = dialog.findViewById(R.id.apply_btt)
- val pairList =
- HomeFragment.getPairList(
- anime,
- cartoons,
- tvs,
- docs,
- movies,
- asian,
- livestream,
- nsfw,
- other
- )
+ val listView = dialog.findViewById(R.id.listview1)
+ val arrayAdapter = ArrayAdapter(ctx, R.layout.sort_bottom_single_choice)
+ listView?.adapter = arrayAdapter
+ listView?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
+
+ listView?.setOnItemClickListener { _, _, i, _ ->
+ if (currentValidApis.isNotEmpty()) {
+ val api = currentValidApis[i].name
+ if (currentSelectedApis.contains(api)) {
+ listView.setItemChecked(i, false)
+ currentSelectedApis -= api
+ } else {
+ listView.setItemChecked(i, true)
+ currentSelectedApis += api
+ }
+ }
+ }
+
+ fun updateList(types: List) {
+ setKey(SEARCH_PREF_TAGS, types.map { it.name })
+
+ arrayAdapter.clear()
+ currentValidApis = validAPIs.filter { api ->
+ api.supportedTypes.any {
+ types.contains(it)
+ }
+ }.sortedBy { it.name.lowercase() }
+
+ val names = currentValidApis.map {
+ if (isMultiLang) "${
+ SubtitleHelper.getFlagFromIso(
+ it.lang
+ )?.plus(" ") ?: ""
+ }${it.name}" else it.name
+ }
+ for ((index, api) in currentValidApis.map { it.name }.withIndex()) {
+ listView?.setItemChecked(index, currentSelectedApis.contains(api))
+ }
+
+ //arrayAdapter.notifyDataSetChanged()
+ arrayAdapter.addAll(names)
+ arrayAdapter.notifyDataSetChanged()
+ }
+
+ val selectedSearchTypes = getKey>(SEARCH_PREF_TAGS)
+ ?.mapNotNull { listName ->
+ TvType.values().firstOrNull { it.name == listName }
+ }
+ ?.toMutableList()
+ ?: mutableListOf(TvType.Movie, TvType.TvSeries)
+
+ bindChips(
+ dialog.home_select_group,
+ selectedSearchTypes,
+ TvType.values().toList()
+ ) { list ->
+ updateList(list)
+ }
cancelBtt?.setOnClickListener {
dialog.dismissSafe()
@@ -284,90 +304,7 @@ class SearchFragment : Fragment() {
context?.setKey(SEARCH_PREF_PROVIDERS, currentSelectedApis.toList())
selectedApis = currentSelectedApis
}
-
- val selectedSearchTypes = context?.getKey>(SEARCH_PREF_TAGS)
- ?.mapNotNull { listName ->
- TvType.values().firstOrNull { it.name == listName }
- }
- ?.toMutableList()
- ?: mutableListOf(TvType.Movie, TvType.TvSeries)
-
- val listView = dialog.findViewById(R.id.listview1)
- val arrayAdapter = ArrayAdapter(ctx, R.layout.sort_bottom_single_choice)
- listView?.adapter = arrayAdapter
- listView?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
-
- listView?.setOnItemClickListener { _, _, i, _ ->
- if (currentValidApis.isNotEmpty()) {
- val api = currentValidApis[i].name
- if (currentSelectedApis.contains(api)) {
- listView.setItemChecked(i, false)
- currentSelectedApis -= api
- } else {
- listView.setItemChecked(i, true)
- currentSelectedApis += api
- }
- }
- }
-
- fun updateList() {
- arrayAdapter.clear()
- currentValidApis = validAPIs.filter { api ->
- api.supportedTypes.any {
- selectedSearchTypes.contains(it)
- }
- }.sortedBy { it.name.lowercase() }
-
- val names = currentValidApis.map {
- if (isMultiLang) "${
- SubtitleHelper.getFlagFromIso(
- it.lang
- )?.plus(" ") ?: ""
- }${it.name}" else it.name
- }
- for ((index, api) in currentValidApis.map { it.name }.withIndex()) {
- listView?.setItemChecked(index, currentSelectedApis.contains(api))
- }
-
- //arrayAdapter.notifyDataSetChanged()
- arrayAdapter.addAll(names)
- arrayAdapter.notifyDataSetChanged()
- }
-
- for ((button, validTypes) in pairList) {
- val isValid =
- validAPIs.any { api -> validTypes.any { api.supportedTypes.contains(it) } }
- button?.isVisible = isValid
- if (isValid) {
- fun buttonContains(): Boolean {
- return selectedSearchTypes.any { validTypes.contains(it) }
- }
-
- button?.isSelected = buttonContains()
- button?.setOnClickListener {
- selectedSearchTypes.clear()
- selectedSearchTypes.addAll(validTypes)
- for ((otherButton, _) in pairList) {
- otherButton?.isSelected = false
- }
- button.isSelected = true
- updateList()
- }
-
- button?.setOnLongClickListener {
- if (!buttonContains()) {
- button.isSelected = true
- selectedSearchTypes.addAll(validTypes)
- } else {
- button.isSelected = false
- selectedSearchTypes.removeAll(validTypes)
- }
- updateList()
- return@setOnLongClickListener true
- }
- }
- }
- updateList()
+ updateList(selectedSearchTypes.toList())
}
}
}
@@ -380,14 +317,6 @@ class SearchFragment : Fragment() {
?.toMutableList()
?: mutableListOf(TvType.Movie, TvType.TvSeries)
- val pairList = getPairList()
- fun updateSelectedList(list: MutableList) {
- selectedSearchTypes = list
- for ((button, validTypes) in pairList) {
- button?.isSelected = selectedSearchTypes.any { validTypes.contains(it) }
- }
- }
-
if (isTrueTvSettings()) {
search_filter.isFocusable = true
search_filter.isFocusableInTouchMode = true
@@ -500,7 +429,7 @@ class SearchFragment : Fragment() {
SEARCH_HISTORY_OPEN -> {
searchViewModel.clearSearch()
if (searchItem.type.isNotEmpty())
- updateSelectedList(searchItem.type.toMutableList())
+ updateChips(home_select_group, searchItem.type.toMutableList())
main_search?.setQuery(searchItem.searchText, true)
}
SEARCH_HISTORY_REMOVE -> {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt
index b629b73f..aceda644 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt
@@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.SearchResponse
-import com.lagradost.cloudstream3.apmap
+import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.ui.APIRepository
@@ -108,9 +108,9 @@ class SearchViewModel : ViewModel() {
repos.filter { a ->
(ignoreSettings || (providersActive.isEmpty() || providersActive.contains(a.name))) && (!isQuickSearch || a.hasQuickSearch)
- }.apmap { a -> // Parallel
+ }.amap { a -> // Parallel
val search = if (isQuickSearch) a.quickSearch(query) else a.search(query)
- if (currentSearchIndex != currentIndex) return@apmap
+ if (currentSearchIndex != currentIndex) return@amap
currentList.add(OnGoingSearch(a.name, search))
_currentSearch.postValue(currentList)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt
index 57074e74..8ea76cda 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt
@@ -79,6 +79,7 @@ val appLanguages = arrayListOf(
Triple("\uD83C\uDDEE\uD83C\uDDE9", "Indonesian", "in"),
Triple("", "Czech", "cs"),
Triple("", "Croatian", "hr"),
+ Triple("", "Bulgarian", "bg"),
).sortedBy { it.second } //ye, we go alphabetical, so ppl don't put their lang on top
class SettingsGeneral : PreferenceFragmentCompat() {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt
index 897e7366..63ed5357 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt
@@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.apmap
+import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.mvvm.Some
import com.lagradost.cloudstream3.mvvm.debugAssert
import com.lagradost.cloudstream3.plugins.PluginManager
@@ -49,7 +49,7 @@ class ExtensionsViewModel : ViewModel() {
val urls = (getKey>(REPOSITORIES_KEY)
?: emptyArray()) + PREBUILT_REPOSITORIES
- val onlinePlugins = urls.toList().apmap {
+ val onlinePlugins = urls.toList().amap {
RepositoryManager.getRepoPlugins(it.url)?.toList() ?: emptyList()
}.flatten().distinctBy { it.second.url }
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt
index aa302c5a..bacd26c8 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt
@@ -9,8 +9,9 @@ import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.lagradost.cloudstream3.R
+import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.mvvm.observe
-import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.getPairList
+import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.ui.settings.appLanguages
@@ -18,6 +19,8 @@ import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
import com.lagradost.cloudstream3.utils.SubtitleHelper
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import kotlinx.android.synthetic.main.fragment_plugins.*
+import kotlinx.android.synthetic.main.tvtypes_chips.*
+import kotlinx.android.synthetic.main.tvtypes_chips_scroll.*
const val PLUGINS_BUNDLE_NAME = "name"
const val PLUGINS_BUNDLE_URL = "url"
@@ -145,56 +148,10 @@ class PluginsFragment : Fragment() {
pluginViewModel.updatePluginList(context, url)
tv_types_scroll_view?.isVisible = true
- // 💀💀💀💀💀💀💀 Recyclerview when
- val pairList = getPairList(
- home_select_anime,
- home_select_cartoons,
- home_select_tv_series,
- home_select_documentaries,
- home_select_movies,
- home_select_asian,
- home_select_livestreams,
- home_select_nsfw,
- home_select_others
- )
-
-// val supportedTypes: Array =
-// pluginViewModel.filteredPlugins.value!!.second.flatMap { it -> it.plugin.second.tvTypes ?: listOf("Other") }.distinct().toTypedArray()
-
- // Copy pasted code
- for ((button, validTypes) in pairList) {
- val validTypesMapped = validTypes.map { it.name }
- val isValid = true
- //validTypes.any { it -> supportedTypes.contains(it.name) }
- button?.isVisible = isValid
- if (isValid) {
- fun buttonContains(): Boolean {
- return pluginViewModel.tvTypes.any { validTypesMapped.contains(it) }
- }
-
- button?.isSelected = buttonContains()
- button?.setOnClickListener {
- pluginViewModel.tvTypes.clear()
- pluginViewModel.tvTypes.addAll(validTypesMapped)
- for ((otherButton, _) in pairList) {
- otherButton?.isSelected = false
- }
- button.isSelected = true
- pluginViewModel.updateFilteredPlugins()
- }
-
- button?.setOnLongClickListener {
- if (!buttonContains()) {
- button.isSelected = true
- pluginViewModel.tvTypes.addAll(validTypesMapped)
- } else {
- button.isSelected = false
- pluginViewModel.tvTypes.removeAll(validTypesMapped)
- }
- pluginViewModel.updateFilteredPlugins()
- return@setOnLongClickListener true
- }
- }
+ bindChips(home_select_group, emptyList(), TvType.values().toList()) { list ->
+ pluginViewModel.tvTypes.clear()
+ pluginViewModel.tvTypes.addAll(list.map { it.name })
+ pluginViewModel.updateFilteredPlugins()
}
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt
index 536dc33b..894a9331 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt
@@ -10,7 +10,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.apmap
+import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.plugins.PluginManager.getPluginPath
@@ -101,7 +101,7 @@ class PluginsViewModel : ViewModel() {
Toast.LENGTH_SHORT
)
}
- }.apmap { (repo, metadata) ->
+ }.amap { (repo, metadata) ->
PluginManager.downloadAndLoadPlugin(
activity,
metadata.url,
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt
index a8ff94f6..1432874b 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt
@@ -12,8 +12,8 @@ import androidx.fragment.app.FragmentActivity
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.readValue
import com.lagradost.cloudstream3.CommonActivity.showToast
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.app
+import com.lagradost.cloudstream3.R
+import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.plugins.PLUGINS_KEY
import com.lagradost.cloudstream3.plugins.PLUGINS_KEY_LOCAL
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt
index 8d6b1b82..ca612385 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ExtractorApi.kt
@@ -235,6 +235,7 @@ val extractorApis: MutableList = arrayListOf(
Vidgomunime(),
Sbflix(),
Streamsss(),
+ Sbspeed(),
Fastream(),
@@ -246,6 +247,8 @@ val extractorApis: MutableList = arrayListOf(
LayarKaca(),
Rasacintaku(),
FEnet(),
+ Kotakajair(),
+ Cdnplayer(),
// WatchSB(), 'cause StreamSB.kt works
Uqload(),
Uqload1(),
@@ -317,6 +320,7 @@ val extractorApis: MutableList = arrayListOf(
Linkbox(),
Acefile(),
SpeedoStream(),
+ SpeedoStream1(),
Zorofile(),
Embedgram(),
Mvidoo(),
@@ -324,6 +328,7 @@ val extractorApis: MutableList = arrayListOf(
Vidmoly(),
Vidmolyme(),
Voe(),
+ Moviehab(),
Gdriveplayerapi(),
Gdriveplayerapp(),
@@ -336,6 +341,7 @@ val extractorApis: MutableList = arrayListOf(
Gdriveplayerco(),
Gdriveplayer(),
DatabaseGdrive(),
+ DatabaseGdrive2(),
YoutubeExtractor(),
YoutubeShortLinkExtractor(),
diff --git a/app/src/main/res/color/chip_color.xml b/app/src/main/res/color/chip_color.xml
new file mode 100644
index 00000000..f4190551
--- /dev/null
+++ b/app/src/main/res/color/chip_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/chip_color_text.xml b/app/src/main/res/color/chip_color_text.xml
new file mode 100644
index 00000000..03d1e7b0
--- /dev/null
+++ b/app/src/main/res/color/chip_color_text.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/home_alt.xml b/app/src/main/res/drawable/home_alt.xml
new file mode 100644
index 00000000..ae0e68a4
--- /dev/null
+++ b/app/src/main/res/drawable/home_alt.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_add_24.xml b/app/src/main/res/drawable/ic_baseline_add_24.xml
index 9fdd2903..b56c474c 100644
--- a/app/src/main/res/drawable/ic_baseline_add_24.xml
+++ b/app/src/main/res/drawable/ic_baseline_add_24.xml
@@ -1,5 +1,8 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml b/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml
index a6108143..ebe459b2 100644
--- a/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml
+++ b/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml
@@ -1,5 +1,8 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml b/app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml
index 23072282..2ec8c110 100644
--- a/app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml
+++ b/app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml
@@ -1,5 +1,8 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_bookmark_24.xml b/app/src/main/res/drawable/ic_baseline_bookmark_24.xml
index 8f0b1b18..42cb88d5 100644
--- a/app/src/main/res/drawable/ic_baseline_bookmark_24.xml
+++ b/app/src/main/res/drawable/ic_baseline_bookmark_24.xml
@@ -1,5 +1,8 @@
-
-
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_delete_outline_24.xml b/app/src/main/res/drawable/ic_baseline_delete_outline_24.xml
index a93d8ab0..b5cd9ffe 100644
--- a/app/src/main/res/drawable/ic_baseline_delete_outline_24.xml
+++ b/app/src/main/res/drawable/ic_baseline_delete_outline_24.xml
@@ -1,10 +1,8 @@
-
-
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_filter_list_24.xml b/app/src/main/res/drawable/ic_baseline_filter_list_24.xml
index 05e18bfa..f016d9b6 100644
--- a/app/src/main/res/drawable/ic_baseline_filter_list_24.xml
+++ b/app/src/main/res/drawable/ic_baseline_filter_list_24.xml
@@ -1,5 +1,8 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml
index 1aeaa998..0f4bc25f 100644
--- a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml
+++ b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml
@@ -1,5 +1,8 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_tune_24.xml b/app/src/main/res/drawable/ic_baseline_tune_24.xml
index 2ce603c5..d0c63a3d 100644
--- a/app/src/main/res/drawable/ic_baseline_tune_24.xml
+++ b/app/src/main/res/drawable/ic_baseline_tune_24.xml
@@ -1,5 +1,8 @@
-
-
+
+
diff --git a/app/src/main/res/drawable/search_icon.xml b/app/src/main/res/drawable/search_icon.xml
index 18e5e6f8..f6cd5ca5 100644
--- a/app/src/main/res/drawable/search_icon.xml
+++ b/app/src/main/res/drawable/search_icon.xml
@@ -1,20 +1,10 @@
-
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/settings_alt.xml b/app/src/main/res/drawable/settings_alt.xml
new file mode 100644
index 00000000..74983a2e
--- /dev/null
+++ b/app/src/main/res/drawable/settings_alt.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/storage_bar_left.xml b/app/src/main/res/drawable/storage_bar_left.xml
new file mode 100644
index 00000000..493db394
--- /dev/null
+++ b/app/src/main/res/drawable/storage_bar_left.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/storage_bar_left_box.xml b/app/src/main/res/drawable/storage_bar_left_box.xml
new file mode 100644
index 00000000..4c624a3a
--- /dev/null
+++ b/app/src/main/res/drawable/storage_bar_left_box.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/storage_bar_mid.xml b/app/src/main/res/drawable/storage_bar_mid.xml
new file mode 100644
index 00000000..111a7345
--- /dev/null
+++ b/app/src/main/res/drawable/storage_bar_mid.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/storage_bar_mid_box.xml b/app/src/main/res/drawable/storage_bar_mid_box.xml
new file mode 100644
index 00000000..090cd54c
--- /dev/null
+++ b/app/src/main/res/drawable/storage_bar_mid_box.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/storage_bar_right.xml b/app/src/main/res/drawable/storage_bar_right.xml
new file mode 100644
index 00000000..42f60344
--- /dev/null
+++ b/app/src/main/res/drawable/storage_bar_right.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/storage_bar_right_box.xml b/app/src/main/res/drawable/storage_bar_right_box.xml
new file mode 100644
index 00000000..29ecd908
--- /dev/null
+++ b/app/src/main/res/drawable/storage_bar_right_box.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index f8ca8cbd..4905d454 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,79 +1,83 @@
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/homeRoot"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|keyboard|navigation"
+ android:paddingTop="0dp">
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ app:itemTextColor="@color/item_select_color"
+ app:labelVisibilityMode="unlabeled"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:menu="@menu/bottom_nav_menu"
+ app:menuGravity="center">
+
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:menu="@menu/bottom_nav_menu" />
+ android:id="@+id/nav_host_fragment"
+ android:name="androidx.navigation.fragment.NavHostFragment"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:defaultNavHost="true"
+ app:layout_constraintBottom_toTopOf="@+id/cast_mini_controller_holder"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/nav_rail_view"
+ app:layout_constraintTop_toTopOf="parent"
+ app:navGraph="@navigation/mobile_navigation" />
+ android:id="@+id/cast_mini_controller_holder"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ app:layout_constraintBottom_toTopOf="@+id/nav_view"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/nav_rail_view"
+ tools:layout_height="100dp">
+ android:id="@+id/cast_mini_controller"
+ class="com.lagradost.cloudstream3.ui.MyMiniControllerFragment"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+
+ app:castControlButtons="@array/cast_mini_controller_control_buttons"
+ app:customCastBackgroundColor="?attr/primaryGrayBackground"
+ tools:ignore="FragmentTagUsage" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_downloads.xml b/app/src/main/res/layout/fragment_downloads.xml
index 5e12eb4c..f88e39d0 100644
--- a/app/src/main/res/layout/fragment_downloads.xml
+++ b/app/src/main/res/layout/fragment_downloads.xml
@@ -1,149 +1,158 @@
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/download_root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/primaryGrayBackground"
+ android:orientation="vertical"
+ tools:context=".ui.download.DownloadFragment">
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/primaryGrayBackground"
+ tools:layout_height="100dp">
+ android:id="@+id/download_storage_appbar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="10dp"
+ android:orientation="vertical"
+ android:visibility="gone"
+ tools:visibility="visible">
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="5dp"
+ android:text="@string/download_storage_text"
+ android:textColor="?attr/textColor" />
-
+
+
-
+ android:layout_height="match_parent"
+ android:layout_weight="0.5"
+ android:background="@drawable/storage_bar_left" />
-
+ android:layout_height="match_parent"
+ android:layout_weight="0.10"
+ android:background="@drawable/storage_bar_mid" />
-
-
+ android:layout_height="match_parent"
+ android:layout_weight="0.10"
+ android:background="@drawable/storage_bar_right" />
+
+
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:layout_gravity="center_vertical"
+ android:layout_marginTop="5dp"
+ android:layout_marginEnd="5dp"
+ android:layout_marginBottom="5dp"
+ android:background="@drawable/storage_bar_left_box" />
+ android:id="@+id/download_used_txt"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:textColor="?attr/textColor"
+ android:textSize="12sp"
+ tools:text="Used • 30.58GB" />
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:layout_gravity="center_vertical"
+ android:layout_margin="5dp"
+ android:background="@drawable/storage_bar_mid_box" />
+ android:id="@+id/download_app_txt"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:textColor="?attr/textColor"
+ android:textSize="12sp"
+ tools:text="App • 30.58GB" />
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:layout_gravity="center_vertical"
+ android:layout_margin="5dp"
+ android:background="@drawable/storage_bar_right_box" />
+ android:id="@+id/download_free_txt"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:textColor="?attr/textColor"
+ android:textSize="12sp"
+ tools:text="Free • 30.58GB" />
+ android:layout_height="match_parent"
+ android:background="?attr/primaryBlackBackground"
+ android:descendantFocusability="afterDescendants"
+ android:nextFocusLeft="@id/nav_rail_view"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"
+ tools:listitem="@layout/download_header_episode" />
+ android:id="@+id/text_no_downloads"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginEnd="8dp"
+ android:textAlignment="center"
+ android:textSize="20sp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+ android:id="@+id/download_loading"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:orientation="vertical"
+ android:paddingTop="40dp"
+ app:shimmer_auto_start="true"
+ app:shimmer_base_alpha="0.2"
+ app:shimmer_duration="@integer/loading_time"
+ app:shimmer_highlight_alpha="0.3"
+ tools:visibility="gone">
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/loading_margin"
+ android:layout_marginEnd="@dimen/loading_margin"
+ android:orientation="vertical">
@@ -189,10 +198,10 @@
+ android:id="@+id/download_stream_button"
+ style="@style/ExtendedFloatingActionButton"
+ android:text="@string/stream"
+ android:textColor="?attr/textColor"
+ app:icon="@drawable/netflix_play"
+ tools:ignore="ContentDescription" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_extensions.xml b/app/src/main/res/layout/fragment_extensions.xml
index c786e2bc..a550efa4 100644
--- a/app/src/main/res/layout/fragment_extensions.xml
+++ b/app/src/main/res/layout/fragment_extensions.xml
@@ -11,12 +11,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
+ android:visibility="visible">
@@ -83,33 +83,42 @@
android:text="@string/extensions"
android:textColor="?attr/textColor" />
-
+ app:cardCornerRadius="@dimen/storage_radius">
-
+ android:orientation="horizontal">
-
+
-
-
+
+
+
+
+
+ android:background="@drawable/storage_bar_left_box" />
+ android:background="@drawable/storage_bar_mid_box" />
+ android:background="@drawable/storage_bar_right_box" />
+
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:paddingStart="10dp"
+ android:paddingEnd="10dp">
-
+
+
-
+ android:focusable="true"
+ android:nextFocusLeft="@id/plugin_storage_appbar"
+ android:src="@drawable/ic_baseline_add_24"
+ app:tint="?attr/textColor" />
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
index 4abdef0b..3d7e3ba5 100644
--- a/app/src/main/res/layout/fragment_home.xml
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -1,87 +1,87 @@
+ android:layout_height="match_parent"
+ tools:context=".ui.home.HomeFragment">
+ android:id="@+id/home_loading"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ tools:visibility="gone">
+ android:layout_width="50dp"
+ android:layout_height="50dp"
+ android:layout_gravity="center"
+ android:visibility="gone"
+ tools:visibility="gone" />
+ android:id="@+id/home_loading_shimmer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:layout_marginTop="15dp"
+ android:orientation="vertical"
+ android:paddingTop="40dp"
+ app:shimmer_auto_start="true"
+ app:shimmer_base_alpha="0.2"
+ app:shimmer_duration="@integer/loading_time"
+ app:shimmer_highlight_alpha="0.3">
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ android:layout_width="125dp"
+ android:layout_height="200dp"
+ android:layout_gravity="center"
+ android:layout_margin="@dimen/loading_margin"
+ android:background="@color/grayShimmer"
+ android:translationX="-164dp"
+ app:cardCornerRadius="@dimen/loading_radius" />
+ android:layout_width="148dp"
+ android:layout_height="234dp"
+ android:layout_gravity="center"
+ android:layout_margin="@dimen/loading_margin"
+ android:background="@color/grayShimmer"
+ app:cardCornerRadius="@dimen/loading_radius" />
+ android:layout_width="125dp"
+ android:layout_height="200dp"
+ android:layout_gravity="center"
+ android:layout_margin="@dimen/loading_margin"
+ android:background="@color/grayShimmer"
+ android:translationX="164dp"
+ app:cardCornerRadius="@dimen/loading_radius" />
+ android:layout_marginEnd="@dimen/result_padding"
+ android:orientation="vertical">
@@ -93,168 +93,170 @@
+ android:id="@+id/home_loading_statusbar"
+ android:layout_width="match_parent"
+ android:layout_height="70dp">
+ android:layout_margin="10dp"
+ android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:contentDescription="@string/home_change_provider_img_des"
+ android:src="@drawable/ic_baseline_keyboard_arrow_down_24" />
+
+
+ android:layout_margin="5dp"
+ android:minWidth="200dp"
+ android:text="@string/reload_error"
+ app:icon="@drawable/ic_baseline_autorenew_24" />
-
-
+ android:layout_gravity="center"
+ android:layout_margin="5dp"
+ android:minWidth="200dp"
+ android:text="@string/result_open_in_browser"
+ app:icon="@drawable/ic_baseline_public_24" />
+ android:id="@+id/result_error_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_margin="5dp"
+ android:gravity="center"
+ android:textColor="?attr/textColor" />
+ android:id="@+id/home_loaded"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/primaryBlackBackground"
+ android:visibility="gone"
+ tools:visibility="visible">
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ android:id="@+id/home_statusbar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/primaryGrayBackground" />
+ android:id="@+id/home_settings_bar"
+
+ android:layout_width="match_parent"
+ android:layout_height="70dp"
+ android:background="?attr/primaryGrayBackground"
+ android:visibility="gone">
+ android:paddingTop="10dp"
+ android:paddingEnd="10dp"
+ android:paddingBottom="10dp">
+ android:id="@+id/home_profile_picture_holder"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="10dp"
+ app:cardCornerRadius="100dp">
+ android:id="@+id/home_profile_picture"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop"
+ tools:ignore="ContentDescription" />
+ android:layout_width="match_parent"
+ android:layout_height="40dp"
+ android:layout_gravity="center_vertical"
+ android:background="@drawable/search_background"
+ android:visibility="visible">
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:iconifiedByDefault="false"
+ android:paddingStart="-10dp"
+ app:iconifiedByDefault="false"
+ app:queryBackground="@color/transparent"
+ app:queryHint="@string/search_hint"
+ app:searchIcon="@drawable/search_icon"
+ tools:ignore="RtlSymmetry" />
+
+
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center_vertical"
+ android:textColor="?attr/textColor"
+ android:textSize="20sp"
+
+ tools:text="Hello World" />
-
-
+ android:id="@+id/home_provider_meta_info"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center_vertical"
+ android:textColor="?attr/grayTextColor"
+ android:textSize="14sp"
+ tools:text="Hello World" />
@@ -277,274 +279,368 @@
-->
+
+
-
+ android:layout_height="match_parent">
-
- android:clipToPadding="false"
- android:paddingStart="148dp"
- android:paddingEnd="148dp"
+
- app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
- android:id="@+id/home_main_poster_recyclerview"
- android:orientation="horizontal"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- tools:listitem="@layout/home_result_grid" />
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:padding="20dp">
-
+
- app:icon="@drawable/ic_baseline_play_arrow_24"
- android:minWidth="120dp"
- android:layout_width="wrap_content" />
-
-
+ android:textColor="?attr/white"
+ app:drawableTint="?attr/white"
+ app:drawableTopCompat="@drawable/ic_outline_info_24"
+ app:tint="?attr/white" />
+
-
+
+
+ android:id="@+id/home_watch_holder"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:visibility="gone"
+ tools:visibility="visible">
+ android:foreground="?android:attr/selectableItemBackgroundBorderless"
+ android:nextFocusLeft="@id/nav_rail_view"
+ android:nextFocusDown="@id/home_watch_child_recyclerview"
+ android:padding="12dp">
+ android:id="@+id/home_watch_parent_item_title"
+ style="@style/WatchHeaderText"
+ android:layout_gravity="center_vertical"
+ android:text="@string/continue_watching" />
+ android:layout_width="30dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="end|center_vertical"
+ android:layout_marginEnd="5dp"
+ android:contentDescription="@string/home_more_info"
+ android:src="@drawable/ic_baseline_arrow_forward_24"
+ app:tint="?attr/textColor" />
+ android:id="@+id/home_watch_child_recyclerview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ android:descendantFocusability="afterDescendants"
+ android:orientation="horizontal"
+ android:paddingHorizontal="5dp"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+ tools:listitem="@layout/home_result_grid" />
+ android:id="@+id/home_bookmarked_holder"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:visibility="gone"
+ tools:visibility="visible">
+ android:foreground="?android:attr/selectableItemBackgroundBorderless"
+ android:nextFocusLeft="@id/nav_rail_view"
+ android:nextFocusUp="@id/home_watch_child_recyclerview"
+ android:nextFocusForward="@id/home_bookmarked_child_recyclerview"
+ android:paddingStart="12dp"
+ android:paddingTop="5dp"
+ android:paddingEnd="12dp"
+ android:paddingBottom="5dp">
+
+
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:singleSelection="true">
-
+ android:layout_height="wrap_content"
-
- android:id="@+id/home_type_watching_btt"
- android:text="@string/type_watching"
- style="@style/RoundedSelectableButton" />
+
- android:id="@+id/home_plan_to_watch_btt"
- android:text="@string/type_plan_to_watch"
- style="@style/RoundedSelectableButton" />
+
- android:id="@+id/home_type_on_hold_btt"
- android:text="@string/type_on_hold"
- style="@style/RoundedSelectableButton" />
+
- android:id="@+id/home_type_dropped_btt"
- android:text="@string/type_dropped"
- style="@style/RoundedSelectableButton" />
+
-
+ android:layout_height="wrap_content"
+ android:nextFocusLeft="@id/home_type_dropped_btt"
+ android:text="@string/type_completed" />
+
+ android:layout_width="30dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="end|center_vertical"
+ android:layout_marginEnd="5dp"
+ android:contentDescription="@string/home_more_info"
+ android:src="@drawable/ic_baseline_arrow_forward_24"
+ app:tint="?attr/textColor" />
+ android:clipToPadding="false"
+ android:descendantFocusability="afterDescendants"
+ android:orientation="horizontal"
+ android:paddingHorizontal="5dp"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+ tools:listitem="@layout/home_result_grid" />
+ android:id="@+id/home_master_recycler"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:descendantFocusability="afterDescendants"
+ android:nextFocusLeft="@id/nav_rail_view"
+ tools:listitem="@layout/homepage_parent" />
+ android:id="@+id/home_api_fab"
+ style="@style/ExtendedFloatingActionButton"
+ android:text="@string/home_source"
+ android:textColor="?attr/textColor"
+ android:visibility="gone"
+ app:icon="@drawable/ic_baseline_filter_list_24"
+ tools:ignore="ContentDescription"
+ tools:visibility="visible" />
+ android:id="@+id/home_random"
+ style="@style/ExtendedFloatingActionButton"
+ android:layout_gravity="bottom|start"
+ android:text="@string/home_random"
+ android:textColor="?attr/textColor"
+ android:visibility="gone"
+ app:icon="@drawable/ic_baseline_play_arrow_24"
+ tools:ignore="ContentDescription"
+ tools:visibility="visible" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_home_tv.xml b/app/src/main/res/layout/fragment_home_tv.xml
index 0636f3dc..5b092927 100644
--- a/app/src/main/res/layout/fragment_home_tv.xml
+++ b/app/src/main/res/layout/fragment_home_tv.xml
@@ -359,52 +359,60 @@
android:fadingEdge="horizontal"
android:requiresFadingEdge="horizontal">
-
+
-
+ android:nextFocusLeft="@id/nav_rail_view"
+ android:nextFocusRight="@id/home_plan_to_watch_btt"
+ android:text="@string/type_watching" />
-
+ android:nextFocusLeft="@id/home_type_watching_btt"
+ android:nextFocusRight="@id/home_type_on_hold_btt"
+ android:text="@string/type_plan_to_watch" />
-
+ android:nextFocusLeft="@id/home_plan_to_watch_btt"
+ android:nextFocusRight="@id/home_type_dropped_btt"
+ android:text="@string/type_on_hold" />
-
+ android:nextFocusLeft="@id/home_type_on_hold_btt"
+ android:nextFocusRight="@id/home_type_completed_btt"
+ android:text="@string/type_dropped" />
-
-
-
+ style="@style/ChipFilled"
+ android:nextFocusLeft="@id/home_type_dropped_btt"
+ android:text="@string/type_completed" />
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
-
diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml
index 31a6b7cd..083cd64e 100644
--- a/app/src/main/res/layout/fragment_search.xml
+++ b/app/src/main/res/layout/fragment_search.xml
@@ -82,86 +82,7 @@
app:tint="?attr/textColor" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_width="match_parent"
+ android:layout_height="60dp">
+
+
-
+
-
+
+
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/tvtypes_chips.xml b/app/src/main/res/layout/tvtypes_chips.xml
new file mode 100644
index 00000000..33ca7762
--- /dev/null
+++ b/app/src/main/res/layout/tvtypes_chips.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/tvtypes_chips_scroll.xml b/app/src/main/res/layout/tvtypes_chips_scroll.xml
new file mode 100644
index 00000000..45b27dbc
--- /dev/null
+++ b/app/src/main/res/layout/tvtypes_chips_scroll.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml
index e3fd3d22..3a5e0929 100644
--- a/app/src/main/res/menu/bottom_nav_menu.xml
+++ b/app/src/main/res/menu/bottom_nav_menu.xml
@@ -3,7 +3,7 @@
\ No newline at end of file
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
new file mode 100644
index 00000000..e32d3d93
--- /dev/null
+++ b/app/src/main/res/values-bg/strings.xml
@@ -0,0 +1,533 @@
+
+
+
+
+ %s еп. %d
+ Актьори: %s
+ Епизод %d ще бъде пуснат след
+ %DD %dh %dm
+ %dh %dm
+ %dm
+
+
+ Плакат
+ @string/result_poster_img_des
+ Плакат на епизод
+ Основен плакат
+ Следващ произволен
+ Върни се
+ Смяна на доставчика
+ Визуализация на фона
+
+
+ Скорост (%.2fx)
+ Оценка: %.1f
+ Намерена е нова актуализация!\n%s -> %s
+ Шаблон
+ %d мин
+
+ CloudStream
+ Пусни с CloudStream
+ Начало
+ Търсене
+ Изтегляния
+ Настройки
+
+ Търсене…
+ Търсете %s...
+
+ Няма данни
+ Още опций
+ Следващ епизод
+ Жанрове
+ Сподели
+ Отвори в браузъра
+ Пропусни зареждането
+ Зареждане…
+
+ Гледане
+ На изчакване
+ Завършено
+ Изпуснат
+ План за гледане
+ Нито един
+ Повторно гледане
+
+ Пускане на филм
+ Възпроизвеждане на живо
+ Поточно предаване на торент
+ Източници
+ субтитри
+ Повторен опит за свързване...
+ Върни се
+ Пусни епизод
+
+
+ Изтегли
+ Изтеглено
+ Изтегля се
+ Изтеглянето е на пауза
+ Изтеглянето започна
+ Изтеглянето се провали
+ Изтеглянето е отменено
+ Изтеглянето е завършено
+ Поток
+
+ Грешка при зареждане на връзки
+ Вътрешна памет
+
+ Дублаж
+ Субтитри
+
+ Изтрий файла
+ Възпроизвеждане на файл
+ Възобновете изтеглянето
+ Пауза на изтеглянето
+
+ Деактивирайте автоматичното докладване на грешки
+ Повече информация
+ Скрий
+ Пусни
+ Информация
+ Филтриране на отметки
+ Отметки
+ Премахване
+ Задайте статус на гледане
+ Приложи
+ Отказ
+ Копирай
+ Затвори
+ Изчисти
+ Запазване
+
+ Скорост на възпроизвеждане
+
+ Настройки на субтитрите
+ Цвят на текста
+ Цвят на контура
+ Цвят на фона
+ Цвят на прозореца
+ Тип ръб
+ Височина на субтитрите
+ Шрифт
+ Размер на шрифта
+
+ Търсене чрез доставчици
+ Търсене с помощта на типове
+
+ %d Банан/а даден/и на разработчиците
+ Не е/са даден/и Банан/и
+
+ Автоматичен избор на език
+ Изтегляне на езици
+ Език на субтитрите
+ Задръжте, за да нулирате по подразбиране
+ Импортирайте шрифтове, като ги поставите в %s
+ Продължете да гледате
+
+ Премахване
+ Повече информация
+ @string/home_play
+
+ Може да е необходим VPN, за да работи правилно този доставчик
+ Този доставчик е торент, препоръчва се VPN
+
+ Метаданните не се предоставят от сайта, зареждането на видео ще бъде неуспешно, ако не съществува на сайта.
+
+ Описание
+ Няма намерен съдържание
+ Няма намерено описание
+
+ Покажи logcat 🐈
+
+ Картина в картина
+ Продължава възпроизвеждането в миниатюрен плейър върху други приложения
+ Бутон за преоразмеряване на плейъра
+ Премахнете черните граници
+ Субтитри
+ Настройки на субтитрите на плейъра
+ Chromecast субтитри
+ Настройки за субтитри на Chromecast
+
+ Режим Eigengravy (промяна скорост на възпроизвеждане)
+ Добавя опция за скорост в плейъра
+ Плъзнете за преместване
+ Плъзнете наляво или надясно, за да контролирате времето във видеоплейъра
+ Плъзнете, за да промените настройките
+ Плъзнете наляво или надясно, за да промените яркостта или силата на звука
+
+ Автоматично пускане на следващ епизод
+ Започнете следващия епизод, когато текущият приключи
+
+ Докоснете двукратно за превъртане
+ Докоснете два пъти за пауза
+ Размер на превъртане
+ Докоснете два пъти от дясната или лявата страна, за да превъртите напред или назад
+ Докоснете в средата, за да направите пауза
+ Използвайте яркостта на системата
+ Използвайте системна яркост в плейъра на приложението вместо тъмно
+ наслагване
+
+ Актуализирайте прогреса на гледане
+ Автоматично синхронизирайте прогреса на текущия си епизод
+
+ Възстановете данните от архив
+
+ Архивиране на данни
+ Зареден архивен файл
+ Неуспешно възстановяване на данни от файл %s
+ Успешно съхранени данни
+ Липсват разрешения за съхранение, моля, опитайте отново
+ Грешка при архивирането на %s
+
+ Търсене
+ Акаунти
+ Актуализации и архивиране
+
+ Информация
+ Подробно търсене
+ Дава ви резултатите от търсенето, разделени по доставчик
+ Изпраща данни само за сривове
+ Не изпраща данни
+ Показване заместващ епизод за аниме
+ Показване на трейлъри
+ Покажете плакати от kitsu
+ Скриване на избраното видео качество в резултатите от търсенето
+
+ Автоматични актуализации на плъгини
+ Показвай актуализации на приложението
+ Автоматично търси нови актуализации при стартиране
+ Актуализация до експериментални версии
+ Търсете експериментални актуализации вместо само пълни версии
+ Github
+ Light novel - приложение от същите разработчици
+ Приложение за аниме от същите разработчици
+ Присъединете се към Discord
+ Дайте банан/и разработчиците
+ Даден/и банан/и
+
+ Език на приложението
+
+ Този доставчик няма поддръжка за Chromecast
+ Няма намерени връзки
+ Връзката е копирана в клипборда
+ Пусни епизода
+ Възстановяване на стойността по подразбиране
+ За съжаление приложението се срина. Анонимен доклад за грешка ще бъде изпратен до
+ разработчиците
+
+ Сезон
+ %s %d%s
+ Без сезон
+ Епизод
+ Епизоди
+ %d-%d
+ %d %s
+ С
+ Е
+ Няма намерени епизоди
+
+ Изтрий файла
+ Изтрий
+ Пауза
+ Продължи
+ -30
+ 30
+ Това ще изтрие за постоянно %s\nСигурни ли сте?
+ %dm\nостава
+
+
+ Продължава
+ Завършен
+ Статус
+ Година
+ Рейтинг
+ Продължителност
+ Уебсайт
+ Синопсис
+
+ На опашката
+ Без субтитри
+ По подразбиране
+
+ Безплатно
+ Използвано
+ Приложения
+
+
+ Филми
+ Телевизионен сериал
+ Анимационни филми
+ Аниме
+ Торенти
+ Документални
+ OVA
+ Азиатски драми
+ На живо
+ Разпоретини
+ други
+
+
+ Филм
+ Серия
+ Анимационен филм
+ @string/anime
+ @string/ova
+ Торент
+ Документален филм
+ Азиатска драма
+ Поток на живо
+ Разпоретинa
+ Видео
+
+ Грешка в източника
+ Дистанционна грешка
+ Грешка в рендъра
+ Неочаквана грешка на плеъра
+ Грешка при изтегляне, проверете разрешенията за съхранение
+
+ Епизод за Chromecast
+ Chromecast огледало
+ Пусни в приложението
+ Пусни в %s
+ Пусни в браузър
+ Копирай връзка
+ Автоматично изтегляне
+ Изтегляне на огледало
+ Презареждане на връзки
+ Изтегляне на субтитри
+
+ покажи качество
+ Покажи дублаж
+ Покажи субтитри
+ Заглавие
+ Превключване на елементите на потребителския интерфейс на плаката
+
+ Няма намерена актуализация
+ Проверка за актуализация
+
+ Заключен
+ Преоразмеряване
+ Източник
+ Пропусни
+
+ Не показвай отново
+ Пропуснете тази актуализация
+ Актуализация
+ Предпочитано качество за гледане
+ Максимален брой знаци за заглавие във видеоплейъра
+ Разделителна способност на видео плейъра
+
+ Размер на видео буфера
+ Дължина на видео буфера
+ Видео кеш на диск
+ Изчистете кеша за видео и изображения
+
+ Ще предизвика случайни сривове, ако е зададено твърде високо. Не променяйте, ако имате малко количество RAM, като Android TV или стар телефон
+ Може да причини проблеми на системи с малко място за съхранение, като устройства с Android TV, ако го зададете твърде високо
+
+ DNS през HTTPS
+ Полезно за заобикаляне на блокирания от ISP доставчик
+
+ Сайт за клониране
+ Премахване на сайта
+ Добавяне на клонинг на съществуващ сайт с различен URL адрес
+
+ Път за изтегляне
+
+ URL адрес на сървъра на Nginx
+
+ Показване на дублирани/субирани аниме
+
+ Побиране в екрана
+ Разтягане
+ Мащабиране
+
+ Опровержение
+
+ Общ
+ Случаен бутон
+ Показване на произволен бутон на началната страница
+ Езици на доставчика
+ Оформление на приложението
+ Предпочитана медия
+ Активирайте разпоретините на поддържани доставчици
+ Кодиране на субтитрите
+ Доставчици
+ Оформление
+
+ Автоматично
+ ТВ оформление
+ Оформление като телефон
+ Оформление като емулатор
+
+ Основен цвят
+ Тема на приложението
+ Местоположение на заглавието на плаката
+ Поставете заглавието под плаката
+
+ парола123
+ MyCoolUsername
+ hello@world.com
+ 127.0.0.1
+ MyCoolSite
+ example.com
+ Езиков код (en)
+
+
+ %s %s
+ Акаунт
+ Излизане
+ Влизане
+ Превключване на акаунт
+ Добавяне на акаунт
+ Създай акаунт
+ Добавете проследяване
+ Добавен %s
+ Синхронизиране
+ Оценен
+ %d / 10
+ /??
+ /%d
+ Удостоверен %s
+ Неуспешно удостоверяване на %s
+
+
+ Нито един
+ Нормално
+ Всичко
+ Макс
+ Мин
+ Контур
+ Удвоени
+ Сянка
+ Повдигнати
+ Синхронизиране на суб
+ 1000ms
+ Забавяне на субтитрите
+ Използвайте това, ако субтитрите се показват %dms твърде рано
+ Използвайте това, ако субтитрите се показват %dms твърде късно
+ Без забавяне на субтитрите
+
+
+
+ Бързата кафява лисица прескача мързеливото куче
+
+ Препоръчва се
+ Заредено %s
+ Зареди от файл
+ Зареди от Интернет
+ Изтеглен файл
+ Главен
+ Поддържащ
+ Заден план
+
+ Източник
+ Случаен
+
+ Очаквайте скоро…
+
+ Cam
+ Cam
+ Cam
+ HQ
+ HD
+ TS
+ TC
+ BlueRay
+ WP
+ DVD
+ 4K
+ SD
+ UHD
+ HDR
+ SDR
+ Web
+
+ Снимка на плакат
+ Плеър
+ Резолюция и заглавие
+ Заглавие
+ Резолюция
+ Невалиден идентификатор
+ Невалидни данни
+ Невалиден адрес
+ Грешка
+ Премахнете затворените надписи от субтитрите
+ Премахнете рекламирането в субтитрите
+ Филтриране по предпочитан медиен език
+ Екстри
+ Трейлър
+ Връзка към потока
+ Обратно към
+ Следващ
+ Гледайте видеоклипове на тези езици
+ Предишен
+ Пропуснете настройката
+ Променете външния вид на приложението, за да отговаря на вашето устройство
+ Докладване за сривове
+ Какво искате да видите
+ Край
+ Разширения
+ Добавяне на хранилище
+ Име на хранилище
+ URL адрес на хранилището
+ Приставката е заредена
+ Приставката е изтрита
+ Неуспешно зареждане %s
+ 18+
+ Започна да изтегля %d %s
+ Изтеглено %d %s успешно
+ Всички %s вече са изтеглени
+ Пакетно изтегляне
+ Плъгин
+ Плъгини
+ Това също ще изтрие всички хранилища за плъгини
+ Изтриване на хранилище
+ Изтеглете списъка със сайтове, които искате да използвате
+ Изтеглено: %d
+ Деактивирано: %d
+ Не е изтеглено: %d
+ Актуализирани %d плъгини
+ CloudStream няма инсталирани сайтове по подразбиране. Трябва да инсталирате сайтовете от хранилища.\n\nПоради безмозъчно премахване на DMCA от Sky Uk Limited 🤮 не можем да свържем сайтовете на хранилищата в приложението.\n\nПрисъединете се към нашия дискорд за връзки или търсете онлайн.
+ Вижте хранилищата на общността
+ Публичен списък
+ Всички субтитри с главни букви
+
+ Изтегляне на всички добавки от това хранилище?
+ %s (Disabled)
+ Потоци
+ Аудио потоци
+ Видео потоци
+ Приложете при рестартиране
+
+ Безопасният режим е активиран
+ Възникна непоправим срив и ние автоматично деактивирахме всички разширения, така че можете да намерите и премахнете разширението, което причинява проблеми.
+ Вижте информация за срива
+
+ Оценка: %s
+ Описание
+ Версия
+ Статус
+ Размер
+ Автори
+ Поддържани
+ език
+ Първо инсталирайте разширението
+
+ HLS плейлист
+
+ Предпочитан видео плеър
+ Вътрешен плеър
+ VLC
+ MPV
+ Уеб видео предаване
+ Браузър
+ Приложението не е намерено
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index d745c7a1..8b3d0fbf 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -22,6 +22,9 @@
#FFF
#000
+ #FFF
+ #000
+
#3d50fa
#121213
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index fb9180f3..e748868b 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -14,4 +14,7 @@
15dp
2000
+
+ 3dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 9840cb80..70ac7516 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -56,6 +56,7 @@
- @color/colorPrimaryDark
- @color/colorAccent
- @color/textColor
+ - @color/whiteText
- @color/grayTextColor
- @color/primaryGrayBackground
- @color/primaryBlackBackground
@@ -75,6 +76,13 @@
- @color/amoledModeLight
+
+
@@ -140,7 +151,7 @@
- @color/colorPrimaryBlue
- #4855A2
- #5A6BCB
- - #5A6BCB
+ - @color/whiteText
- @color/colorPrimaryBlue
@@ -150,7 +161,7 @@
- @color/colorPrimaryPurple
- #4704A3
- #7125DB
- - #7125DB
+ - @color/whiteText
- @color/colorPrimaryPurple
@@ -159,7 +170,7 @@
- @color/colorPrimaryGreen
- #007363
- #39C1AE
- - #39C1AE
+ - @color/blackText
- @color/colorPrimaryGreen
@@ -168,7 +179,7 @@
- @color/colorPrimaryGreenApple
- #319B5A
- #51C57E
- - #51C57E
+ - @color/blackText
- @color/colorPrimaryGreenApple
@@ -177,7 +188,7 @@
- @color/colorPrimaryRed
- #B62B2B
- @color/colorPrimaryRed
- - @color/colorPrimaryRed
+ - @color/whiteText
- @color/colorPrimaryRed
@@ -187,7 +198,7 @@
- @color/colorPrimaryBanana
- #9B7D31
- #C5B251
- - #C5A851
+ - @color/blackText
- @color/colorPrimaryBanana
@@ -196,7 +207,7 @@
- @color/colorPrimaryParty
- #C1495B
- #FD798C
- - #BF5968
+ - @color/blackText
- @color/colorPrimaryParty
@@ -205,7 +216,7 @@
- @color/colorPrimaryPink
- #DD1280
- #FF4DAE
- - #DD1280
+ - @color/blackText
- @color/colorPrimaryPink
@@ -214,7 +225,7 @@
- @color/colorPrimaryCarnationPink
- #83366f
- #BD5DA5
- - #BD5DA5
+ - @color/blackText
- @color/colorPrimaryCarnationPink
@@ -224,7 +235,7 @@
- @color/colorPrimaryMaroon
- #370C0C
- #451010
- - #451010
+ - @color/whiteText
- @color/colorPrimaryMaroon
@@ -234,7 +245,7 @@
- @color/colorPrimaryDarkGreen
- #003d00
- #004500
- - #004500
+ - @color/whiteText
- @color/colorPrimaryDarkGreen
@@ -244,7 +255,7 @@
- @color/colorPrimaryNavyBlue
- #000073
- #000080
- - #000080
+ - @color/whiteText
- @color/colorPrimaryNavyBlue
@@ -254,7 +265,7 @@
- @color/colorPrimaryGrey
- #484848
- #515151
- - #515151
+ - @color/whiteText
- @color/colorPrimaryGrey
@@ -264,7 +275,7 @@
- @color/colorPrimaryWhite
- #CCCCCC
- #FFFFFF
- - #FFFFFF
+ - @color/blackText
- @color/colorPrimaryWhite
@@ -274,7 +285,7 @@
- @color/colorPrimaryBrown
- #582700
- #622C00
- - #622C00
+ - @color/whiteText
- @color/colorPrimaryBrown
diff --git a/app/src/test/java/com/lagradost/cloudstream3/ProviderTests.kt b/app/src/test/java/com/lagradost/cloudstream3/ProviderTests.kt
index 34bd9f18..846dfdb5 100644
--- a/app/src/test/java/com/lagradost/cloudstream3/ProviderTests.kt
+++ b/app/src/test/java/com/lagradost/cloudstream3/ProviderTests.kt
@@ -162,7 +162,7 @@ class ProviderTests {
// @Test
// fun providerCorrectHomepage() {
// runBlocking {
-// getAllProviders().apmap { api ->
+// getAllProviders().amap { api ->
// if (api.hasMainPage) {
// try {
// val homepage = api.getMainPage()
@@ -197,7 +197,7 @@ class ProviderTests {
// suspend fun providerCorrect() {
// val invalidProvider = ArrayList>()
// val providers = getAllProviders()
-// providers.apmap { api ->
+// providers.amap { api ->
// try {
// println("Trying $api")
// if (testSingleProviderApi(api)) {
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 90f9ac32..00000000
--- a/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-buildscript {
- ext.kotlin_version = "1.7.10"
- repositories {
- google()
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:7.2.1'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.5.0"
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- google()
- jcenter()
- }
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 00000000..6e2a24f3
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,26 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath("com.android.tools.build:gradle:7.3.1")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20")
+ classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.5.0")
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle.kts files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+tasks.register("clean", Delete::class) {
+ delete(rootProject.buildDir)
+}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index a2c4ced7..baa28c97 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Fri Apr 30 17:11:15 CEST 2021
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index 5d77faec..00000000
--- a/settings.gradle
+++ /dev/null
@@ -1,2 +0,0 @@
-include ':app'
-rootProject.name = "CloudStream"
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 00000000..17070047
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,3 @@
+rootProject.name = "CloudStream"
+
+include(":app")
\ No newline at end of file