mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
a0c112ad3c
201 changed files with 9890 additions and 6807 deletions
4
.github/workflows/build_to_archive.yml
vendored
4
.github/workflows/build_to_archive.yml
vendored
|
@ -32,10 +32,10 @@ jobs:
|
||||||
private_key: ${{ secrets.GH_APP_KEY }}
|
private_key: ${{ secrets.GH_APP_KEY }}
|
||||||
repository: "recloudstream/cloudstream-archive"
|
repository: "recloudstream/cloudstream-archive"
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
- name: Grant execute permission for gradlew
|
- name: Grant execute permission for gradlew
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
4
.github/workflows/generate_dokka.yml
vendored
4
.github/workflows/generate_dokka.yml
vendored
|
@ -42,10 +42,10 @@ jobs:
|
||||||
cd $GITHUB_WORKSPACE/dokka/
|
cd $GITHUB_WORKSPACE/dokka/
|
||||||
rm -rf "./-cloudstream"
|
rm -rf "./-cloudstream"
|
||||||
|
|
||||||
- name: Setup JDK 11
|
- name: Setup JDK 17
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 11
|
java-version: 17
|
||||||
|
|
||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
uses: android-actions/setup-android@v2
|
uses: android-actions/setup-android@v2
|
||||||
|
|
4
.github/workflows/prerelease.yml
vendored
4
.github/workflows/prerelease.yml
vendored
|
@ -24,10 +24,10 @@ jobs:
|
||||||
private_key: ${{ secrets.GH_APP_KEY }}
|
private_key: ${{ secrets.GH_APP_KEY }}
|
||||||
repository: "recloudstream/secrets"
|
repository: "recloudstream/secrets"
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
- name: Grant execute permission for gradlew
|
- name: Grant execute permission for gradlew
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
4
.github/workflows/pull_request.yml
vendored
4
.github/workflows/pull_request.yml
vendored
|
@ -7,10 +7,10 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up JDK 11
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
- name: Grant execute permission for gradlew
|
- name: Grant execute permission for gradlew
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CompilerConfiguration">
|
<component name="CompilerConfiguration">
|
||||||
<bytecodeTargetLevel target="11" />
|
<bytecodeTargetLevel target="17" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
|
@ -8,7 +8,6 @@
|
||||||
<option name="testRunner" value="GRADLE" />
|
<option name="testRunner" value="GRADLE" />
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="11" />
|
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
|
|
|
@ -7,7 +7,6 @@ plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
id("kotlin-kapt")
|
id("kotlin-kapt")
|
||||||
id("kotlin-android-extensions")
|
|
||||||
id("org.jetbrains.dokka")
|
id("org.jetbrains.dokka")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +27,11 @@ android {
|
||||||
testOptions {
|
testOptions {
|
||||||
unitTests.isReturnDefaultValues = true
|
unitTests.isReturnDefaultValues = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewBinding {
|
||||||
|
enable = true
|
||||||
|
}
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
create("prerelease") {
|
create("prerelease") {
|
||||||
if (prereleaseStoreFile != null) {
|
if (prereleaseStoreFile != null) {
|
||||||
|
@ -48,7 +52,7 @@ android {
|
||||||
targetSdk = 33
|
targetSdk = 33
|
||||||
|
|
||||||
versionCode = 59
|
versionCode = 59
|
||||||
versionName = "4.0.1"
|
versionName = "4.1.1"
|
||||||
|
|
||||||
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
|
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
|
||||||
|
|
||||||
|
@ -143,6 +147,7 @@ dependencies {
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
|
||||||
|
androidTestImplementation("androidx.test:core")
|
||||||
|
|
||||||
//implementation("io.karn:khttp-android:0.1.2") //okhttp instead
|
//implementation("io.karn:khttp-android:0.1.2") //okhttp instead
|
||||||
// implementation("org.jsoup:jsoup:1.13.1")
|
// implementation("org.jsoup:jsoup:1.13.1")
|
||||||
|
@ -215,7 +220,7 @@ dependencies {
|
||||||
|
|
||||||
implementation("com.github.discord:OverlappingPanels:0.1.3")
|
implementation("com.github.discord:OverlappingPanels:0.1.3")
|
||||||
// debugImplementation because LeakCanary should only run in debug builds.
|
// debugImplementation because LeakCanary should only run in debug builds.
|
||||||
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
|
//debugImplementation("com.squareup.leakcanary:leakcanary-android:2.12")
|
||||||
|
|
||||||
// for shimmer when loading
|
// for shimmer when loading
|
||||||
implementation("com.facebook.shimmer:shimmer:0.5.0")
|
implementation("com.facebook.shimmer:shimmer:0.5.0")
|
||||||
|
@ -229,13 +234,13 @@ dependencies {
|
||||||
//implementation("com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT")
|
//implementation("com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT")
|
||||||
|
|
||||||
// newpipe yt taken from https://github.com/TeamNewPipe/NewPipe/blob/dev/app/build.gradle#L204
|
// newpipe yt taken from https://github.com/TeamNewPipe/NewPipe/blob/dev/app/build.gradle#L204
|
||||||
implementation("com.github.TeamNewPipe:NewPipeExtractor:master-SNAPSHOT")
|
implementation("com.github.TeamNewPipe:NewPipeExtractor:8495ad619e")
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.6")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.6")
|
||||||
|
|
||||||
// Library/extensions searching with Levenshtein distance
|
// Library/extensions searching with Levenshtein distance
|
||||||
implementation("me.xdrop:fuzzywuzzy:1.4.0")
|
implementation("me.xdrop:fuzzywuzzy:1.4.0")
|
||||||
|
|
||||||
// color pallette for images -> colors
|
// color palette for images -> colors
|
||||||
implementation("androidx.palette:palette-ktx:1.0.0")
|
implementation("androidx.palette:palette-ktx:1.0.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,30 @@
|
||||||
package com.lagradost.cloudstream3
|
package com.lagradost.cloudstream3
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.PersistableBundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import androidx.test.core.app.ActivityScenario
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentHomeBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentHomeTvBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentPlayerTvBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentResultBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentResultTvBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentSearchBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentSearchTvBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.HomeResultGridBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.HomepageParentBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.HomepageParentTvBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutTvBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.RepositoryItemBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.SearchResultGridBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.SearchResultGridExpandedBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.TrailerCustomLayoutBinding
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||||
import com.lagradost.cloudstream3.utils.TestingUtils
|
import com.lagradost.cloudstream3.utils.TestingUtils
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
@ -8,16 +32,23 @@ import org.junit.Assert
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instrumented test, which will execute on an Android device.
|
* Instrumented test, which will execute on an Android device.
|
||||||
*
|
*
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
*/
|
*/
|
||||||
|
class TestApplication : Activity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
|
||||||
|
super.onCreate(savedInstanceState, persistentState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ExampleInstrumentedTest {
|
class ExampleInstrumentedTest {
|
||||||
private fun getAllProviders(): List<MainAPI> {
|
private fun getAllProviders(): Array<MainAPI> {
|
||||||
println("Providers: ${APIHolder.allProviders.size}")
|
println("Providers: ${APIHolder.allProviders.size}")
|
||||||
return APIHolder.allProviders //.filter { !it.usesWebView }
|
return APIHolder.allProviders.toTypedArray() //.filter { !it.usesWebView }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -26,6 +57,73 @@ class ExampleInstrumentedTest {
|
||||||
println("Done providersExist")
|
println("Done providersExist")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws
|
||||||
|
private inline fun <reified T : ViewBinding> testAllLayouts(
|
||||||
|
activity: Activity,
|
||||||
|
vararg layouts: Int
|
||||||
|
) {
|
||||||
|
|
||||||
|
val bind = T::class.java.methods.first { it.name == "bind" }
|
||||||
|
val inflater = LayoutInflater.from(activity)
|
||||||
|
for (layout in layouts) {
|
||||||
|
val root = inflater.inflate(layout, null, false)
|
||||||
|
bind.invoke(null, root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Throws
|
||||||
|
fun layoutTest() {
|
||||||
|
ActivityScenario.launch(MainActivity::class.java).use { scenario ->
|
||||||
|
scenario.onActivity { activity: MainActivity ->
|
||||||
|
// FragmentHomeHeadBinding and FragmentHomeHeadTvBinding CANT be the same
|
||||||
|
//testAllLayouts<FragmentHomeHeadBinding>(activity, R.layout.fragment_home_head, R.layout.fragment_home_head_tv)
|
||||||
|
//testAllLayouts<FragmentHomeHeadTvBinding>(activity, R.layout.fragment_home_head, R.layout.fragment_home_head_tv)
|
||||||
|
|
||||||
|
// main cant be tested
|
||||||
|
// testAllLayouts<ActivityMainTvBinding>(activity,R.layout.activity_main, R.layout.activity_main_tv)
|
||||||
|
// testAllLayouts<ActivityMainBinding>(activity,R.layout.activity_main, R.layout.activity_main_tv)
|
||||||
|
//testAllLayouts<ActivityMainBinding>(activity, R.layout.activity_main_tv)
|
||||||
|
|
||||||
|
testAllLayouts<FragmentPlayerBinding>(activity, R.layout.fragment_player,R.layout.fragment_player_tv)
|
||||||
|
testAllLayouts<FragmentPlayerTvBinding>(activity, R.layout.fragment_player,R.layout.fragment_player_tv)
|
||||||
|
|
||||||
|
// testAllLayouts<FragmentResultBinding>(activity, R.layout.fragment_result,R.layout.fragment_result_tv)
|
||||||
|
// testAllLayouts<FragmentResultTvBinding>(activity, R.layout.fragment_result,R.layout.fragment_result_tv)
|
||||||
|
|
||||||
|
testAllLayouts<PlayerCustomLayoutBinding>(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv, R.layout.trailer_custom_layout)
|
||||||
|
testAllLayouts<PlayerCustomLayoutTvBinding>(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv, R.layout.trailer_custom_layout)
|
||||||
|
testAllLayouts<TrailerCustomLayoutBinding>(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv, R.layout.trailer_custom_layout)
|
||||||
|
|
||||||
|
testAllLayouts<RepositoryItemBinding>(activity, R.layout.repository_item_tv, R.layout.repository_item)
|
||||||
|
testAllLayouts<RepositoryItemTvBinding>(activity, R.layout.repository_item_tv, R.layout.repository_item)
|
||||||
|
|
||||||
|
testAllLayouts<RepositoryItemBinding>(activity, R.layout.repository_item_tv, R.layout.repository_item)
|
||||||
|
testAllLayouts<RepositoryItemTvBinding>(activity, R.layout.repository_item_tv, R.layout.repository_item)
|
||||||
|
|
||||||
|
testAllLayouts<FragmentHomeBinding>(activity, R.layout.fragment_home_tv, R.layout.fragment_home)
|
||||||
|
testAllLayouts<FragmentHomeTvBinding>(activity, R.layout.fragment_home_tv, R.layout.fragment_home)
|
||||||
|
|
||||||
|
testAllLayouts<FragmentSearchBinding>(activity, R.layout.fragment_search_tv, R.layout.fragment_search)
|
||||||
|
testAllLayouts<FragmentSearchTvBinding>(activity, R.layout.fragment_search_tv, R.layout.fragment_search)
|
||||||
|
|
||||||
|
testAllLayouts<HomeResultGridBinding>(activity, R.layout.home_result_grid_expanded, R.layout.home_result_grid)
|
||||||
|
//testAllLayouts<HomeResultGridExpandedBinding>(activity, R.layout.home_result_grid_expanded, R.layout.home_result_grid) ??? fails ???
|
||||||
|
|
||||||
|
testAllLayouts<SearchResultGridExpandedBinding>(activity, R.layout.search_result_grid, R.layout.search_result_grid_expanded)
|
||||||
|
testAllLayouts<SearchResultGridBinding>(activity, R.layout.search_result_grid, R.layout.search_result_grid_expanded)
|
||||||
|
|
||||||
|
|
||||||
|
// testAllLayouts<HomeScrollViewBinding>(activity, R.layout.home_scroll_view, R.layout.home_scroll_view_tv)
|
||||||
|
// testAllLayouts<HomeScrollViewTvBinding>(activity, R.layout.home_scroll_view, R.layout.home_scroll_view_tv)
|
||||||
|
|
||||||
|
testAllLayouts<HomepageParentTvBinding>(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent)
|
||||||
|
testAllLayouts<HomepageParentBinding>(activity, R.layout.homepage_parent_tv, R.layout.homepage_parent)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Throws(AssertionError::class)
|
@Throws(AssertionError::class)
|
||||||
fun providerCorrectData() {
|
fun providerCorrectData() {
|
||||||
|
@ -49,7 +147,7 @@ class ExampleInstrumentedTest {
|
||||||
@Test
|
@Test
|
||||||
fun providerCorrectHomepage() {
|
fun providerCorrectHomepage() {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
getAllProviders().amap { api ->
|
getAllProviders().toList().amap { api ->
|
||||||
TestingUtils.testHomepage(api, ::println)
|
TestingUtils.testHomepage(api, ::println)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,8 +51,8 @@ class CustomReportSender : ReportSender {
|
||||||
thread { // to not run it on main thread
|
thread { // to not run it on main thread
|
||||||
runBlocking {
|
runBlocking {
|
||||||
suspendSafeApiCall {
|
suspendSafeApiCall {
|
||||||
val post = app.post(url, data = data)
|
app.post(url, data = data)
|
||||||
println("Report response: $post")
|
//println("Report response: $post")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import android.content.res.Resources
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.*
|
import android.view.*
|
||||||
|
import android.view.View.NO_ID
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
@ -27,6 +28,7 @@ import com.lagradost.cloudstream3.ui.player.PlayerEventType
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
||||||
import com.lagradost.cloudstream3.ui.result.UiText
|
import com.lagradost.cloudstream3.ui.result.UiText
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.isRtl
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
import com.lagradost.cloudstream3.utils.Event
|
import com.lagradost.cloudstream3.utils.Event
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper
|
import com.lagradost.cloudstream3.utils.UIHelper
|
||||||
|
@ -34,9 +36,18 @@ import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.shouldShowPIPMode
|
import com.lagradost.cloudstream3.utils.UIHelper.shouldShowPIPMode
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
import org.schabi.newpipe.extractor.NewPipe
|
import org.schabi.newpipe.extractor.NewPipe
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
object CommonActivity {
|
object CommonActivity {
|
||||||
|
|
||||||
|
private var _activity: WeakReference<Activity>? = null
|
||||||
|
var activity
|
||||||
|
get() = _activity?.get()
|
||||||
|
private set(value) {
|
||||||
|
_activity = WeakReference(value)
|
||||||
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
fun Activity?.getCastSession(): CastSession? {
|
fun Activity?.getCastSession(): CastSession? {
|
||||||
return (this as MainActivity?)?.mSessionManager?.currentCastSession
|
return (this as MainActivity?)?.mSessionManager?.currentCastSession
|
||||||
|
@ -56,6 +67,30 @@ object CommonActivity {
|
||||||
|
|
||||||
var currentToast: Toast? = null
|
var currentToast: Toast? = null
|
||||||
|
|
||||||
|
fun showToast(@StringRes message: Int, duration: Int? = null) {
|
||||||
|
val act = activity ?: return
|
||||||
|
act.runOnUiThread {
|
||||||
|
showToast(act, act.getString(message), duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showToast(message: String?, duration: Int? = null) {
|
||||||
|
val act = activity ?: return
|
||||||
|
act.runOnUiThread {
|
||||||
|
showToast(act, message, duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showToast(message: UiText?, duration: Int? = null) {
|
||||||
|
val act = activity ?: return
|
||||||
|
if (message == null) return
|
||||||
|
act.runOnUiThread {
|
||||||
|
showToast(act, message.asString(act), duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@MainThread
|
||||||
fun showToast(act: Activity?, text: UiText, duration: Int) {
|
fun showToast(act: Activity?, text: UiText, duration: Int) {
|
||||||
if (act == null) return
|
if (act == null) return
|
||||||
text.asStringNull(act)?.let {
|
text.asStringNull(act)?.let {
|
||||||
|
@ -140,6 +175,7 @@ object CommonActivity {
|
||||||
|
|
||||||
fun init(act: ComponentActivity?) {
|
fun init(act: ComponentActivity?) {
|
||||||
if (act == null) return
|
if (act == null) return
|
||||||
|
activity = act
|
||||||
//https://stackoverflow.com/questions/52594181/how-to-know-if-user-has-disabled-picture-in-picture-feature-permission
|
//https://stackoverflow.com/questions/52594181/how-to-know-if-user-has-disabled-picture-in-picture-feature-permission
|
||||||
//https://developer.android.com/guide/topics/ui/picture-in-picture
|
//https://developer.android.com/guide/topics/ui/picture-in-picture
|
||||||
canShowPipMode =
|
canShowPipMode =
|
||||||
|
@ -260,27 +296,58 @@ object CommonActivity {
|
||||||
) // THEME IS SET BEFORE VIEW IS CREATED TO APPLY THE THEME TO THE MAIN VIEW
|
) // THEME IS SET BEFORE VIEW IS CREATED TO APPLY THE THEME TO THE MAIN VIEW
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** because we want closes find, aka when multiple have the same id, we go to parent
|
||||||
|
until the correct one is found */
|
||||||
|
private fun localLook(from: View, id: Int): View? {
|
||||||
|
if (id == NO_ID) return null
|
||||||
|
var currentLook: View = from
|
||||||
|
while (true) {
|
||||||
|
currentLook.findViewById<View?>(id)?.let { return it }
|
||||||
|
currentLook = (currentLook.parent as? View) ?: break
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
/*var currentLook: View = view
|
||||||
|
while (true) {
|
||||||
|
val tmpNext = currentLook.findViewById<View?>(nextId)
|
||||||
|
if (tmpNext != null) {
|
||||||
|
next = tmpNext
|
||||||
|
break
|
||||||
|
}
|
||||||
|
currentLook = currentLook.parent as? View ?: break
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/** recursively looks for a next focus up to a depth of 10,
|
||||||
|
* this is used to override the normal shit focus system
|
||||||
|
* because this application has a lot of invisible views that messes with some tv devices*/
|
||||||
private fun getNextFocus(
|
private fun getNextFocus(
|
||||||
act: Activity?,
|
act: Activity?,
|
||||||
view: View?,
|
view: View?,
|
||||||
direction: FocusDirection,
|
direction: FocusDirection,
|
||||||
depth: Int = 0
|
depth: Int = 0
|
||||||
): Int? {
|
): View? {
|
||||||
|
// if input is invalid let android decide + depth test to not crash if loop is found
|
||||||
if (view == null || depth >= 10 || act == null) {
|
if (view == null || depth >= 10 || act == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val nextId = when (direction) {
|
var nextId = when (direction) {
|
||||||
FocusDirection.Left -> {
|
FocusDirection.Start -> {
|
||||||
view.nextFocusLeftId
|
if (view.isRtl())
|
||||||
|
view.nextFocusRightId
|
||||||
|
else
|
||||||
|
view.nextFocusLeftId
|
||||||
}
|
}
|
||||||
|
|
||||||
FocusDirection.Up -> {
|
FocusDirection.Up -> {
|
||||||
view.nextFocusUpId
|
view.nextFocusUpId
|
||||||
}
|
}
|
||||||
|
|
||||||
FocusDirection.Right -> {
|
FocusDirection.End -> {
|
||||||
view.nextFocusRightId
|
if (view.isRtl())
|
||||||
|
view.nextFocusLeftId
|
||||||
|
else
|
||||||
|
view.nextFocusRightId
|
||||||
}
|
}
|
||||||
|
|
||||||
FocusDirection.Down -> {
|
FocusDirection.Down -> {
|
||||||
|
@ -288,27 +355,35 @@ object CommonActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (nextId != -1) {
|
if (nextId == NO_ID) {
|
||||||
val next = act.findViewById<View?>(nextId)
|
// if not specified then use forward id
|
||||||
//println("NAME: ${next.accessibilityClassName} | ${next?.isShown}" )
|
nextId = view.nextFocusForwardId
|
||||||
|
// if view is still not found to next focus then return and let android decide
|
||||||
if (next?.isShown == false) {
|
if (nextId == NO_ID) return null
|
||||||
getNextFocus(act, next, direction, depth + 1)
|
|
||||||
} else {
|
|
||||||
if (depth == 0) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
nextId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var next = act.findViewById<View?>(nextId) ?: return null
|
||||||
|
|
||||||
|
next = localLook(view, nextId) ?: next
|
||||||
|
|
||||||
|
var currentLook: View = view
|
||||||
|
while (currentLook.findViewById<View?>(nextId)?.also { next = it } == null) {
|
||||||
|
currentLook = (currentLook.parent as? View) ?: break
|
||||||
|
}
|
||||||
|
|
||||||
|
// if cant focus but visible then break and let android decide
|
||||||
|
if (!next.isFocusable && next.isShown) return null
|
||||||
|
|
||||||
|
// if not shown then continue because we will "skip" over views to get to a replacement
|
||||||
|
if (!next.isShown) return getNextFocus(act, next, direction, depth + 1)
|
||||||
|
|
||||||
|
// nothing wrong with the view found, return it
|
||||||
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class FocusDirection {
|
private enum class FocusDirection {
|
||||||
Left,
|
Start,
|
||||||
Right,
|
End,
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
}
|
}
|
||||||
|
@ -407,67 +482,64 @@ object CommonActivity {
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** overrides focus and custom key events */
|
||||||
fun dispatchKeyEvent(act: Activity?, event: KeyEvent?): Boolean? {
|
fun dispatchKeyEvent(act: Activity?, event: KeyEvent?): Boolean? {
|
||||||
if (act == null) return null
|
if (act == null) return null
|
||||||
|
val currentFocus = act.currentFocus
|
||||||
|
|
||||||
event?.keyCode?.let { keyCode ->
|
event?.keyCode?.let { keyCode ->
|
||||||
when (event.action) {
|
if (currentFocus == null || event.action != KeyEvent.ACTION_DOWN) return@let
|
||||||
KeyEvent.ACTION_DOWN -> {
|
val nextView = when (keyCode) {
|
||||||
if (act.currentFocus != null) {
|
KeyEvent.KEYCODE_DPAD_LEFT -> getNextFocus(
|
||||||
val next = when (keyCode) {
|
act,
|
||||||
KeyEvent.KEYCODE_DPAD_LEFT -> getNextFocus(
|
currentFocus,
|
||||||
act,
|
FocusDirection.Start
|
||||||
act.currentFocus,
|
)
|
||||||
FocusDirection.Left
|
|
||||||
)
|
|
||||||
|
|
||||||
KeyEvent.KEYCODE_DPAD_RIGHT -> getNextFocus(
|
KeyEvent.KEYCODE_DPAD_RIGHT -> getNextFocus(
|
||||||
act,
|
act,
|
||||||
act.currentFocus,
|
currentFocus,
|
||||||
FocusDirection.Right
|
FocusDirection.End
|
||||||
)
|
)
|
||||||
|
|
||||||
KeyEvent.KEYCODE_DPAD_UP -> getNextFocus(
|
KeyEvent.KEYCODE_DPAD_UP -> getNextFocus(
|
||||||
act,
|
act,
|
||||||
act.currentFocus,
|
currentFocus,
|
||||||
FocusDirection.Up
|
FocusDirection.Up
|
||||||
)
|
)
|
||||||
|
|
||||||
KeyEvent.KEYCODE_DPAD_DOWN -> getNextFocus(
|
KeyEvent.KEYCODE_DPAD_DOWN -> getNextFocus(
|
||||||
act,
|
act,
|
||||||
act.currentFocus,
|
currentFocus,
|
||||||
FocusDirection.Down
|
FocusDirection.Down
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
|
||||||
|
|
||||||
if (next != null && next != -1) {
|
|
||||||
val nextView = act.findViewById<View?>(next)
|
|
||||||
if (nextView != null) {
|
|
||||||
nextView.requestFocus()
|
|
||||||
keyEventListener?.invoke(Pair(event, true))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when (keyCode) {
|
|
||||||
KeyEvent.KEYCODE_DPAD_CENTER -> {
|
|
||||||
if (act.currentFocus is SearchView || act.currentFocus is SearchView.SearchAutoComplete) {
|
|
||||||
UIHelper.showInputMethod(act.currentFocus?.findFocus())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//println("Keycode: $keyCode")
|
|
||||||
//showToast(
|
|
||||||
// this,
|
|
||||||
// "Got Keycode $keyCode | ${KeyEvent.keyCodeToString(keyCode)} \n ${event?.action}",
|
|
||||||
// Toast.LENGTH_LONG
|
|
||||||
//)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nextView != null) {
|
||||||
|
nextView.requestFocus()
|
||||||
|
keyEventListener?.invoke(Pair(event, true))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER &&
|
||||||
|
(act.currentFocus is SearchView || act.currentFocus is SearchView.SearchAutoComplete)
|
||||||
|
) {
|
||||||
|
UIHelper.showInputMethod(act.currentFocus?.findFocus())
|
||||||
|
}
|
||||||
|
|
||||||
|
//println("Keycode: $keyCode")
|
||||||
|
//showToast(
|
||||||
|
// this,
|
||||||
|
// "Got Keycode $keyCode | ${KeyEvent.keyCodeToString(keyCode)} \n ${event?.action}",
|
||||||
|
// Toast.LENGTH_LONG
|
||||||
|
//)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if someone else want to override the focus then don't handle the event as it is already
|
||||||
|
// consumed. used in video player
|
||||||
if (keyEventListener?.invoke(Pair(event, false)) == true) {
|
if (keyEventListener?.invoke(Pair(event, false)) == true) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,10 @@ object APIHolder {
|
||||||
val allProviders = threadSafeListOf<MainAPI>()
|
val allProviders = threadSafeListOf<MainAPI>()
|
||||||
|
|
||||||
fun initAll() {
|
fun initAll() {
|
||||||
for (api in allProviders) {
|
synchronized(allProviders) {
|
||||||
api.init()
|
for (api in allProviders) {
|
||||||
|
api.init()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
apiMap = null
|
apiMap = null
|
||||||
}
|
}
|
||||||
|
@ -64,27 +66,35 @@ object APIHolder {
|
||||||
var apiMap: Map<String, Int>? = null
|
var apiMap: Map<String, Int>? = null
|
||||||
|
|
||||||
fun addPluginMapping(plugin: MainAPI) {
|
fun addPluginMapping(plugin: MainAPI) {
|
||||||
apis = apis + plugin
|
synchronized(apis) {
|
||||||
|
apis = apis + plugin
|
||||||
|
}
|
||||||
initMap(true)
|
initMap(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removePluginMapping(plugin: MainAPI) {
|
fun removePluginMapping(plugin: MainAPI) {
|
||||||
apis = apis.filter { it != plugin }
|
synchronized(apis) {
|
||||||
|
apis = apis.filter { it != plugin }
|
||||||
|
}
|
||||||
initMap(true)
|
initMap(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initMap(forcedUpdate: Boolean = false) {
|
private fun initMap(forcedUpdate: Boolean = false) {
|
||||||
if (apiMap == null || forcedUpdate)
|
synchronized(apis) {
|
||||||
apiMap = apis.mapIndexed { index, api -> api.name to index }.toMap()
|
if (apiMap == null || forcedUpdate)
|
||||||
|
apiMap = apis.mapIndexed { index, api -> api.name to index }.toMap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getApiFromNameNull(apiName: String?): MainAPI? {
|
fun getApiFromNameNull(apiName: String?): MainAPI? {
|
||||||
if (apiName == null) return null
|
if (apiName == null) return null
|
||||||
synchronized(allProviders) {
|
synchronized(allProviders) {
|
||||||
initMap()
|
initMap()
|
||||||
return apiMap?.get(apiName)?.let { apis.getOrNull(it) }
|
synchronized(apis) {
|
||||||
// Leave the ?. null check, it can crash regardless
|
return apiMap?.get(apiName)?.let { apis.getOrNull(it) }
|
||||||
?: allProviders.firstOrNull { it.name == apiName }
|
// Leave the ?. null check, it can crash regardless
|
||||||
|
?: allProviders.firstOrNull { it.name == apiName }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,7 +225,7 @@ object APIHolder {
|
||||||
val hashSet = HashSet<String>()
|
val hashSet = HashSet<String>()
|
||||||
val activeLangs = getApiProviderLangSettings()
|
val activeLangs = getApiProviderLangSettings()
|
||||||
val hasUniversal = activeLangs.contains(AllLanguagesName)
|
val hasUniversal = activeLangs.contains(AllLanguagesName)
|
||||||
hashSet.addAll(apis.filter { hasUniversal || activeLangs.contains(it.lang) }
|
hashSet.addAll(synchronized(apis) { apis.filter { hasUniversal || activeLangs.contains(it.lang) } }
|
||||||
.map { it.name })
|
.map { it.name })
|
||||||
|
|
||||||
/*val set = settingsManager.getStringSet(
|
/*val set = settingsManager.getStringSet(
|
||||||
|
@ -314,8 +324,9 @@ object APIHolder {
|
||||||
} ?: default
|
} ?: default
|
||||||
val langs = this.getApiProviderLangSettings()
|
val langs = this.getApiProviderLangSettings()
|
||||||
val hasUniversal = langs.contains(AllLanguagesName)
|
val hasUniversal = langs.contains(AllLanguagesName)
|
||||||
val allApis = apis.filter { hasUniversal || langs.contains(it.lang) }
|
val allApis = synchronized(apis) {
|
||||||
.filter { api -> api.hasMainPage || !hasHomePageIsRequired }
|
apis.filter { api -> (hasUniversal || langs.contains(api.lang)) && (api.hasMainPage || !hasHomePageIsRequired) }
|
||||||
|
}
|
||||||
return if (currentPrefMedia.isEmpty()) {
|
return if (currentPrefMedia.isEmpty()) {
|
||||||
allApis
|
allApis
|
||||||
} else {
|
} else {
|
||||||
|
@ -736,6 +747,7 @@ fun fixTitle(str: String): String {
|
||||||
.replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else it }
|
.replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get rhino context in a safe way as it needs to be initialized on the main thread.
|
* Get rhino context in a safe way as it needs to be initialized on the main thread.
|
||||||
* Make sure you get the scope using: val scope: Scriptable = rhino.initSafeStandardObjects()
|
* Make sure you get the scope using: val scope: Scriptable = rhino.initSafeStandardObjects()
|
||||||
|
@ -1122,7 +1134,7 @@ interface LoadResponse {
|
||||||
var isTrailersEnabled = true
|
var isTrailersEnabled = true
|
||||||
|
|
||||||
fun LoadResponse.isMovie(): Boolean {
|
fun LoadResponse.isMovie(): Boolean {
|
||||||
return this.type.isMovieType()
|
return this.type.isMovieType() || this is MovieLoadResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmName("addActorNames")
|
@JvmName("addActorNames")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.lagradost.cloudstream3
|
package com.lagradost.cloudstream3
|
||||||
|
|
||||||
|
import android.animation.ValueAnimator
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
@ -14,13 +15,17 @@ import android.view.KeyEvent
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.annotation.IdRes
|
import androidx.annotation.IdRes
|
||||||
|
import androidx.annotation.MainThread
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.view.children
|
||||||
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
@ -40,6 +45,7 @@ import com.google.android.gms.cast.framework.CastContext
|
||||||
import com.google.android.gms.cast.framework.Session
|
import com.google.android.gms.cast.framework.Session
|
||||||
import com.google.android.gms.cast.framework.SessionManager
|
import com.google.android.gms.cast.framework.SessionManager
|
||||||
import com.google.android.gms.cast.framework.SessionManagerListener
|
import com.google.android.gms.cast.framework.SessionManagerListener
|
||||||
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.navigationrail.NavigationRailView
|
import com.google.android.material.navigationrail.NavigationRailView
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
@ -58,6 +64,11 @@ import com.lagradost.cloudstream3.CommonActivity.onUserLeaveHint
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.CommonActivity.updateLocale
|
import com.lagradost.cloudstream3.CommonActivity.updateLocale
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
|
import com.lagradost.cloudstream3.databinding.ActivityMainBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.ActivityMainTvBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.BottomResultviewPreviewBinding
|
||||||
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
|
import com.lagradost.cloudstream3.mvvm.debugAssert
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.mvvm.observeNullable
|
import com.lagradost.cloudstream3.mvvm.observeNullable
|
||||||
|
@ -69,7 +80,7 @@ import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringPlayer
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringSearch
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringSearch
|
||||||
|
@ -85,6 +96,7 @@ import com.lagradost.cloudstream3.ui.result.ResultViewModel2
|
||||||
import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
|
import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
|
||||||
import com.lagradost.cloudstream3.ui.result.setImage
|
import com.lagradost.cloudstream3.ui.result.setImage
|
||||||
import com.lagradost.cloudstream3.ui.result.setText
|
import com.lagradost.cloudstream3.ui.result.setText
|
||||||
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchFragment
|
import com.lagradost.cloudstream3.ui.search.SearchFragment
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
|
||||||
|
@ -97,12 +109,14 @@ import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
|
||||||
import com.lagradost.cloudstream3.utils.ApkInstaller
|
import com.lagradost.cloudstream3.utils.ApkInstaller
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
|
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.isLtr
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isNetworkAvailable
|
import com.lagradost.cloudstream3.utils.AppUtils.isNetworkAvailable
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
|
import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
||||||
|
import com.lagradost.cloudstream3.utils.BackupUtils.backup
|
||||||
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
|
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
|
@ -121,32 +135,19 @@ import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
|
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
|
||||||
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
|
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
|
||||||
import com.lagradost.nicehttp.Requests
|
import com.lagradost.nicehttp.Requests
|
||||||
import com.lagradost.nicehttp.ResponseParser
|
import com.lagradost.nicehttp.ResponseParser
|
||||||
import kotlinx.android.synthetic.main.activity_main.cast_mini_controller_holder
|
|
||||||
import kotlinx.android.synthetic.main.activity_main.nav_host_fragment
|
|
||||||
import kotlinx.android.synthetic.main.activity_main.nav_rail_view
|
|
||||||
import kotlinx.android.synthetic.main.activity_main.nav_view
|
|
||||||
import kotlinx.android.synthetic.main.bottom_resultview_preview.resultview_preview_description
|
|
||||||
import kotlinx.android.synthetic.main.bottom_resultview_preview.resultview_preview_loading
|
|
||||||
import kotlinx.android.synthetic.main.bottom_resultview_preview.resultview_preview_loading_shimmer
|
|
||||||
import kotlinx.android.synthetic.main.bottom_resultview_preview.resultview_preview_meta_duration
|
|
||||||
import kotlinx.android.synthetic.main.bottom_resultview_preview.resultview_preview_meta_rating
|
|
||||||
import kotlinx.android.synthetic.main.bottom_resultview_preview.resultview_preview_meta_type
|
|
||||||
import kotlinx.android.synthetic.main.bottom_resultview_preview.resultview_preview_meta_year
|
|
||||||
import kotlinx.android.synthetic.main.bottom_resultview_preview.resultview_preview_more_info
|
|
||||||
import kotlinx.android.synthetic.main.bottom_resultview_preview.resultview_preview_poster
|
|
||||||
import kotlinx.android.synthetic.main.bottom_resultview_preview.resultview_preview_result
|
|
||||||
import kotlinx.android.synthetic.main.bottom_resultview_preview.resultview_preview_title
|
|
||||||
import kotlinx.android.synthetic.main.fragment_result_swipe.media_route_button
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
@ -334,7 +335,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
this@with.runOnUiThread {
|
this@with.runOnUiThread {
|
||||||
try {
|
try {
|
||||||
showToast(
|
showToast(
|
||||||
this@with,
|
|
||||||
getString(if (isSuccessful) R.string.authenticated_user else R.string.authenticated_user_fail).format(
|
getString(if (isSuccessful) R.string.authenticated_user else R.string.authenticated_user_fail).format(
|
||||||
api.name
|
api.name
|
||||||
)
|
)
|
||||||
|
@ -362,8 +362,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
|
|
||||||
// Use both navigation views to support both layouts.
|
// Use both navigation views to support both layouts.
|
||||||
// It might be better to use the QuickSearch.
|
// It might be better to use the QuickSearch.
|
||||||
nav_view?.selectedItemId = R.id.navigation_search
|
activity?.findViewById<BottomNavigationView>(R.id.nav_view)?.selectedItemId =
|
||||||
nav_rail_view?.selectedItemId = R.id.navigation_search
|
R.id.navigation_search
|
||||||
|
activity?.findViewById<NavigationRailView>(R.id.nav_rail_view)?.selectedItemId =
|
||||||
|
R.id.navigation_search
|
||||||
} else if (safeURI(str)?.scheme == appStringPlayer) {
|
} else if (safeURI(str)?.scheme == appStringPlayer) {
|
||||||
val uri = Uri.parse(str)
|
val uri = Uri.parse(str)
|
||||||
val name = uri.getQueryParameter("name")
|
val name = uri.getQueryParameter("name")
|
||||||
|
@ -396,10 +398,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
this.navigate(R.id.navigation_downloads)
|
this.navigate(R.id.navigation_downloads)
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
for (api in apis) {
|
synchronized(apis) {
|
||||||
if (str.startsWith(api.mainUrl)) {
|
for (api in apis) {
|
||||||
loadResult(str, api.name)
|
if (str.startsWith(api.mainUrl)) {
|
||||||
return true
|
loadResult(str, api.name)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -440,7 +444,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
this.hideKeyboard()
|
this.hideKeyboard()
|
||||||
|
|
||||||
// Fucks up anime info layout since that has its own layout
|
// Fucks up anime info layout since that has its own layout
|
||||||
cast_mini_controller_holder?.isVisible =
|
binding?.castMiniControllerHolder?.isVisible =
|
||||||
!listOf(
|
!listOf(
|
||||||
R.id.navigation_results_phone,
|
R.id.navigation_results_phone,
|
||||||
R.id.navigation_results_tv,
|
R.id.navigation_results_tv,
|
||||||
|
@ -476,15 +480,27 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
R.id.navigation_player,
|
R.id.navigation_player,
|
||||||
).contains(destination.id)
|
).contains(destination.id)
|
||||||
|
|
||||||
nav_host_fragment?.apply {
|
binding?.navHostFragment?.apply {
|
||||||
val params = layoutParams as ConstraintLayout.LayoutParams
|
val params = layoutParams as ConstraintLayout.LayoutParams
|
||||||
|
val push =
|
||||||
|
if (!dontPush && isTvSettings()) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0
|
||||||
|
|
||||||
|
if (!this.isLtr()) {
|
||||||
|
params.setMargins(
|
||||||
|
params.leftMargin,
|
||||||
|
params.topMargin,
|
||||||
|
push,
|
||||||
|
params.bottomMargin
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
params.setMargins(
|
||||||
|
push,
|
||||||
|
params.topMargin,
|
||||||
|
params.rightMargin,
|
||||||
|
params.bottomMargin
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
params.setMargins(
|
|
||||||
if (!dontPush && isTvSettings()) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0,
|
|
||||||
params.topMargin,
|
|
||||||
params.rightMargin,
|
|
||||||
params.bottomMargin
|
|
||||||
)
|
|
||||||
layoutParams = params
|
layoutParams = params
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,21 +510,22 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
Configuration.ORIENTATION_PORTRAIT -> {
|
Configuration.ORIENTATION_PORTRAIT -> {
|
||||||
false
|
isTvSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding?.apply {
|
||||||
|
navView.isVisible = isNavVisible && !landscape
|
||||||
|
navRailView.isVisible = isNavVisible && landscape
|
||||||
|
|
||||||
nav_view?.isVisible = isNavVisible && !landscape
|
// Hide library on TV since it is not supported yet :(
|
||||||
nav_rail_view?.isVisible = isNavVisible && landscape
|
val isTrueTv = isTrueTvSettings()
|
||||||
|
navView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv
|
||||||
// Hide library on TV since it is not supported yet :(
|
navRailView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv
|
||||||
val isTrueTv = isTrueTvSettings()
|
}
|
||||||
nav_view?.menu?.findItem(R.id.navigation_library)?.isVisible = !isTrueTv
|
|
||||||
nav_rail_view?.menu?.findItem(R.id.navigation_library)?.isVisible = !isTrueTv
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//private var mCastSession: CastSession? = null
|
//private var mCastSession: CastSession? = null
|
||||||
|
@ -576,11 +593,23 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
||||||
CommonActivity.dispatchKeyEvent(this, event)?.let {
|
val start = System.currentTimeMillis()
|
||||||
return it
|
try {
|
||||||
|
val response = CommonActivity.dispatchKeyEvent(this, event)
|
||||||
|
|
||||||
|
if (response != null)
|
||||||
|
return response
|
||||||
|
} finally {
|
||||||
|
debugAssert({
|
||||||
|
val end = System.currentTimeMillis()
|
||||||
|
val delta = end - start
|
||||||
|
delta > 100
|
||||||
|
}) {
|
||||||
|
"Took over 100ms to navigate, smth is VERY wrong"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.dispatchKeyEvent(event)
|
return super.dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -685,27 +714,29 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
private fun onAllPluginsLoaded(success: Boolean = false) {
|
private fun onAllPluginsLoaded(success: Boolean = false) {
|
||||||
ioSafe {
|
ioSafe {
|
||||||
pluginsLock.withLock {
|
pluginsLock.withLock {
|
||||||
// Load cloned sites after plugins have been loaded since clones depend on plugins.
|
synchronized(allProviders) {
|
||||||
try {
|
// Load cloned sites after plugins have been loaded since clones depend on plugins.
|
||||||
getKey<Array<SettingsGeneral.CustomSite>>(USER_PROVIDER_API)?.let { list ->
|
try {
|
||||||
list.forEach { custom ->
|
getKey<Array<SettingsGeneral.CustomSite>>(USER_PROVIDER_API)?.let { list ->
|
||||||
allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass }
|
list.forEach { custom ->
|
||||||
?.let {
|
allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass }
|
||||||
allProviders.add(it.javaClass.newInstance().apply {
|
?.let {
|
||||||
name = custom.name
|
allProviders.add(it.javaClass.newInstance().apply {
|
||||||
lang = custom.lang
|
name = custom.name
|
||||||
mainUrl = custom.url.trimEnd('/')
|
lang = custom.lang
|
||||||
canBeOverridden = false
|
mainUrl = custom.url.trimEnd('/')
|
||||||
})
|
canBeOverridden = false
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// it.hashCode() is not enough to make sure they are distinct
|
||||||
|
apis =
|
||||||
|
allProviders.distinctBy { it.lang + it.name + it.mainUrl + it.javaClass.name }
|
||||||
|
APIHolder.apiMap = null
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
}
|
}
|
||||||
// it.hashCode() is not enough to make sure they are distinct
|
|
||||||
apis =
|
|
||||||
allProviders.distinctBy { it.lang + it.name + it.mainUrl + it.javaClass.name }
|
|
||||||
APIHolder.apiMap = null
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logError(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -721,28 +752,217 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hidePreviewPopupDialog() {
|
private fun hidePreviewPopupDialog() {
|
||||||
viewModel.clear()
|
|
||||||
bottomPreviewPopup.dismissSafe(this)
|
bottomPreviewPopup.dismissSafe(this)
|
||||||
|
bottomPreviewPopup = null
|
||||||
|
bottomPreviewBinding = null
|
||||||
}
|
}
|
||||||
|
|
||||||
var bottomPreviewPopup: BottomSheetDialog? = null
|
private var bottomPreviewPopup: BottomSheetDialog? = null
|
||||||
private fun showPreviewPopupDialog(): BottomSheetDialog {
|
private var bottomPreviewBinding: BottomResultviewPreviewBinding? = null
|
||||||
val ret = (bottomPreviewPopup ?: run {
|
private fun showPreviewPopupDialog(): BottomResultviewPreviewBinding {
|
||||||
|
val ret = (bottomPreviewBinding ?: run {
|
||||||
val builder =
|
val builder =
|
||||||
BottomSheetDialog(this)
|
BottomSheetDialog(this)
|
||||||
builder.setContentView(R.layout.bottom_resultview_preview)
|
val binding: BottomResultviewPreviewBinding =
|
||||||
|
BottomResultviewPreviewBinding.inflate(builder.layoutInflater, null, false)
|
||||||
|
bottomPreviewBinding = binding
|
||||||
|
builder.setContentView(binding.root)
|
||||||
builder.setOnDismissListener {
|
builder.setOnDismissListener {
|
||||||
bottomPreviewPopup = null
|
bottomPreviewPopup = null
|
||||||
|
bottomPreviewBinding = null
|
||||||
viewModel.clear()
|
viewModel.clear()
|
||||||
}
|
}
|
||||||
builder.setCanceledOnTouchOutside(true)
|
builder.setCanceledOnTouchOutside(true)
|
||||||
builder.show()
|
builder.show()
|
||||||
builder
|
bottomPreviewPopup = builder
|
||||||
|
binding
|
||||||
})
|
})
|
||||||
bottomPreviewPopup = ret
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var binding: ActivityMainBinding? = null
|
||||||
|
|
||||||
|
object TvFocus {
|
||||||
|
data class FocusTarget(
|
||||||
|
val width: Int,
|
||||||
|
val height: Int,
|
||||||
|
val x: Float,
|
||||||
|
val y: Float,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun lerp(a: FocusTarget, b: FocusTarget, lerp: Float): FocusTarget {
|
||||||
|
val ilerp = 1 - lerp
|
||||||
|
return FocusTarget(
|
||||||
|
width = (a.width * ilerp + b.width * lerp).toInt(),
|
||||||
|
height = (a.height * ilerp + b.height * lerp).toInt(),
|
||||||
|
x = a.x * ilerp + b.x * lerp,
|
||||||
|
y = a.y * ilerp + b.y * lerp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var last: FocusTarget = FocusTarget(0, 0, 0.0f, 0.0f)
|
||||||
|
var current: FocusTarget = FocusTarget(0, 0, 0.0f, 0.0f)
|
||||||
|
|
||||||
|
var focusOutline: WeakReference<View> = WeakReference(null)
|
||||||
|
var lastFocus: WeakReference<View> = WeakReference(null)
|
||||||
|
private val layoutListener: View.OnLayoutChangeListener =
|
||||||
|
View.OnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
|
||||||
|
updateFocusView(
|
||||||
|
v, same = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private val attachListener: View.OnAttachStateChangeListener =
|
||||||
|
object : View.OnAttachStateChangeListener {
|
||||||
|
override fun onViewAttachedToWindow(v: View) {
|
||||||
|
updateFocusView(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewDetachedFromWindow(v: View) {
|
||||||
|
// removes the focus view but not the listener as updateFocusView(null) will remove the listener
|
||||||
|
focusOutline.get()?.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setTargetPosition(target: FocusTarget) {
|
||||||
|
focusOutline.get()?.apply {
|
||||||
|
layoutParams = layoutParams?.apply {
|
||||||
|
width = target.width
|
||||||
|
height = target.height
|
||||||
|
}
|
||||||
|
|
||||||
|
translationX = target.x
|
||||||
|
translationY = target.y
|
||||||
|
bringToFront()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var animator: ValueAnimator? = null
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
fun updateFocusView(newFocus: View?, same: Boolean = false) {
|
||||||
|
val focusOutline = focusOutline.get() ?: return
|
||||||
|
lastFocus.get()?.apply {
|
||||||
|
removeOnLayoutChangeListener(layoutListener)
|
||||||
|
removeOnAttachStateChangeListener(attachListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
val wasGone = focusOutline.isGone
|
||||||
|
|
||||||
|
val visible =
|
||||||
|
newFocus != null && newFocus.measuredHeight > 0 && newFocus.measuredWidth > 0 && newFocus.isShown && newFocus.tag != "tv_no_focus_tag"
|
||||||
|
focusOutline.isVisible = visible
|
||||||
|
|
||||||
|
if (newFocus != null) {
|
||||||
|
lastFocus = WeakReference(newFocus)
|
||||||
|
|
||||||
|
val out = IntArray(2)
|
||||||
|
newFocus.getLocationInWindow(out)
|
||||||
|
val (screenX, screenY) = out
|
||||||
|
var (x, y) = screenX.toFloat() to screenY.toFloat()
|
||||||
|
val (currentX, currentY) = focusOutline.translationX to focusOutline.translationY
|
||||||
|
// println(">><<< $x $y $currentX $currentY")
|
||||||
|
if (!newFocus.isLtr()) {
|
||||||
|
x = x - focusOutline.rootView.width + newFocus.measuredWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
// out of bounds = 0,0
|
||||||
|
if (screenX == 0 && screenY == 0) {
|
||||||
|
focusOutline.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
newFocus.addOnLayoutChangeListener(layoutListener)
|
||||||
|
newFocus.addOnAttachStateChangeListener(attachListener)
|
||||||
|
|
||||||
|
val start = FocusTarget(
|
||||||
|
x = currentX,
|
||||||
|
y = currentY,
|
||||||
|
width = focusOutline.measuredWidth,
|
||||||
|
height = focusOutline.measuredHeight
|
||||||
|
)
|
||||||
|
val end = FocusTarget(
|
||||||
|
x = x,
|
||||||
|
y = y,
|
||||||
|
width = newFocus.measuredWidth,
|
||||||
|
height = newFocus.measuredHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
// if they are the same within then snap, aka scrolling
|
||||||
|
val deltaMin = 50.toPx
|
||||||
|
if (start.width == end.width && start.height == end.height && (start.x - end.x).absoluteValue < deltaMin && (start.y - end.y).absoluteValue < deltaMin) {
|
||||||
|
animator?.cancel()
|
||||||
|
last = start
|
||||||
|
current = end
|
||||||
|
setTargetPosition(end)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if running then "reuse"
|
||||||
|
if (animator?.isRunning == true) {
|
||||||
|
current = end
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
animator?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
last = start
|
||||||
|
current = end
|
||||||
|
|
||||||
|
// if previously gone, then tp
|
||||||
|
if (wasGone) {
|
||||||
|
setTargetPosition(current)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// animate between a and b
|
||||||
|
animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply {
|
||||||
|
startDelay = 0
|
||||||
|
duration = 100
|
||||||
|
addUpdateListener { animation ->
|
||||||
|
val animatedValue = animation.animatedValue as Float
|
||||||
|
val target = FocusTarget.lerp(last, current, minOf(animatedValue, 1.0f))
|
||||||
|
setTargetPosition(target)
|
||||||
|
}
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// post check
|
||||||
|
if (!same) {
|
||||||
|
newFocus.postDelayed({
|
||||||
|
updateFocusView(lastFocus.get(), same = true)
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
the following is working, but somewhat bad code code
|
||||||
|
|
||||||
|
if (!wasGone) {
|
||||||
|
(focusOutline.parent as? ViewGroup)?.let {
|
||||||
|
TransitionManager.endTransitions(it)
|
||||||
|
TransitionManager.beginDelayedTransition(
|
||||||
|
it,
|
||||||
|
TransitionSet().addTransition(ChangeBounds())
|
||||||
|
.addTransition(ChangeTransform())
|
||||||
|
.setDuration(100)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
focusOutline.layoutParams = focusOutline.layoutParams?.apply {
|
||||||
|
width = newFocus.measuredWidth
|
||||||
|
height = newFocus.measuredHeight
|
||||||
|
}
|
||||||
|
focusOutline.translationX = x.toFloat()
|
||||||
|
focusOutline.translationY = y.toFloat()*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
app.initClient(this)
|
app.initClient(this)
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
@ -767,16 +987,48 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
if (isCastApiAvailable()) {
|
if (isCastApiAvailable()) {
|
||||||
mSessionManager = CastContext.getSharedInstance(this).sessionManager
|
mSessionManager = CastContext.getSharedInstance(this).sessionManager
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (t: Throwable) {
|
||||||
logError(e)
|
logError(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
|
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
|
||||||
updateTv()
|
updateTv()
|
||||||
if (isTvSettings()) {
|
|
||||||
setContentView(R.layout.activity_main_tv)
|
// backup when we update the app, I don't trust myself to not boot lock users, might want to make this a setting?
|
||||||
} else {
|
try {
|
||||||
setContentView(R.layout.activity_main)
|
val appVer = BuildConfig.VERSION_NAME
|
||||||
|
val lastAppAutoBackup: String = getKey("VERSION_NAME") ?: ""
|
||||||
|
if (appVer != lastAppAutoBackup) {
|
||||||
|
setKey("VERSION_NAME", BuildConfig.VERSION_NAME)
|
||||||
|
backup()
|
||||||
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
logError(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// just in case, MAIN SHOULD *NEVER* BOOT LOOP CRASH
|
||||||
|
binding = try {
|
||||||
|
if (isTvSettings()) {
|
||||||
|
val newLocalBinding = ActivityMainTvBinding.inflate(layoutInflater, null, false)
|
||||||
|
setContentView(newLocalBinding.root)
|
||||||
|
TvFocus.focusOutline = WeakReference(newLocalBinding.focusOutline)
|
||||||
|
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
|
||||||
|
// println("refocus $oldFocus -> $newFocus")
|
||||||
|
TvFocus.updateFocusView(newFocus)
|
||||||
|
}
|
||||||
|
newLocalBinding.root.viewTreeObserver.addOnScrollChangedListener {
|
||||||
|
TvFocus.updateFocusView(TvFocus.lastFocus.get(), same = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
ActivityMainBinding.bind(newLocalBinding.root) // this may crash
|
||||||
|
} else {
|
||||||
|
val newLocalBinding = ActivityMainBinding.inflate(layoutInflater, null, false)
|
||||||
|
setContentView(newLocalBinding.root)
|
||||||
|
newLocalBinding
|
||||||
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
showToast(txt(R.string.unable_to_inflate, t.message ?: ""), Toast.LENGTH_LONG)
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
changeStatusBarState(isEmulatorSettings())
|
changeStatusBarState(isEmulatorSettings())
|
||||||
|
@ -807,7 +1059,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
|
|
||||||
if (PluginManager.checkSafeModeFile()) {
|
if (PluginManager.checkSafeModeFile()) {
|
||||||
normalSafeApiCall {
|
normalSafeApiCall {
|
||||||
showToast(this, R.string.safe_mode_file, Toast.LENGTH_LONG)
|
showToast(R.string.safe_mode_file, Toast.LENGTH_LONG)
|
||||||
}
|
}
|
||||||
} else if (lastError == null) {
|
} else if (lastError == null) {
|
||||||
ioSafe {
|
ioSafe {
|
||||||
|
@ -861,43 +1113,44 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
|
|
||||||
observeNullable(viewModel.page) { resource ->
|
observeNullable(viewModel.page) { resource ->
|
||||||
if (resource == null) {
|
if (resource == null) {
|
||||||
bottomPreviewPopup.dismissSafe(this)
|
hidePreviewPopupDialog()
|
||||||
return@observeNullable
|
return@observeNullable
|
||||||
}
|
}
|
||||||
when (resource) {
|
when (resource) {
|
||||||
is Resource.Failure -> {
|
is Resource.Failure -> {
|
||||||
showToast(this, R.string.error)
|
showToast(R.string.error)
|
||||||
|
viewModel.clear()
|
||||||
hidePreviewPopupDialog()
|
hidePreviewPopupDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Loading -> {
|
is Resource.Loading -> {
|
||||||
showPreviewPopupDialog().apply {
|
showPreviewPopupDialog().apply {
|
||||||
resultview_preview_loading?.isVisible = true
|
resultviewPreviewLoading.isVisible = true
|
||||||
resultview_preview_result?.isVisible = false
|
resultviewPreviewResult.isVisible = false
|
||||||
resultview_preview_loading_shimmer?.startShimmer()
|
resultviewPreviewLoadingShimmer.startShimmer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
val d = resource.value
|
val d = resource.value
|
||||||
showPreviewPopupDialog().apply {
|
showPreviewPopupDialog().apply {
|
||||||
resultview_preview_loading?.isVisible = false
|
resultviewPreviewLoading.isVisible = false
|
||||||
resultview_preview_result?.isVisible = true
|
resultviewPreviewResult.isVisible = true
|
||||||
resultview_preview_loading_shimmer?.stopShimmer()
|
resultviewPreviewLoadingShimmer.stopShimmer()
|
||||||
|
|
||||||
resultview_preview_title?.text = d.title
|
resultviewPreviewTitle.text = d.title
|
||||||
|
|
||||||
resultview_preview_meta_type.setText(d.typeText)
|
resultviewPreviewMetaType.setText(d.typeText)
|
||||||
resultview_preview_meta_year.setText(d.yearText)
|
resultviewPreviewMetaYear.setText(d.yearText)
|
||||||
resultview_preview_meta_duration.setText(d.durationText)
|
resultviewPreviewMetaDuration.setText(d.durationText)
|
||||||
resultview_preview_meta_rating.setText(d.ratingText)
|
resultviewPreviewMetaRating.setText(d.ratingText)
|
||||||
|
|
||||||
resultview_preview_description?.setText(d.plotText)
|
resultviewPreviewDescription.setText(d.plotText)
|
||||||
resultview_preview_poster?.setImage(
|
resultviewPreviewPoster.setImage(
|
||||||
d.posterImage ?: d.posterBackgroundImage
|
d.posterImage ?: d.posterBackgroundImage
|
||||||
)
|
)
|
||||||
|
|
||||||
resultview_preview_poster?.setOnClickListener {
|
resultviewPreviewPoster.setOnClickListener {
|
||||||
//viewModel.updateWatchStatus(WatchType.PLANTOWATCH)
|
//viewModel.updateWatchStatus(WatchType.PLANTOWATCH)
|
||||||
val value = viewModel.watchStatus.value ?: WatchType.NONE
|
val value = viewModel.watchStatus.value ?: WatchType.NONE
|
||||||
|
|
||||||
|
@ -908,12 +1161,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
showApply = false,
|
showApply = false,
|
||||||
{}) {
|
{}) {
|
||||||
viewModel.updateWatchStatus(WatchType.values()[it])
|
viewModel.updateWatchStatus(WatchType.values()[it])
|
||||||
bookmarksUpdatedEvent(true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isTvSettings()) // dont want this clickable on tv layout
|
if (!isTvSettings()) // dont want this clickable on tv layout
|
||||||
resultview_preview_description?.setOnClickListener { view ->
|
resultviewPreviewDescription.setOnClickListener { view ->
|
||||||
view.context?.let { ctx ->
|
view.context?.let { ctx ->
|
||||||
val builder: AlertDialog.Builder =
|
val builder: AlertDialog.Builder =
|
||||||
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
|
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
|
||||||
|
@ -923,7 +1175,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resultview_preview_more_info?.setOnClickListener {
|
resultviewPreviewMoreInfo.setOnClickListener {
|
||||||
|
viewModel.clear()
|
||||||
hidePreviewPopupDialog()
|
hidePreviewPopupDialog()
|
||||||
lastPopup?.let {
|
lastPopup?.let {
|
||||||
loadSearchResult(it)
|
loadSearchResult(it)
|
||||||
|
@ -964,7 +1217,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
ioSafe {
|
ioSafe {
|
||||||
initAll()
|
initAll()
|
||||||
// No duplicates (which can happen by registerMainAPI)
|
// No duplicates (which can happen by registerMainAPI)
|
||||||
apis = allProviders.distinctBy { it }
|
apis = synchronized(allProviders) {
|
||||||
|
allProviders.distinctBy { it }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
// val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
||||||
|
@ -977,6 +1232,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
|
|
||||||
navController.addOnDestinationChangedListener { _: NavController, navDestination: NavDestination, bundle: Bundle? ->
|
navController.addOnDestinationChangedListener { _: NavController, navDestination: NavDestination, bundle: Bundle? ->
|
||||||
// Intercept search and add a query
|
// Intercept search and add a query
|
||||||
|
updateNavBar(navDestination)
|
||||||
if (navDestination.matchDestination(R.id.navigation_search) && !nextSearchQuery.isNullOrBlank()) {
|
if (navDestination.matchDestination(R.id.navigation_search) && !nextSearchQuery.isNullOrBlank()) {
|
||||||
bundle?.apply {
|
bundle?.apply {
|
||||||
this.putString(SearchFragment.SEARCH_QUERY, nextSearchQuery)
|
this.putString(SearchFragment.SEARCH_QUERY, nextSearchQuery)
|
||||||
|
@ -995,29 +1251,47 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
.setPopExitAnim(R.anim.nav_pop_exit)
|
.setPopExitAnim(R.anim.nav_pop_exit)
|
||||||
.setPopUpTo(navController.graph.startDestination, false)
|
.setPopUpTo(navController.graph.startDestination, false)
|
||||||
.build()*/
|
.build()*/
|
||||||
nav_view?.setupWithNavController(navController)
|
|
||||||
val nav_rail = findViewById<NavigationRailView?>(R.id.nav_rail_view)
|
|
||||||
nav_rail?.setupWithNavController(navController)
|
|
||||||
if (isTvSettings()) {
|
|
||||||
nav_rail?.background?.alpha = 200
|
|
||||||
} else {
|
|
||||||
nav_rail?.background?.alpha = 255
|
|
||||||
|
|
||||||
|
val rippleColor = ColorStateList.valueOf(getResourceColor(R.attr.colorPrimary, 0.1f))
|
||||||
|
|
||||||
|
binding?.navView?.apply {
|
||||||
|
itemRippleColor = rippleColor
|
||||||
|
itemActiveIndicatorColor = rippleColor
|
||||||
|
setupWithNavController(navController)
|
||||||
|
setOnItemSelectedListener { item ->
|
||||||
|
onNavDestinationSelected(
|
||||||
|
item,
|
||||||
|
navController
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nav_rail?.setOnItemSelectedListener { item ->
|
|
||||||
onNavDestinationSelected(
|
binding?.navRailView?.apply {
|
||||||
item,
|
itemRippleColor = rippleColor
|
||||||
navController
|
itemActiveIndicatorColor = rippleColor
|
||||||
)
|
setupWithNavController(navController)
|
||||||
}
|
if (isTvSettings()) {
|
||||||
nav_view?.setOnItemSelectedListener { item ->
|
background?.alpha = 200
|
||||||
onNavDestinationSelected(
|
} else {
|
||||||
item,
|
background?.alpha = 255
|
||||||
navController
|
}
|
||||||
)
|
|
||||||
}
|
setOnItemSelectedListener { item ->
|
||||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
onNavDestinationSelected(
|
||||||
updateNavBar(destination)
|
item,
|
||||||
|
navController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun noFocus(view: View) {
|
||||||
|
view.tag = view.context.getString(R.string.tv_no_focus_tag)
|
||||||
|
(view as? ViewGroup)?.let {
|
||||||
|
for (child in it.children) {
|
||||||
|
noFocus(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
noFocus(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadCache()
|
loadCache()
|
||||||
|
@ -1040,17 +1314,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
true
|
true
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
val rippleColor = ColorStateList.valueOf(getResourceColor(R.attr.colorPrimary, 0.1f))
|
|
||||||
nav_view?.itemRippleColor = rippleColor
|
|
||||||
nav_rail?.itemRippleColor = rippleColor
|
|
||||||
nav_rail?.itemActiveIndicatorColor = rippleColor
|
|
||||||
nav_view?.itemActiveIndicatorColor = rippleColor
|
|
||||||
|
|
||||||
if (!checkWrite()) {
|
if (!checkWrite()) {
|
||||||
requestRW()
|
requestRW()
|
||||||
if (checkWrite()) return
|
if (checkWrite()) return
|
||||||
}
|
}
|
||||||
CastButtonFactory.setUpMediaRouteButton(this, media_route_button)
|
//CastButtonFactory.setUpMediaRouteButton(this, media_route_button)
|
||||||
|
|
||||||
// THIS IS CURRENTLY REMOVED BECAUSE HIGHER VERS OF ANDROID NEEDS A NOTIFICATION
|
// THIS IS CURRENTLY REMOVED BECAUSE HIGHER VERS OF ANDROID NEEDS A NOTIFICATION
|
||||||
//if (!VideoDownloadManager.isMyServiceRunning(this, VideoDownloadKeepAliveService::class.java)) {
|
//if (!VideoDownloadManager.isMyServiceRunning(this, VideoDownloadKeepAliveService::class.java)) {
|
||||||
|
@ -1117,14 +1386,15 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
var providersAndroidManifestString = "Current androidmanifest should be:\n"
|
var providersAndroidManifestString = "Current androidmanifest should be:\n"
|
||||||
for (api in allProviders) {
|
synchronized(allProviders) {
|
||||||
providersAndroidManifestString += "<data android:scheme=\"https\" android:host=\"${
|
for (api in allProviders) {
|
||||||
api.mainUrl.removePrefix(
|
providersAndroidManifestString += "<data android:scheme=\"https\" android:host=\"${
|
||||||
"https://"
|
api.mainUrl.removePrefix(
|
||||||
)
|
"https://"
|
||||||
}\" android:pathPrefix=\"/\"/>\n"
|
)
|
||||||
|
}\" android:pathPrefix=\"/\"/>\n"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println(providersAndroidManifestString)
|
println(providersAndroidManifestString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.lagradost.cloudstream3.extractors
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.lagradost.cloudstream3.app
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.getAndUnpack
|
||||||
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
|
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||||
|
|
||||||
|
open class StreamoUpload : ExtractorApi() {
|
||||||
|
override val name = "StreamoUpload"
|
||||||
|
override val mainUrl = "https://streamoupload.xyz"
|
||||||
|
override val requiresReferer = true
|
||||||
|
|
||||||
|
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||||
|
val sources = mutableListOf<ExtractorLink>()
|
||||||
|
val response = app.get(url, referer = referer)
|
||||||
|
val scriptElements = response.document.select("script").map { script ->
|
||||||
|
if (script.data().contains("eval(function(p,a,c,k,e,d)")) {
|
||||||
|
val data = getAndUnpack(script.data())
|
||||||
|
.substringAfter("sources:[")
|
||||||
|
.substringBefore("],")
|
||||||
|
.replace("file", "\"file\"")
|
||||||
|
.trim()
|
||||||
|
tryParseJson<File>(data)?.let {
|
||||||
|
M3u8Helper.generateM3u8(
|
||||||
|
name,
|
||||||
|
it.file,
|
||||||
|
"$mainUrl/",
|
||||||
|
).forEach { m3uData -> sources.add(m3uData) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sources
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class File(
|
||||||
|
@JsonProperty("file") val file: String,
|
||||||
|
)
|
||||||
|
}
|
|
@ -21,10 +21,11 @@ class CrossTmdbProvider : TmdbProvider() {
|
||||||
return Regex("""[^a-zA-Z0-9-]""").replace(name, "")
|
return Regex("""[^a-zA-Z0-9-]""").replace(name, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private val validApis by lazy {
|
private val validApis
|
||||||
apis.filter { it.lang == this.lang && it::class.java != this::class.java }
|
get() =
|
||||||
//.distinctBy { it.uniqueId }
|
synchronized(apis) { apis.filter { it.lang == this.lang && it::class.java != this::class.java } }
|
||||||
}
|
//.distinctBy { it.uniqueId }
|
||||||
|
|
||||||
|
|
||||||
data class CrossMetaData(
|
data class CrossMetaData(
|
||||||
@JsonProperty("isSuccess") val isSuccess: Boolean,
|
@JsonProperty("isSuccess") val isSuccess: Boolean,
|
||||||
|
@ -60,7 +61,8 @@ class CrossTmdbProvider : TmdbProvider() {
|
||||||
|
|
||||||
override suspend fun load(url: String): LoadResponse? {
|
override suspend fun load(url: String): LoadResponse? {
|
||||||
val base = super.load(url)?.apply {
|
val base = super.load(url)?.apply {
|
||||||
this.recommendations = this.recommendations?.filterIsInstance<MovieSearchResponse>() // TODO REMOVE
|
this.recommendations =
|
||||||
|
this.recommendations?.filterIsInstance<MovieSearchResponse>() // TODO REMOVE
|
||||||
val matchName = filterName(this.name)
|
val matchName = filterName(this.name)
|
||||||
when (this) {
|
when (this) {
|
||||||
is MovieLoadResponse -> {
|
is MovieLoadResponse -> {
|
||||||
|
@ -98,6 +100,7 @@ class CrossTmdbProvider : TmdbProvider() {
|
||||||
this.dataUrl =
|
this.dataUrl =
|
||||||
CrossMetaData(true, data.map { it.apiName to it.dataUrl }).toJson()
|
CrossMetaData(true, data.map { it.apiName to it.dataUrl }).toJson()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
throw ErrorLoadingException("Nothing besides movies are implemented for this provider")
|
throw ErrorLoadingException("Nothing besides movies are implemented for this provider")
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,13 +25,16 @@ class MultiAnimeProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val validApis by lazy {
|
private val validApis
|
||||||
APIHolder.apis.filter {
|
get() =
|
||||||
it.lang == this.lang && it::class.java != this::class.java && it.supportedTypes.contains(
|
synchronized(APIHolder.apis) {
|
||||||
TvType.Anime
|
APIHolder.apis.filter {
|
||||||
)
|
it.lang == this.lang && it::class.java != this::class.java && it.supportedTypes.contains(
|
||||||
}
|
TvType.Anime
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun filterName(name: String): String {
|
private fun filterName(name: String): String {
|
||||||
return Regex("""[^a-zA-Z0-9-]""").replace(name, "")
|
return Regex("""[^a-zA-Z0-9-]""").replace(name, "")
|
||||||
|
|
|
@ -57,32 +57,6 @@ fun <T> LifecycleOwner.observeNullable(liveData: LiveData<T>, action: (t: T) ->
|
||||||
liveData.observe(this) { action(it) }
|
liveData.observe(this) { action(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : Any> some(value: T?): Some<T> {
|
|
||||||
return if (value == null) {
|
|
||||||
Some.None
|
|
||||||
} else {
|
|
||||||
Some.Success(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Some<out T> {
|
|
||||||
data class Success<out T>(val value: T) : Some<T>()
|
|
||||||
object None : Some<Nothing>()
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return when (this) {
|
|
||||||
is None -> "None"
|
|
||||||
is Success -> "Some(${value.toString()})"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class ResourceSome<out T> {
|
|
||||||
data class Success<out T>(val value: T) : ResourceSome<T>()
|
|
||||||
object None : ResourceSome<Nothing>()
|
|
||||||
data class Loading(val data: Any? = null) : ResourceSome<Nothing>()
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Resource<out T> {
|
sealed class Resource<out T> {
|
||||||
data class Success<out T>(val value: T) : Resource<T>()
|
data class Success<out T>(val value: T) : Resource<T>()
|
||||||
data class Failure(
|
data class Failure(
|
||||||
|
@ -155,6 +129,70 @@ fun CoroutineScope.launchSafe(
|
||||||
return this.launch(context, start, obj)
|
return this.launch(context, start, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun<T> throwAbleToResource(
|
||||||
|
throwable: Throwable
|
||||||
|
): Resource<T> {
|
||||||
|
return when (throwable) {
|
||||||
|
is NullPointerException -> {
|
||||||
|
for (line in throwable.stackTrace) {
|
||||||
|
if (line?.fileName?.endsWith("provider.kt", ignoreCase = true) == true) {
|
||||||
|
return Resource.Failure(
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"NullPointerException at ${line.fileName} ${line.lineNumber}\nSite might have updated or added Cloudflare/DDOS protection"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
safeFail(throwable)
|
||||||
|
}
|
||||||
|
is SocketTimeoutException, is InterruptedIOException -> {
|
||||||
|
Resource.Failure(
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"Connection Timeout\nPlease try again later."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is HttpException -> {
|
||||||
|
Resource.Failure(
|
||||||
|
false,
|
||||||
|
throwable.statusCode,
|
||||||
|
null,
|
||||||
|
throwable.message ?: "HttpException"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is UnknownHostException -> {
|
||||||
|
Resource.Failure(true, null, null, "Cannot connect to server, try again later.\n${throwable.message}")
|
||||||
|
}
|
||||||
|
is ErrorLoadingException -> {
|
||||||
|
Resource.Failure(
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
throwable.message ?: "Error loading, try again later."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is NotImplementedError -> {
|
||||||
|
Resource.Failure(false, null, null, "This operation is not implemented.")
|
||||||
|
}
|
||||||
|
is SSLHandshakeException -> {
|
||||||
|
Resource.Failure(
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
(throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is CancellationException -> {
|
||||||
|
throwable.cause?.let {
|
||||||
|
throwAbleToResource(it)
|
||||||
|
} ?: safeFail(throwable)
|
||||||
|
}
|
||||||
|
else -> safeFail(throwable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun <T> safeApiCall(
|
suspend fun <T> safeApiCall(
|
||||||
apiCall: suspend () -> T,
|
apiCall: suspend () -> T,
|
||||||
): Resource<T> {
|
): Resource<T> {
|
||||||
|
@ -163,60 +201,7 @@ suspend fun <T> safeApiCall(
|
||||||
Resource.Success(apiCall.invoke())
|
Resource.Success(apiCall.invoke())
|
||||||
} catch (throwable: Throwable) {
|
} catch (throwable: Throwable) {
|
||||||
logError(throwable)
|
logError(throwable)
|
||||||
when (throwable) {
|
throwAbleToResource(throwable)
|
||||||
is NullPointerException -> {
|
|
||||||
for (line in throwable.stackTrace) {
|
|
||||||
if (line?.fileName?.endsWith("provider.kt", ignoreCase = true) == true) {
|
|
||||||
return@withContext Resource.Failure(
|
|
||||||
false,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
"NullPointerException at ${line.fileName} ${line.lineNumber}\nSite might have updated or added Cloudflare/DDOS protection"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
safeFail(throwable)
|
|
||||||
}
|
|
||||||
is SocketTimeoutException, is InterruptedIOException -> {
|
|
||||||
Resource.Failure(
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
"Connection Timeout\nPlease try again later."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is HttpException -> {
|
|
||||||
Resource.Failure(
|
|
||||||
false,
|
|
||||||
throwable.statusCode,
|
|
||||||
null,
|
|
||||||
throwable.message ?: "HttpException"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is UnknownHostException -> {
|
|
||||||
Resource.Failure(true, null, null, "Cannot connect to server, try again later.")
|
|
||||||
}
|
|
||||||
is ErrorLoadingException -> {
|
|
||||||
Resource.Failure(
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
throwable.message ?: "Error loading, try again later."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
is NotImplementedError -> {
|
|
||||||
Resource.Failure(false, null, null, "This operation is not implemented.")
|
|
||||||
}
|
|
||||||
is SSLHandshakeException -> {
|
|
||||||
Resource.Failure(
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
(throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> safeFail(throwable)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -36,7 +36,9 @@ abstract class Plugin {
|
||||||
Log.i(PLUGIN_TAG, "Adding ${element.name} (${element.mainUrl}) MainAPI")
|
Log.i(PLUGIN_TAG, "Adding ${element.name} (${element.mainUrl}) MainAPI")
|
||||||
element.sourcePlugin = this.__filename
|
element.sourcePlugin = this.__filename
|
||||||
// Race condition causing which would case duplicates if not for distinctBy
|
// Race condition causing which would case duplicates if not for distinctBy
|
||||||
APIHolder.allProviders.add(element)
|
synchronized(APIHolder.allProviders) {
|
||||||
|
APIHolder.allProviders.add(element)
|
||||||
|
}
|
||||||
APIHolder.addPluginMapping(element)
|
APIHolder.addPluginMapping(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,10 +53,14 @@ abstract class Plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Manifest {
|
class Manifest {
|
||||||
@JsonProperty("name") var name: String? = null
|
@JsonProperty("name")
|
||||||
@JsonProperty("pluginClassName") var pluginClassName: String? = null
|
var name: String? = null
|
||||||
@JsonProperty("version") var version: Int? = null
|
@JsonProperty("pluginClassName")
|
||||||
@JsonProperty("requiresResources") var requiresResources: Boolean = false
|
var pluginClassName: String? = null
|
||||||
|
@JsonProperty("version")
|
||||||
|
var version: Int? = null
|
||||||
|
@JsonProperty("requiresResources")
|
||||||
|
var requiresResources: Boolean = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -163,7 +163,8 @@ object PluginManager {
|
||||||
private val classLoaders: MutableMap<PathClassLoader, Plugin> =
|
private val classLoaders: MutableMap<PathClassLoader, Plugin> =
|
||||||
HashMap<PathClassLoader, Plugin>()
|
HashMap<PathClassLoader, Plugin>()
|
||||||
|
|
||||||
private var loadedLocalPlugins = false
|
var loadedLocalPlugins = false
|
||||||
|
private set
|
||||||
private val gson = Gson()
|
private val gson = Gson()
|
||||||
|
|
||||||
private suspend fun maybeLoadPlugin(context: Context, file: File) {
|
private suspend fun maybeLoadPlugin(context: Context, file: File) {
|
||||||
|
@ -531,10 +532,14 @@ object PluginManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove all registered apis
|
// remove all registered apis
|
||||||
APIHolder.apis.filter { api -> api.sourcePlugin == plugin.__filename }.forEach {
|
synchronized(APIHolder.apis) {
|
||||||
removePluginMapping(it)
|
APIHolder.apis.filter { api -> api.sourcePlugin == plugin.__filename }.forEach {
|
||||||
|
removePluginMapping(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
synchronized(APIHolder.allProviders) {
|
||||||
|
APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename }
|
||||||
}
|
}
|
||||||
APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename }
|
|
||||||
extractorApis.removeIf { provider: ExtractorApi -> provider.sourcePlugin == plugin.__filename }
|
extractorApis.removeIf { provider: ExtractorApi -> provider.sourcePlugin == plugin.__filename }
|
||||||
|
|
||||||
classLoaders.values.removeIf { v -> v == plugin }
|
classLoaders.values.removeIf { v -> v == plugin }
|
||||||
|
|
|
@ -24,7 +24,7 @@ class GrdLayoutManager(val context: Context, _spanCount: Int) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRequestChildFocus(
|
/*override fun onRequestChildFocus(
|
||||||
parent: RecyclerView,
|
parent: RecyclerView,
|
||||||
state: RecyclerView.State,
|
state: RecyclerView.State,
|
||||||
child: View,
|
child: View,
|
||||||
|
@ -32,13 +32,17 @@ class GrdLayoutManager(val context: Context, _spanCount: Int) :
|
||||||
): Boolean {
|
): Boolean {
|
||||||
// android.widget.FrameLayout$LayoutParams cannot be cast to androidx.recyclerview.widget.RecyclerView$LayoutParams
|
// android.widget.FrameLayout$LayoutParams cannot be cast to androidx.recyclerview.widget.RecyclerView$LayoutParams
|
||||||
return try {
|
return try {
|
||||||
val pos = maxOf(0, getPosition(focused!!) - 2)
|
if(focused != null) {
|
||||||
parent.scrollToPosition(pos)
|
// val pos = maxOf(0, getPosition(focused) - 2) // IDK WHY
|
||||||
|
val pos = getPosition(focused)
|
||||||
|
if(pos >= 0) parent.scrollToPosition(pos)
|
||||||
|
}
|
||||||
|
|
||||||
super.onRequestChildFocus(parent, state, child, focused)
|
super.onRequestChildFocus(parent, state, child, focused)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// Allows moving right and left with focus https://gist.github.com/vganin/8930b41f55820ec49e4d
|
// Allows moving right and left with focus https://gist.github.com/vganin/8930b41f55820ec49e4d
|
||||||
override fun onInterceptFocusSearch(focused: View, direction: Int): View? {
|
override fun onInterceptFocusSearch(focused: View, direction: Int): View? {
|
||||||
|
@ -65,8 +69,17 @@ class GrdLayoutManager(val context: Context, _spanCount: Int) :
|
||||||
val spanCount = this.spanCount
|
val spanCount = this.spanCount
|
||||||
val orientation = this.orientation
|
val orientation = this.orientation
|
||||||
|
|
||||||
|
// fixes arabic by inverting left and right layout focus
|
||||||
|
val correctDirection = if(this.isLayoutRTL) {
|
||||||
|
when(direction) {
|
||||||
|
View.FOCUS_RIGHT -> View.FOCUS_LEFT
|
||||||
|
View.FOCUS_LEFT -> View.FOCUS_RIGHT
|
||||||
|
else -> direction
|
||||||
|
}
|
||||||
|
} else direction
|
||||||
|
|
||||||
if (orientation == VERTICAL) {
|
if (orientation == VERTICAL) {
|
||||||
when (direction) {
|
when (correctDirection) {
|
||||||
View.FOCUS_DOWN -> {
|
View.FOCUS_DOWN -> {
|
||||||
return spanCount
|
return spanCount
|
||||||
}
|
}
|
||||||
|
@ -81,7 +94,7 @@ class GrdLayoutManager(val context: Context, _spanCount: Int) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (orientation == HORIZONTAL) {
|
} else if (orientation == HORIZONTAL) {
|
||||||
when (direction) {
|
when (correctDirection) {
|
||||||
View.FOCUS_DOWN -> {
|
View.FOCUS_DOWN -> {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,16 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.appcompat.widget.AppCompatImageView
|
import androidx.appcompat.widget.AppCompatImageView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import kotlinx.android.synthetic.main.activity_easter_egg_monke.*
|
import com.lagradost.cloudstream3.databinding.ActivityEasterEggMonkeBinding
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class EasterEggMonke : AppCompatActivity() {
|
class EasterEggMonke : AppCompatActivity() {
|
||||||
|
|
||||||
|
lateinit var binding : ActivityEasterEggMonkeBinding
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_easter_egg_monke)
|
|
||||||
|
binding = ActivityEasterEggMonkeBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
val handler = Handler(mainLooper)
|
val handler = Handler(mainLooper)
|
||||||
lateinit var runnable: Runnable
|
lateinit var runnable: Runnable
|
||||||
|
@ -32,15 +34,14 @@ class EasterEggMonke : AppCompatActivity() {
|
||||||
handler.postDelayed(runnable, 300)
|
handler.postDelayed(runnable, 300)
|
||||||
}
|
}
|
||||||
handler.postDelayed(runnable, 1000)
|
handler.postDelayed(runnable, 1000)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shower() {
|
private fun shower() {
|
||||||
|
|
||||||
val containerW = frame.width
|
val containerW = binding.frame.width
|
||||||
val containerH = frame.height
|
val containerH = binding.frame.height
|
||||||
var starW: Float = monke.width.toFloat()
|
var starW: Float = binding.monke.width.toFloat()
|
||||||
var starH: Float = monke.height.toFloat()
|
var starH: Float = binding.monke.height.toFloat()
|
||||||
|
|
||||||
val newStar = AppCompatImageView(this)
|
val newStar = AppCompatImageView(this)
|
||||||
val idx = (monkeys.size * Math.random()).toInt()
|
val idx = (monkeys.size * Math.random()).toInt()
|
||||||
|
@ -48,7 +49,7 @@ class EasterEggMonke : AppCompatActivity() {
|
||||||
newStar.isVisible = true
|
newStar.isVisible = true
|
||||||
newStar.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
|
newStar.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||||
FrameLayout.LayoutParams.WRAP_CONTENT)
|
FrameLayout.LayoutParams.WRAP_CONTENT)
|
||||||
frame.addView(newStar)
|
binding.frame.addView(newStar)
|
||||||
|
|
||||||
newStar.scaleX = Math.random().toFloat() * 1.5f + newStar.scaleX
|
newStar.scaleX = Math.random().toFloat() * 1.5f + newStar.scaleX
|
||||||
newStar.scaleY = newStar.scaleX
|
newStar.scaleY = newStar.scaleX
|
||||||
|
@ -70,7 +71,7 @@ class EasterEggMonke : AppCompatActivity() {
|
||||||
|
|
||||||
set.addListener(object : AnimatorListenerAdapter() {
|
set.addListener(object : AnimatorListenerAdapter() {
|
||||||
override fun onAnimationEnd(animation: Animator) {
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
frame.removeView(newStar)
|
binding.frame.removeView(newStar)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -12,20 +12,23 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import com.lagradost.cloudstream3.MainActivity
|
import com.lagradost.cloudstream3.MainActivity
|
||||||
import com.lagradost.cloudstream3.R
|
|
||||||
import com.lagradost.cloudstream3.USER_AGENT
|
import com.lagradost.cloudstream3.USER_AGENT
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentWebviewBinding
|
||||||
import com.lagradost.cloudstream3.network.WebViewResolver
|
import com.lagradost.cloudstream3.network.WebViewResolver
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
|
import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
|
||||||
import kotlinx.android.synthetic.main.fragment_webview.*
|
|
||||||
|
|
||||||
class WebviewFragment : Fragment() {
|
class WebviewFragment : Fragment() {
|
||||||
|
|
||||||
|
var binding: FragmentWebviewBinding? = null
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
val url = arguments?.getString(WEBVIEW_URL) ?: "".also {
|
val url = arguments?.getString(WEBVIEW_URL) ?: "".also {
|
||||||
findNavController().popBackStack()
|
findNavController().popBackStack()
|
||||||
}
|
}
|
||||||
|
|
||||||
web_view.webViewClient = object : WebViewClient() {
|
binding?.webView?.webViewClient = object : WebViewClient() {
|
||||||
override fun shouldOverrideUrlLoading(
|
override fun shouldOverrideUrlLoading(
|
||||||
view: WebView?,
|
view: WebView?,
|
||||||
request: WebResourceRequest?
|
request: WebResourceRequest?
|
||||||
|
@ -40,24 +43,28 @@ class WebviewFragment : Fragment() {
|
||||||
return super.shouldOverrideUrlLoading(view, request)
|
return super.shouldOverrideUrlLoading(view, request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding?.webView?.apply {
|
||||||
|
WebViewResolver.webViewUserAgent = settings.userAgentString
|
||||||
|
|
||||||
WebViewResolver.webViewUserAgent = web_view.settings.userAgentString
|
addJavascriptInterface(RepoApi(activity), "RepoApi")
|
||||||
|
settings.javaScriptEnabled = true
|
||||||
web_view.addJavascriptInterface(RepoApi(activity), "RepoApi")
|
settings.userAgentString = USER_AGENT
|
||||||
web_view.settings.javaScriptEnabled = true
|
settings.domStorageEnabled = true
|
||||||
web_view.settings.userAgentString = USER_AGENT
|
|
||||||
web_view.settings.domStorageEnabled = true
|
|
||||||
// WebView.setWebContentsDebuggingEnabled(true)
|
// WebView.setWebContentsDebuggingEnabled(true)
|
||||||
|
|
||||||
web_view.loadUrl(url)
|
loadUrl(url)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
|
val localBinding = FragmentWebviewBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
// Inflate the layout for this fragment
|
// Inflate the layout for this fragment
|
||||||
return inflater.inflate(R.layout.fragment_webview, container, false)
|
return localBinding.root//inflater.inflate(R.layout.fragment_webview, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -70,7 +77,7 @@ class WebviewFragment : Fragment() {
|
||||||
|
|
||||||
private class RepoApi(val activity: FragmentActivity?) {
|
private class RepoApi(val activity: FragmentActivity?) {
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
fun installRepo(repoUrl: String) {
|
fun installRepo(repoUrl: String) {
|
||||||
activity?.loadRepository(repoUrl)
|
activity?.loadRepository(repoUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.content.DialogInterface
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||||
|
import com.lagradost.cloudstream3.CommonActivity.activity
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
@ -19,7 +20,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
|
|
||||||
object DownloadButtonSetup {
|
object DownloadButtonSetup {
|
||||||
fun handleDownloadClick(activity: Activity?, click: DownloadClickEvent) {
|
fun handleDownloadClick(click: DownloadClickEvent) {
|
||||||
val id = click.data.id
|
val id = click.data.id
|
||||||
if (click.data !is VideoDownloadHelper.DownloadEpisodeCached) return
|
if (click.data !is VideoDownloadHelper.DownloadEpisodeCached) return
|
||||||
when (click.action) {
|
when (click.action) {
|
||||||
|
@ -89,9 +90,9 @@ object DownloadButtonSetup {
|
||||||
)?.fileLength
|
)?.fileLength
|
||||||
?: 0
|
?: 0
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
showToast(act, R.string.delete, Toast.LENGTH_LONG)
|
showToast(R.string.delete, Toast.LENGTH_LONG)
|
||||||
} else {
|
} else {
|
||||||
showToast(act, R.string.download, Toast.LENGTH_LONG)
|
showToast(R.string.download, Toast.LENGTH_LONG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
package com.lagradost.cloudstream3.ui.download
|
|
||||||
|
|
||||||
interface DownloadButtonViewHolder {
|
|
||||||
var downloadButton : EasyDownloadButton
|
|
||||||
fun reattachDownloadButton()
|
|
||||||
}
|
|
|
@ -3,18 +3,12 @@ package com.lagradost.cloudstream3.ui.download
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.cardview.widget.CardView
|
|
||||||
import androidx.core.widget.ContentLoadingProgressBar
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.databinding.DownloadChildEpisodeBinding
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import kotlinx.android.synthetic.main.download_child_episode.view.*
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
const val DOWNLOAD_ACTION_PLAY_FILE = 0
|
const val DOWNLOAD_ACTION_PLAY_FILE = 0
|
||||||
const val DOWNLOAD_ACTION_DELETE_FILE = 1
|
const val DOWNLOAD_ACTION_DELETE_FILE = 1
|
||||||
|
@ -36,39 +30,9 @@ class DownloadChildAdapter(
|
||||||
private val clickCallback: (DownloadClickEvent) -> Unit,
|
private val clickCallback: (DownloadClickEvent) -> Unit,
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
private val mBoundViewHolders: HashSet<DownloadButtonViewHolder> = HashSet()
|
|
||||||
private fun getAllBoundViewHolders(): Set<DownloadButtonViewHolder?>? {
|
|
||||||
return Collections.unmodifiableSet(mBoundViewHolders)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun killAdapter() {
|
|
||||||
getAllBoundViewHolders()?.forEach { view ->
|
|
||||||
view?.downloadButton?.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
|
||||||
if (holder is DownloadButtonViewHolder) {
|
|
||||||
holder.downloadButton.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
|
||||||
if (holder is DownloadButtonViewHolder) {
|
|
||||||
holder.downloadButton.dispose()
|
|
||||||
mBoundViewHolders.remove(holder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
|
|
||||||
if (holder is DownloadButtonViewHolder) {
|
|
||||||
holder.reattachDownloadButton()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return DownloadChildViewHolder(
|
return DownloadChildViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(R.layout.download_child_episode, parent, false),
|
DownloadChildEpisodeBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||||
clickCallback
|
clickCallback
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -77,7 +41,6 @@ class DownloadChildAdapter(
|
||||||
when (holder) {
|
when (holder) {
|
||||||
is DownloadChildViewHolder -> {
|
is DownloadChildViewHolder -> {
|
||||||
holder.bind(cardList[position])
|
holder.bind(cardList[position])
|
||||||
mBoundViewHolders.add(holder)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,66 +51,44 @@ class DownloadChildAdapter(
|
||||||
|
|
||||||
class DownloadChildViewHolder
|
class DownloadChildViewHolder
|
||||||
constructor(
|
constructor(
|
||||||
itemView: View,
|
val binding: DownloadChildEpisodeBinding,
|
||||||
private val clickCallback: (DownloadClickEvent) -> Unit,
|
private val clickCallback: (DownloadClickEvent) -> Unit,
|
||||||
) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
override var downloadButton = EasyDownloadButton()
|
|
||||||
|
|
||||||
private val title: TextView = itemView.download_child_episode_text
|
/*private val title: TextView = itemView.download_child_episode_text
|
||||||
private val extraInfo: TextView = itemView.download_child_episode_text_extra
|
private val extraInfo: TextView = itemView.download_child_episode_text_extra
|
||||||
private val holder: CardView = itemView.download_child_episode_holder
|
private val holder: CardView = itemView.download_child_episode_holder
|
||||||
private val progressBar: ContentLoadingProgressBar = itemView.download_child_episode_progress
|
private val progressBar: ContentLoadingProgressBar = itemView.download_child_episode_progress
|
||||||
private val progressBarDownload: ContentLoadingProgressBar = itemView.download_child_episode_progress_downloaded
|
private val progressBarDownload: ContentLoadingProgressBar = itemView.download_child_episode_progress_downloaded
|
||||||
private val downloadImage: ImageView = itemView.download_child_episode_download
|
private val downloadImage: ImageView = itemView.download_child_episode_download*/
|
||||||
|
|
||||||
private var localCard: VisualDownloadChildCached? = null
|
|
||||||
|
|
||||||
fun bind(card: VisualDownloadChildCached) {
|
fun bind(card: VisualDownloadChildCached) {
|
||||||
localCard = card
|
|
||||||
val d = card.data
|
val d = card.data
|
||||||
|
|
||||||
val posDur = getViewPos(d.id)
|
val posDur = getViewPos(d.id)
|
||||||
if (posDur != null) {
|
binding.downloadChildEpisodeProgress.apply {
|
||||||
val visualPos = posDur.fixVisual()
|
if (posDur != null) {
|
||||||
progressBar.max = (visualPos.duration / 1000).toInt()
|
val visualPos = posDur.fixVisual()
|
||||||
progressBar.progress = (visualPos.position / 1000).toInt()
|
max = (visualPos.duration / 1000).toInt()
|
||||||
progressBar.visibility = View.VISIBLE
|
progress = (visualPos.position / 1000).toInt()
|
||||||
} else {
|
visibility = View.VISIBLE
|
||||||
progressBar.visibility = View.GONE
|
} else {
|
||||||
|
visibility = View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
title.text = title.context.getNameFull(d.name, d.episode, d.season)
|
binding.downloadButton.setDefaultClickListener(card.data, binding.downloadChildEpisodeTextExtra, clickCallback)
|
||||||
title.isSelected = true // is needed for text repeating
|
|
||||||
|
|
||||||
downloadButton.setUpButton(
|
binding.downloadChildEpisodeText.apply {
|
||||||
card.currentBytes,
|
text = context.getNameFull(d.name, d.episode, d.season)
|
||||||
card.totalBytes,
|
isSelected = true // is needed for text repeating
|
||||||
progressBarDownload,
|
}
|
||||||
downloadImage,
|
|
||||||
extraInfo,
|
|
||||||
card.data,
|
|
||||||
clickCallback
|
|
||||||
)
|
|
||||||
|
|
||||||
holder.setOnClickListener {
|
|
||||||
|
binding.downloadChildEpisodeHolder.setOnClickListener {
|
||||||
clickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
|
clickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, d))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reattachDownloadButton() {
|
|
||||||
downloadButton.dispose()
|
|
||||||
val card = localCard
|
|
||||||
if (card != null) {
|
|
||||||
downloadButton.setUpButton(
|
|
||||||
card.currentBytes,
|
|
||||||
card.totalBytes,
|
|
||||||
progressBarDownload,
|
|
||||||
downloadImage,
|
|
||||||
extraInfo,
|
|
||||||
card.data,
|
|
||||||
clickCallback
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||||
|
@ -15,13 +16,12 @@ import com.lagradost.cloudstream3.utils.DataStore.getKeys
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
import kotlinx.android.synthetic.main.fragment_child_downloads.*
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class DownloadChildFragment : Fragment() {
|
class DownloadChildFragment : Fragment() {
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance(headerName: String, folder: String) : Bundle {
|
fun newInstance(headerName: String, folder: String): Bundle {
|
||||||
return Bundle().apply {
|
return Bundle().apply {
|
||||||
putString("folder", folder)
|
putString("folder", folder)
|
||||||
putString("name", headerName)
|
putString("name", headerName)
|
||||||
|
@ -30,13 +30,20 @@ class DownloadChildFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
(download_child_list?.adapter as DownloadChildAdapter?)?.killAdapter()
|
|
||||||
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent -= it }
|
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent -= it }
|
||||||
|
binding = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
var binding: FragmentChildDownloadsBinding? = null
|
||||||
return inflater.inflate(R.layout.fragment_child_downloads, container, false)
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
val localBinding = FragmentChildDownloadsBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root//inflater.inflate(R.layout.fragment_child_downloads, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateList(folder: String) = main {
|
private fun updateList(folder: String) = main {
|
||||||
|
@ -50,14 +57,15 @@ class DownloadChildFragment : Fragment() {
|
||||||
?: return@mapNotNull null
|
?: return@mapNotNull null
|
||||||
VisualDownloadChildCached(info.fileLength, info.totalBytes, it)
|
VisualDownloadChildCached(info.fileLength, info.totalBytes, it)
|
||||||
}
|
}
|
||||||
}.sortedBy { it.data.episode + (it.data.season?: 0)*100000 }
|
}.sortedBy { it.data.episode + (it.data.season ?: 0) * 100000 }
|
||||||
if (eps.isEmpty()) {
|
if (eps.isEmpty()) {
|
||||||
activity?.onBackPressed()
|
activity?.onBackPressed()
|
||||||
return@main
|
return@main
|
||||||
}
|
}
|
||||||
|
|
||||||
(download_child_list?.adapter as DownloadChildAdapter? ?: return@main).cardList = eps
|
(binding?.downloadChildList?.adapter as DownloadChildAdapter? ?: return@main).cardList =
|
||||||
download_child_list?.adapter?.notifyDataSetChanged()
|
eps
|
||||||
|
binding?.downloadChildList?.adapter?.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,23 +80,26 @@ class DownloadChildFragment : Fragment() {
|
||||||
activity?.onBackPressed() // TODO FIX
|
activity?.onBackPressed() // TODO FIX
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
context?.fixPaddingStatusbar(download_child_root)
|
fixPaddingStatusbar(binding?.downloadChildRoot)
|
||||||
|
|
||||||
download_child_toolbar.title = name
|
binding?.downloadChildToolbar?.apply {
|
||||||
download_child_toolbar.setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
|
title = name
|
||||||
download_child_toolbar.setNavigationOnClickListener {
|
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
|
||||||
activity?.onBackPressed()
|
setNavigationOnClickListener {
|
||||||
|
activity?.onBackPressed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
|
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
|
||||||
DownloadChildAdapter(
|
DownloadChildAdapter(
|
||||||
ArrayList(),
|
ArrayList(),
|
||||||
) { click ->
|
) { click ->
|
||||||
handleDownloadClick(activity, click)
|
handleDownloadClick(click)
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadDeleteEventListener = { id: Int ->
|
downloadDeleteEventListener = { id: Int ->
|
||||||
val list = (download_child_list?.adapter as DownloadChildAdapter?)?.cardList
|
val list = (binding?.downloadChildList?.adapter as DownloadChildAdapter?)?.cardList
|
||||||
if (list != null) {
|
if (list != null) {
|
||||||
if (list.any { it.data.id == id }) {
|
if (list.any { it.data.id == id }) {
|
||||||
updateList(folder)
|
updateList(folder)
|
||||||
|
@ -98,8 +109,8 @@ class DownloadChildFragment : Fragment() {
|
||||||
|
|
||||||
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
|
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
|
||||||
|
|
||||||
download_child_list.adapter = adapter
|
binding?.downloadChildList?.adapter = adapter
|
||||||
download_child_list.layoutManager = GridLayoutManager(context, 1)
|
binding?.downloadChildList?.layoutManager = GridLayoutManager(context, 1)
|
||||||
|
|
||||||
updateList(folder)
|
updateList(folder)
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,10 +34,10 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
import kotlinx.android.synthetic.main.fragment_downloads.*
|
|
||||||
import kotlinx.android.synthetic.main.stream_input.*
|
|
||||||
import android.text.format.Formatter.formatShortFileSize
|
import android.text.format.Formatter.formatShortFileSize
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.StreamInputBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.ui.player.BasicLink
|
import com.lagradost.cloudstream3.ui.player.BasicLink
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
|
@ -60,8 +60,8 @@ class DownloadFragment : Fragment() {
|
||||||
|
|
||||||
private fun setList(list: List<VisualDownloadHeaderCached>) {
|
private fun setList(list: List<VisualDownloadHeaderCached>) {
|
||||||
main {
|
main {
|
||||||
(download_list?.adapter as DownloadHeaderAdapter?)?.cardList = list
|
(binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.cardList = list
|
||||||
download_list?.adapter?.notifyDataSetChanged()
|
binding?.downloadList?.adapter?.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,10 +70,12 @@ class DownloadFragment : Fragment() {
|
||||||
VideoDownloadManager.downloadDeleteEvent -= downloadDeleteEventListener!!
|
VideoDownloadManager.downloadDeleteEvent -= downloadDeleteEventListener!!
|
||||||
downloadDeleteEventListener = null
|
downloadDeleteEventListener = null
|
||||||
}
|
}
|
||||||
(download_list?.adapter as DownloadHeaderAdapter?)?.killAdapter()
|
binding = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var binding : FragmentDownloadsBinding? = null
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
@ -82,7 +84,9 @@ class DownloadFragment : Fragment() {
|
||||||
downloadsViewModel =
|
downloadsViewModel =
|
||||||
ViewModelProvider(this)[DownloadViewModel::class.java]
|
ViewModelProvider(this)[DownloadViewModel::class.java]
|
||||||
|
|
||||||
return inflater.inflate(R.layout.fragment_downloads, container, false)
|
val localBinding = FragmentDownloadsBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root//inflater.inflate(R.layout.fragment_downloads, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var downloadDeleteEventListener: ((Int) -> Unit)? = null
|
private var downloadDeleteEventListener: ((Int) -> Unit)? = null
|
||||||
|
@ -92,36 +96,40 @@ class DownloadFragment : Fragment() {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
|
|
||||||
observe(downloadsViewModel.noDownloadsText) {
|
observe(downloadsViewModel.noDownloadsText) {
|
||||||
text_no_downloads.text = it
|
binding?.textNoDownloads?.text = it
|
||||||
}
|
}
|
||||||
observe(downloadsViewModel.headerCards) {
|
observe(downloadsViewModel.headerCards) {
|
||||||
setList(it)
|
setList(it)
|
||||||
download_loading.isVisible = false
|
binding?.downloadLoading?.isVisible = false
|
||||||
}
|
}
|
||||||
observe(downloadsViewModel.availableBytes) {
|
observe(downloadsViewModel.availableBytes) {
|
||||||
download_free_txt?.text =
|
binding?.downloadFreeTxt?.text =
|
||||||
getString(R.string.storage_size_format).format(
|
getString(R.string.storage_size_format).format(
|
||||||
getString(R.string.free_storage),
|
getString(R.string.free_storage),
|
||||||
formatShortFileSize(view.context, it)
|
formatShortFileSize(view.context, it)
|
||||||
)
|
)
|
||||||
download_free?.setLayoutWidth(it)
|
binding?.downloadFree?.setLayoutWidth(it)
|
||||||
}
|
}
|
||||||
observe(downloadsViewModel.usedBytes) {
|
observe(downloadsViewModel.usedBytes) {
|
||||||
download_used_txt?.text =
|
binding?.apply {
|
||||||
getString(R.string.storage_size_format).format(
|
downloadUsedTxt.text =
|
||||||
getString(R.string.used_storage),
|
getString(R.string.storage_size_format).format(
|
||||||
formatShortFileSize(view.context, it)
|
getString(R.string.used_storage),
|
||||||
)
|
formatShortFileSize(view.context, it)
|
||||||
download_used?.setLayoutWidth(it)
|
)
|
||||||
download_storage_appbar?.isVisible = it > 0
|
downloadUsed.setLayoutWidth(it)
|
||||||
|
downloadStorageAppbar.isVisible = it > 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
observe(downloadsViewModel.downloadBytes) {
|
observe(downloadsViewModel.downloadBytes) {
|
||||||
download_app_txt?.text =
|
binding?.apply {
|
||||||
getString(R.string.storage_size_format).format(
|
downloadAppTxt.text =
|
||||||
getString(R.string.app_storage),
|
getString(R.string.storage_size_format).format(
|
||||||
formatShortFileSize(view.context, it)
|
getString(R.string.app_storage),
|
||||||
)
|
formatShortFileSize(view.context, it)
|
||||||
download_app?.setLayoutWidth(it)
|
)
|
||||||
|
downloadApp.setLayoutWidth(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
|
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
|
||||||
|
@ -154,7 +162,7 @@ class DownloadFragment : Fragment() {
|
||||||
},
|
},
|
||||||
{ downloadClickEvent ->
|
{ downloadClickEvent ->
|
||||||
if (downloadClickEvent.data !is VideoDownloadHelper.DownloadEpisodeCached) return@DownloadHeaderAdapter
|
if (downloadClickEvent.data !is VideoDownloadHelper.DownloadEpisodeCached) return@DownloadHeaderAdapter
|
||||||
handleDownloadClick(activity, downloadClickEvent)
|
handleDownloadClick(downloadClickEvent)
|
||||||
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
if (downloadClickEvent.action == DOWNLOAD_ACTION_DELETE_FILE) {
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
downloadsViewModel.updateList(ctx)
|
downloadsViewModel.updateList(ctx)
|
||||||
|
@ -164,7 +172,7 @@ class DownloadFragment : Fragment() {
|
||||||
)
|
)
|
||||||
|
|
||||||
downloadDeleteEventListener = { id ->
|
downloadDeleteEventListener = { id ->
|
||||||
val list = (download_list?.adapter as DownloadHeaderAdapter?)?.cardList
|
val list = (binding?.downloadList?.adapter as DownloadHeaderAdapter?)?.cardList
|
||||||
if (list != null) {
|
if (list != null) {
|
||||||
if (list.any { it.data.id == id }) {
|
if (list.any { it.data.id == id }) {
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
|
@ -177,31 +185,36 @@ class DownloadFragment : Fragment() {
|
||||||
|
|
||||||
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
|
downloadDeleteEventListener?.let { VideoDownloadManager.downloadDeleteEvent += it }
|
||||||
|
|
||||||
download_list?.adapter = adapter
|
binding?.downloadList?.apply {
|
||||||
download_list?.layoutManager = GridLayoutManager(context, 1)
|
this.adapter = adapter
|
||||||
|
layoutManager = GridLayoutManager(context, 1)
|
||||||
|
}
|
||||||
|
|
||||||
// Should be visible in emulator layout
|
// Should be visible in emulator layout
|
||||||
download_stream_button?.isGone = isTrueTvSettings()
|
binding?.downloadStreamButton?.isGone = isTrueTvSettings()
|
||||||
download_stream_button?.setOnClickListener {
|
binding?.downloadStreamButton?.setOnClickListener {
|
||||||
val dialog =
|
val dialog =
|
||||||
Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom)
|
Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom)
|
||||||
dialog.setContentView(R.layout.stream_input)
|
|
||||||
|
val binding = StreamInputBinding.inflate(dialog.layoutInflater)
|
||||||
|
|
||||||
|
dialog.setContentView(binding.root)
|
||||||
|
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
// If user has clicked the switch do not interfere
|
// If user has clicked the switch do not interfere
|
||||||
var preventAutoSwitching = false
|
var preventAutoSwitching = false
|
||||||
dialog.hls_switch?.setOnClickListener {
|
binding.hlsSwitch.setOnClickListener {
|
||||||
preventAutoSwitching = true
|
preventAutoSwitching = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun activateSwitchOnHls(text: String?) {
|
fun activateSwitchOnHls(text: String?) {
|
||||||
dialog.hls_switch?.isChecked = normalSafeApiCall {
|
binding.hlsSwitch.isChecked = normalSafeApiCall {
|
||||||
URI(text).path?.substringAfterLast(".")?.contains("m3u")
|
URI(text).path?.substringAfterLast(".")?.contains("m3u")
|
||||||
} == true
|
} == true
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.stream_referer?.doOnTextChanged { text, _, _, _ ->
|
binding.streamReferer.doOnTextChanged { text, _, _, _ ->
|
||||||
if (!preventAutoSwitching)
|
if (!preventAutoSwitching)
|
||||||
activateSwitchOnHls(text?.toString())
|
activateSwitchOnHls(text?.toString())
|
||||||
}
|
}
|
||||||
|
@ -210,16 +223,16 @@ class DownloadFragment : Fragment() {
|
||||||
0
|
0
|
||||||
)?.text?.toString()?.let { copy ->
|
)?.text?.toString()?.let { copy ->
|
||||||
val fixedText = copy.trim()
|
val fixedText = copy.trim()
|
||||||
dialog.stream_url?.setText(fixedText)
|
binding.streamUrl.setText(fixedText)
|
||||||
activateSwitchOnHls(fixedText)
|
activateSwitchOnHls(fixedText)
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.apply_btt?.setOnClickListener {
|
binding.applyBtt.setOnClickListener {
|
||||||
val url = dialog.stream_url.text?.toString()
|
val url = binding.streamUrl.text?.toString()
|
||||||
if (url.isNullOrEmpty()) {
|
if (url.isNullOrEmpty()) {
|
||||||
showToast(activity, R.string.error_invalid_url, Toast.LENGTH_SHORT)
|
showToast(R.string.error_invalid_url, Toast.LENGTH_SHORT)
|
||||||
} else {
|
} else {
|
||||||
val referer = dialog.stream_referer.text?.toString()
|
val referer = binding.streamReferer.text?.toString()
|
||||||
|
|
||||||
activity?.navigate(
|
activity?.navigate(
|
||||||
R.id.global_to_navigation_player,
|
R.id.global_to_navigation_player,
|
||||||
|
@ -228,7 +241,7 @@ class DownloadFragment : Fragment() {
|
||||||
listOf(BasicLink(url)),
|
listOf(BasicLink(url)),
|
||||||
extract = true,
|
extract = true,
|
||||||
referer = referer,
|
referer = referer,
|
||||||
isM3u8 = dialog.hls_switch?.isChecked
|
isM3u8 = binding.hlsSwitch.isChecked
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -237,22 +250,22 @@ class DownloadFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.cancel_btt?.setOnClickListener {
|
binding.cancelBtt.setOnClickListener {
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
download_list?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
binding?.downloadList?.setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||||
val dy = scrollY - oldScrollY
|
val dy = scrollY - oldScrollY
|
||||||
if (dy > 0) { //check for scroll down
|
if (dy > 0) { //check for scroll down
|
||||||
download_stream_button?.shrink() // hide
|
binding?.downloadStreamButton?.shrink() // hide
|
||||||
} else if (dy < -5) {
|
} else if (dy < -5) {
|
||||||
download_stream_button?.extend() // show
|
binding?.downloadStreamButton?.extend() // show
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
downloadsViewModel.updateList(requireContext())
|
downloadsViewModel.updateList(requireContext())
|
||||||
|
|
||||||
context?.fixPaddingStatusbar(download_root)
|
fixPaddingStatusbar(binding?.downloadRoot)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,16 +5,13 @@ import android.text.format.Formatter.formatShortFileSize
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import androidx.core.view.isVisible
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.cardview.widget.CardView
|
|
||||||
import androidx.core.widget.ContentLoadingProgressBar
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import kotlinx.android.synthetic.main.download_header_episode.view.*
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
data class VisualDownloadHeaderCached(
|
data class VisualDownloadHeaderCached(
|
||||||
|
@ -26,7 +23,10 @@ data class VisualDownloadHeaderCached(
|
||||||
val child: VideoDownloadHelper.DownloadEpisodeCached?,
|
val child: VideoDownloadHelper.DownloadEpisodeCached?,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class DownloadHeaderClickEvent(val action: Int, val data: VideoDownloadHelper.DownloadHeaderCached)
|
data class DownloadHeaderClickEvent(
|
||||||
|
val action: Int,
|
||||||
|
val data: VideoDownloadHelper.DownloadHeaderCached
|
||||||
|
)
|
||||||
|
|
||||||
class DownloadHeaderAdapter(
|
class DownloadHeaderAdapter(
|
||||||
var cardList: List<VisualDownloadHeaderCached>,
|
var cardList: List<VisualDownloadHeaderCached>,
|
||||||
|
@ -34,39 +34,13 @@ class DownloadHeaderAdapter(
|
||||||
private val movieClickCallback: (DownloadClickEvent) -> Unit,
|
private val movieClickCallback: (DownloadClickEvent) -> Unit,
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
private val mBoundViewHolders: HashSet<DownloadButtonViewHolder> = HashSet()
|
|
||||||
private fun getAllBoundViewHolders(): Set<DownloadButtonViewHolder?>? {
|
|
||||||
return Collections.unmodifiableSet(mBoundViewHolders)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun killAdapter() {
|
|
||||||
getAllBoundViewHolders()?.forEach { view ->
|
|
||||||
view?.downloadButton?.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
|
||||||
if (holder is DownloadButtonViewHolder) {
|
|
||||||
holder.downloadButton.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
|
||||||
if (holder is DownloadButtonViewHolder) {
|
|
||||||
holder.downloadButton.dispose()
|
|
||||||
mBoundViewHolders.remove(holder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
|
|
||||||
if (holder is DownloadButtonViewHolder) {
|
|
||||||
holder.reattachDownloadButton()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return DownloadHeaderViewHolder(
|
return DownloadHeaderViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(R.layout.download_header_episode, parent, false),
|
DownloadHeaderEpisodeBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
),
|
||||||
clickCallback,
|
clickCallback,
|
||||||
movieClickCallback
|
movieClickCallback
|
||||||
)
|
)
|
||||||
|
@ -76,7 +50,6 @@ class DownloadHeaderAdapter(
|
||||||
when (holder) {
|
when (holder) {
|
||||||
is DownloadHeaderViewHolder -> {
|
is DownloadHeaderViewHolder -> {
|
||||||
holder.bind(cardList[position])
|
holder.bind(cardList[position])
|
||||||
mBoundViewHolders.add(holder)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,93 +60,89 @@ class DownloadHeaderAdapter(
|
||||||
|
|
||||||
class DownloadHeaderViewHolder
|
class DownloadHeaderViewHolder
|
||||||
constructor(
|
constructor(
|
||||||
itemView: View,
|
val binding: DownloadHeaderEpisodeBinding,
|
||||||
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
|
private val clickCallback: (DownloadHeaderClickEvent) -> Unit,
|
||||||
private val movieClickCallback: (DownloadClickEvent) -> Unit,
|
private val movieClickCallback: (DownloadClickEvent) -> Unit,
|
||||||
) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
override var downloadButton = EasyDownloadButton()
|
|
||||||
|
|
||||||
private val poster: ImageView? = itemView.download_header_poster
|
/*private val poster: ImageView? = itemView.download_header_poster
|
||||||
private val title: TextView = itemView.download_header_title
|
private val title: TextView = itemView.download_header_title
|
||||||
private val extraInfo: TextView = itemView.download_header_info
|
private val extraInfo: TextView = itemView.download_header_info
|
||||||
private val holder: CardView = itemView.episode_holder
|
private val holder: CardView = itemView.episode_holder
|
||||||
|
|
||||||
private val downloadBar: ContentLoadingProgressBar = itemView.download_header_progress_downloaded
|
private val downloadBar: ContentLoadingProgressBar = itemView.download_header_progress_downloaded
|
||||||
private val downloadImage: ImageView = itemView.download_header_episode_download
|
private val downloadImage: ImageView = itemView.download_header_episode_download
|
||||||
private val normalImage: ImageView = itemView.download_header_goto_child
|
private val normalImage: ImageView = itemView.download_header_goto_child*/
|
||||||
private var localCard: VisualDownloadHeaderCached? = null
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(card: VisualDownloadHeaderCached) {
|
fun bind(card: VisualDownloadHeaderCached) {
|
||||||
localCard = card
|
|
||||||
val d = card.data
|
val d = card.data
|
||||||
|
|
||||||
poster?.setImage(d.poster)
|
binding.downloadHeaderPoster.apply {
|
||||||
poster?.setOnClickListener {
|
setImage(d.poster)
|
||||||
clickCallback.invoke(DownloadHeaderClickEvent(1, d))
|
setOnClickListener {
|
||||||
|
clickCallback.invoke(DownloadHeaderClickEvent(1, d))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
title.text = d.name
|
binding.apply {
|
||||||
val mbString = formatShortFileSize(itemView.context, card.totalBytes)
|
|
||||||
|
|
||||||
//val isMovie = d.type.isMovieType()
|
binding.downloadHeaderTitle.text = d.name
|
||||||
if (card.child != null) {
|
val mbString = formatShortFileSize(itemView.context, card.totalBytes)
|
||||||
downloadBar.visibility = View.VISIBLE
|
|
||||||
downloadImage.visibility = View.VISIBLE
|
|
||||||
normalImage.visibility = View.GONE
|
|
||||||
/*setUpButton(
|
|
||||||
card.currentBytes,
|
|
||||||
card.totalBytes,
|
|
||||||
downloadBar,
|
|
||||||
downloadImage,
|
|
||||||
extraInfo,
|
|
||||||
card.child,
|
|
||||||
movieClickCallback
|
|
||||||
)*/
|
|
||||||
|
|
||||||
holder.setOnClickListener {
|
//val isMovie = d.type.isMovieType()
|
||||||
movieClickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, card.child))
|
if (card.child != null) {
|
||||||
}
|
//downloadHeaderProgressDownloaded.visibility = View.VISIBLE
|
||||||
} else {
|
|
||||||
downloadBar.visibility = View.GONE
|
|
||||||
downloadImage.visibility = View.GONE
|
|
||||||
normalImage.visibility = View.VISIBLE
|
|
||||||
|
|
||||||
try {
|
// downloadHeaderEpisodeDownload.visibility = View.VISIBLE
|
||||||
extraInfo.text =
|
binding.downloadHeaderGotoChild.visibility = View.GONE
|
||||||
extraInfo.context.getString(R.string.extra_info_format).format(
|
|
||||||
card.totalDownloads,
|
downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, movieClickCallback)
|
||||||
if (card.totalDownloads == 1) extraInfo.context.getString(R.string.episode) else extraInfo.context.getString(
|
downloadButton.isVisible = true
|
||||||
R.string.episodes
|
/*setUpButton(
|
||||||
),
|
card.currentBytes,
|
||||||
mbString
|
card.totalBytes,
|
||||||
|
downloadBar,
|
||||||
|
downloadImage,
|
||||||
|
extraInfo,
|
||||||
|
card.child,
|
||||||
|
movieClickCallback
|
||||||
|
)*/
|
||||||
|
|
||||||
|
episodeHolder.setOnClickListener {
|
||||||
|
movieClickCallback.invoke(
|
||||||
|
DownloadClickEvent(
|
||||||
|
DOWNLOAD_ACTION_PLAY_FILE,
|
||||||
|
card.child
|
||||||
|
)
|
||||||
)
|
)
|
||||||
} catch (t : Throwable) {
|
}
|
||||||
// you probably formatted incorrectly
|
} else {
|
||||||
extraInfo.text = "Error"
|
downloadButton.isVisible = false
|
||||||
logError(t)
|
// downloadHeaderProgressDownloaded.visibility = View.GONE
|
||||||
|
// downloadHeaderEpisodeDownload.visibility = View.GONE
|
||||||
|
binding.downloadHeaderGotoChild.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
try {
|
||||||
|
downloadHeaderInfo.text =
|
||||||
|
downloadHeaderInfo.context.getString(R.string.extra_info_format).format(
|
||||||
|
card.totalDownloads,
|
||||||
|
if (card.totalDownloads == 1) downloadHeaderInfo.context.getString(R.string.episode) else downloadHeaderInfo.context.getString(
|
||||||
|
R.string.episodes
|
||||||
|
),
|
||||||
|
mbString
|
||||||
|
)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
// you probably formatted incorrectly
|
||||||
|
downloadHeaderInfo.text = "Error"
|
||||||
|
logError(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
episodeHolder.setOnClickListener {
|
||||||
|
clickCallback.invoke(DownloadHeaderClickEvent(0, d))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
holder.setOnClickListener {
|
|
||||||
clickCallback.invoke(DownloadHeaderClickEvent(0, d))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun reattachDownloadButton() {
|
|
||||||
downloadButton.dispose()
|
|
||||||
val card = localCard
|
|
||||||
if (card?.child != null) {
|
|
||||||
downloadButton.setUpButton(
|
|
||||||
card.currentBytes,
|
|
||||||
card.totalBytes,
|
|
||||||
downloadBar,
|
|
||||||
downloadImage,
|
|
||||||
extraInfo,
|
|
||||||
card.child,
|
|
||||||
movieClickCallback
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,199 @@
|
||||||
|
package com.lagradost.cloudstream3.ui.download.button
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.text.format.Formatter
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.LayoutRes
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.widget.ContentLoadingProgressBar
|
||||||
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
|
|
||||||
|
typealias DownloadStatusTell = VideoDownloadManager.DownloadType
|
||||||
|
|
||||||
|
data class DownloadMetadata(
|
||||||
|
var id: Int,
|
||||||
|
var downloadedLength: Long,
|
||||||
|
var totalLength: Long,
|
||||||
|
var status: DownloadStatusTell? = null
|
||||||
|
) {
|
||||||
|
val progressPercentage: Long
|
||||||
|
get() = if (downloadedLength < 1024) 0 else maxOf(
|
||||||
|
0,
|
||||||
|
minOf(100, (downloadedLength * 100L) / totalLength)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
|
FrameLayout(context, attributeSet) {
|
||||||
|
|
||||||
|
var persistentId: Int? = null // used to save sessions
|
||||||
|
|
||||||
|
lateinit var progressBar: ContentLoadingProgressBar
|
||||||
|
var progressText: TextView? = null
|
||||||
|
|
||||||
|
/*val gid: String? get() = sessionIdToGid[persistentId]
|
||||||
|
|
||||||
|
// used for resuming data
|
||||||
|
var _lastRequestOverride: UriRequest? = null
|
||||||
|
var lastRequest: UriRequest?
|
||||||
|
get() = _lastRequestOverride ?: sessionIdToLastRequest[persistentId]
|
||||||
|
set(value) {
|
||||||
|
_lastRequestOverride = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var files: List<AbstractClient.JsonFile> = emptyList()*/
|
||||||
|
protected var isZeroBytes: Boolean = true
|
||||||
|
|
||||||
|
fun inflate(@LayoutRes layout: Int) {
|
||||||
|
inflate(context, layout, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
resetViewData()
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun resetViewData() {
|
||||||
|
// lastRequest = null
|
||||||
|
isZeroBytes = true
|
||||||
|
persistentId = null
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentMetaData: DownloadMetadata =
|
||||||
|
DownloadMetadata(0, 0, 0, null)
|
||||||
|
|
||||||
|
fun setPersistentId(id: Int) {
|
||||||
|
persistentId = id
|
||||||
|
currentMetaData.id = id
|
||||||
|
|
||||||
|
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(context, id)?.let { savedData ->
|
||||||
|
val downloadedBytes = savedData.fileLength
|
||||||
|
val totalBytes = savedData.totalBytes
|
||||||
|
|
||||||
|
/*lastRequest = savedData.uriRequest
|
||||||
|
files = savedData.files
|
||||||
|
|
||||||
|
var totalBytes: Long = 0
|
||||||
|
var downloadedBytes: Long = 0
|
||||||
|
for (file in savedData.files) {
|
||||||
|
downloadedBytes += file.completedLength
|
||||||
|
totalBytes += file.length
|
||||||
|
}*/
|
||||||
|
setProgress(downloadedBytes, totalBytes)
|
||||||
|
// some extra padding for just in case
|
||||||
|
val status = VideoDownloadManager.downloadStatus[id]
|
||||||
|
?: if (downloadedBytes > 1024L && downloadedBytes + 1024L >= totalBytes) DownloadStatusTell.IsDone else DownloadStatusTell.IsPaused
|
||||||
|
currentMetaData.apply {
|
||||||
|
this.id = id
|
||||||
|
this.downloadedLength = downloadedBytes
|
||||||
|
this.totalLength = totalBytes
|
||||||
|
this.status = status
|
||||||
|
}
|
||||||
|
setStatus(status)
|
||||||
|
} ?: run {
|
||||||
|
resetView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun setStatus(status: VideoDownloadManager.DownloadType?)
|
||||||
|
|
||||||
|
open fun setProgress(downloadedBytes: Long, totalBytes: Long) {
|
||||||
|
isZeroBytes = downloadedBytes == 0L
|
||||||
|
val steps = 10000L
|
||||||
|
progressBar.max = steps.toInt()
|
||||||
|
// div by zero error and 1 byte off is ok impo
|
||||||
|
val progress = (downloadedBytes * steps / (totalBytes + 1L)).toInt()
|
||||||
|
|
||||||
|
val animation = ProgressBarAnimation(
|
||||||
|
progressBar,
|
||||||
|
progressBar.progress.toFloat(),
|
||||||
|
progress.toFloat()
|
||||||
|
).apply {
|
||||||
|
fillAfter = true
|
||||||
|
duration =
|
||||||
|
if (progress > progressBar.progress) // we don't want to animate backward changes in progress
|
||||||
|
100
|
||||||
|
else
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isZeroBytes) {
|
||||||
|
progressText?.isVisible = false
|
||||||
|
} else {
|
||||||
|
progressText?.apply {
|
||||||
|
val currentMbString = Formatter.formatShortFileSize(context, downloadedBytes)
|
||||||
|
val totalMbString = Formatter.formatShortFileSize(context, totalBytes)
|
||||||
|
text =
|
||||||
|
//if (isTextPercentage) "%d%%".format(setCurrentBytes * 100L / setTotalBytes) else
|
||||||
|
context?.getString(R.string.download_size_format)
|
||||||
|
?.format(currentMbString, totalMbString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
progressBar.startAnimation(animation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun downloadStatusEvent(data: Pair<Int, VideoDownloadManager.DownloadType>) {
|
||||||
|
val (id, status) = data
|
||||||
|
if (id == persistentId) {
|
||||||
|
currentMetaData.status = status
|
||||||
|
setStatus(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*fun downloadDeleteEvent(data: Int) {
|
||||||
|
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*fun downloadEvent(data: Pair<Int, VideoDownloadManager.DownloadActionType>) {
|
||||||
|
val (id, action) = data
|
||||||
|
|
||||||
|
}*/
|
||||||
|
|
||||||
|
fun downloadProgressEvent(data: Triple<Int, Long, Long>) {
|
||||||
|
val (id, bytesDownloaded, bytesTotal) = data
|
||||||
|
if (id == persistentId) {
|
||||||
|
currentMetaData.downloadedLength = bytesDownloaded
|
||||||
|
currentMetaData.totalLength = bytesTotal
|
||||||
|
|
||||||
|
setProgress(bytesDownloaded, bytesTotal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAttachedToWindow() {
|
||||||
|
VideoDownloadManager.downloadStatusEvent += ::downloadStatusEvent
|
||||||
|
//VideoDownloadManager.downloadDeleteEvent += ::downloadDeleteEvent
|
||||||
|
//VideoDownloadManager.downloadEvent += ::downloadEvent
|
||||||
|
VideoDownloadManager.downloadProgressEvent += ::downloadProgressEvent
|
||||||
|
|
||||||
|
val pid = persistentId
|
||||||
|
if (pid != null) {
|
||||||
|
// refresh in case of onDetachedFromWindow -> onAttachedToWindow while still being ???????
|
||||||
|
setPersistentId(pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onAttachedToWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromWindow() {
|
||||||
|
VideoDownloadManager.downloadStatusEvent -= ::downloadStatusEvent
|
||||||
|
//VideoDownloadManager.downloadDeleteEvent -= ::downloadDeleteEvent
|
||||||
|
//VideoDownloadManager.downloadEvent -= ::downloadEvent
|
||||||
|
VideoDownloadManager.downloadProgressEvent -= ::downloadProgressEvent
|
||||||
|
|
||||||
|
super.onDetachedFromWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No checks required. Arg will always include a download with current id
|
||||||
|
* */
|
||||||
|
abstract fun updateViewOnDownload(metadata: DownloadMetadata)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a clean slate again, might be useful in recyclerview?
|
||||||
|
* */
|
||||||
|
abstract fun resetView()
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.lagradost.cloudstream3.ui.download.button
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
|
|
||||||
|
class DownloadButton(context: Context, attributeSet: AttributeSet) :
|
||||||
|
PieFetchButton(context, attributeSet) {
|
||||||
|
|
||||||
|
var mainText: TextView? = null
|
||||||
|
override fun onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow()
|
||||||
|
progressText = findViewById(R.id.result_movie_download_text_precentage)
|
||||||
|
mainText = findViewById(R.id.result_movie_download_text)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setStatus(status: DownloadStatusTell?) {
|
||||||
|
super.setStatus(status)
|
||||||
|
val txt = when (status) {
|
||||||
|
DownloadStatusTell.IsPaused -> R.string.download_paused
|
||||||
|
DownloadStatusTell.IsDownloading -> R.string.downloading
|
||||||
|
DownloadStatusTell.IsDone -> R.string.downloaded
|
||||||
|
else -> R.string.download
|
||||||
|
}
|
||||||
|
mainText?.setText(txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setDefaultClickListener(
|
||||||
|
card: VideoDownloadHelper.DownloadEpisodeCached,
|
||||||
|
textView: TextView?,
|
||||||
|
callback: (DownloadClickEvent) -> Unit
|
||||||
|
) {
|
||||||
|
this.setDefaultClickListener(
|
||||||
|
this.findViewById<MaterialButton>(R.id.download_movie_button),
|
||||||
|
textView,
|
||||||
|
card,
|
||||||
|
callback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun updateViewOnDownload(metadata: DownloadMetadata) {
|
||||||
|
super.updateViewOnDownload(metadata)
|
||||||
|
|
||||||
|
val isVis = metadata.progressPercentage > 0
|
||||||
|
progressText?.isVisible = isVis
|
||||||
|
if (isVis)
|
||||||
|
progressText?.text = "${metadata.progressPercentage}%"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,322 @@
|
||||||
|
package com.lagradost.cloudstream3.ui.download.button
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.view.animation.AnimationUtils
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DELETE_FILE
|
||||||
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
|
||||||
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK
|
||||||
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_PAUSE_DOWNLOAD
|
||||||
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_PLAY_FILE
|
||||||
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_RESUME_DOWNLOAD
|
||||||
|
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIcons
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
|
|
||||||
|
|
||||||
|
open class PieFetchButton(context: Context, attributeSet: AttributeSet) :
|
||||||
|
BaseFetchButton(context, attributeSet) {
|
||||||
|
|
||||||
|
private var waitingAnimation: Int = 0
|
||||||
|
private var animateWaiting: Boolean = false
|
||||||
|
private var activeOutline: Int = 0
|
||||||
|
private var nonActiveOutline: Int = 0
|
||||||
|
|
||||||
|
private var iconInit: Int = 0
|
||||||
|
private var iconError: Int = 0
|
||||||
|
private var iconComplete: Int = 0
|
||||||
|
private var iconActive: Int = 0
|
||||||
|
private var iconWaiting: Int = 0
|
||||||
|
private var iconRemoved: Int = 0
|
||||||
|
private var iconPaused: Int = 0
|
||||||
|
private var hideWhenIcon: Boolean = true
|
||||||
|
|
||||||
|
var overrideLayout: Int? = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val fillArray = arrayOf(
|
||||||
|
R.drawable.circular_progress_bar_clockwise,
|
||||||
|
R.drawable.circular_progress_bar_counter_clockwise,
|
||||||
|
R.drawable.circular_progress_bar_small_to_large,
|
||||||
|
R.drawable.circular_progress_bar_top_to_bottom,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var progressBarBackground: View
|
||||||
|
private var statusView: ImageView
|
||||||
|
|
||||||
|
open fun onInflate() {}
|
||||||
|
|
||||||
|
init {
|
||||||
|
context.obtainStyledAttributes(attributeSet, R.styleable.PieFetchButton, 0, 0).apply {
|
||||||
|
try {
|
||||||
|
inflate(
|
||||||
|
overrideLayout ?: getResourceId(
|
||||||
|
R.styleable.PieFetchButton_download_layout,
|
||||||
|
R.layout.download_button_view
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(
|
||||||
|
"PieFetchButton", "Error inflating PieFetchButton, " +
|
||||||
|
"check that you have declared the required aria2c attrs: aria2c_icon_scale aria2c_icon_color aria2c_outline_color aria2c_fill_color"
|
||||||
|
)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
progressBar = findViewById(R.id.progress_downloaded)
|
||||||
|
progressBarBackground = findViewById(R.id.progress_downloaded_background)
|
||||||
|
statusView = findViewById(R.id.image_download_status)
|
||||||
|
|
||||||
|
animateWaiting = getBoolean(
|
||||||
|
R.styleable.PieFetchButton_download_animate_waiting,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
hideWhenIcon = getBoolean(
|
||||||
|
R.styleable.PieFetchButton_download_hide_when_icon,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
waitingAnimation = getResourceId(
|
||||||
|
R.styleable.PieFetchButton_download_waiting_animation,
|
||||||
|
R.anim.rotate_around_center_point
|
||||||
|
)
|
||||||
|
|
||||||
|
activeOutline = getResourceId(
|
||||||
|
R.styleable.PieFetchButton_download_outline_active, R.drawable.circle_shape
|
||||||
|
)
|
||||||
|
|
||||||
|
nonActiveOutline = getResourceId(
|
||||||
|
R.styleable.PieFetchButton_download_outline_non_active,
|
||||||
|
R.drawable.circle_shape_dotted
|
||||||
|
)
|
||||||
|
iconInit = getResourceId(
|
||||||
|
R.styleable.PieFetchButton_download_icon_init, R.drawable.netflix_download
|
||||||
|
)
|
||||||
|
iconError = getResourceId(
|
||||||
|
R.styleable.PieFetchButton_download_icon_paused, R.drawable.download_icon_error
|
||||||
|
)
|
||||||
|
iconComplete = getResourceId(
|
||||||
|
R.styleable.PieFetchButton_download_icon_complete, R.drawable.download_icon_done
|
||||||
|
)
|
||||||
|
iconPaused = getResourceId(
|
||||||
|
R.styleable.PieFetchButton_download_icon_paused, 0//R.drawable.download_icon_pause
|
||||||
|
)
|
||||||
|
iconActive = getResourceId(
|
||||||
|
R.styleable.PieFetchButton_download_icon_active, 0 //R.drawable.download_icon_load
|
||||||
|
)
|
||||||
|
iconWaiting = getResourceId(
|
||||||
|
R.styleable.PieFetchButton_download_icon_waiting, 0
|
||||||
|
)
|
||||||
|
iconRemoved = getResourceId(
|
||||||
|
R.styleable.PieFetchButton_download_icon_removed, R.drawable.netflix_download
|
||||||
|
)
|
||||||
|
|
||||||
|
val fillIndex = getInt(R.styleable.PieFetchButton_download_fill, 0)
|
||||||
|
|
||||||
|
val progressDrawable = getResourceId(
|
||||||
|
R.styleable.PieFetchButton_download_fill_override, fillArray[fillIndex]
|
||||||
|
)
|
||||||
|
|
||||||
|
progressBar.progressDrawable = ContextCompat.getDrawable(context, progressDrawable)
|
||||||
|
|
||||||
|
recycle()
|
||||||
|
}
|
||||||
|
resetView()
|
||||||
|
onInflate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var currentStatus: DownloadStatusTell? = null
|
||||||
|
/*private fun getActivity(): Activity? {
|
||||||
|
var context = context
|
||||||
|
while (context is ContextWrapper) {
|
||||||
|
if (context is Activity) {
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
context = context.baseContext
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun callback(event : DownloadClickEvent) {
|
||||||
|
handleDownloadClick(
|
||||||
|
getActivity(),
|
||||||
|
event
|
||||||
|
)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
protected fun setDefaultClickListener(
|
||||||
|
view: View, textView: TextView?, card: VideoDownloadHelper.DownloadEpisodeCached,
|
||||||
|
callback: (DownloadClickEvent) -> Unit
|
||||||
|
) {
|
||||||
|
this.progressText = textView
|
||||||
|
this.setPersistentId(card.id)
|
||||||
|
view.setOnClickListener {
|
||||||
|
if (isZeroBytes) {
|
||||||
|
callback(DownloadClickEvent(DOWNLOAD_ACTION_DOWNLOAD, card))
|
||||||
|
//callback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_DOWNLOAD, data))
|
||||||
|
} else {
|
||||||
|
val list = arrayListOf(
|
||||||
|
Pair(DOWNLOAD_ACTION_PLAY_FILE, R.string.popup_play_file),
|
||||||
|
Pair(DOWNLOAD_ACTION_DELETE_FILE, R.string.popup_delete_file),
|
||||||
|
)
|
||||||
|
|
||||||
|
currentMetaData.apply {
|
||||||
|
// DON'T RESUME A DOWNLOADED FILE lastState != VideoDownloadManager.DownloadType.IsDone &&
|
||||||
|
if ((downloadedLength * 100 / totalLength) < 98) {
|
||||||
|
list.add(
|
||||||
|
if (status == VideoDownloadManager.DownloadType.IsDownloading)
|
||||||
|
Pair(DOWNLOAD_ACTION_PAUSE_DOWNLOAD, R.string.popup_pause_download)
|
||||||
|
else
|
||||||
|
Pair(
|
||||||
|
DOWNLOAD_ACTION_RESUME_DOWNLOAD,
|
||||||
|
R.string.popup_resume_download
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
it.popupMenuNoIcons(
|
||||||
|
list
|
||||||
|
) {
|
||||||
|
callback(DownloadClickEvent(itemId, card))
|
||||||
|
//callback.invoke(DownloadClickEvent(itemId, data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view.setOnLongClickListener {
|
||||||
|
callback(DownloadClickEvent(DOWNLOAD_ACTION_LONG_CLICK, card))
|
||||||
|
|
||||||
|
//clickCallback.invoke(DownloadClickEvent(DOWNLOAD_ACTION_LONG_CLICK, data))
|
||||||
|
return@setOnLongClickListener true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun setDefaultClickListener(
|
||||||
|
card: VideoDownloadHelper.DownloadEpisodeCached,
|
||||||
|
textView: TextView?,
|
||||||
|
callback: (DownloadClickEvent) -> Unit
|
||||||
|
) {
|
||||||
|
setDefaultClickListener(this, textView, card, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*open fun setDefaultClickListener(requestGetter: suspend BaseFetchButton.() -> List<UriRequest>) {
|
||||||
|
this.setOnClickListener {
|
||||||
|
when (this.currentStatus) {
|
||||||
|
null -> {
|
||||||
|
setStatus(DownloadStatusTell.IsPending)
|
||||||
|
ioThread {
|
||||||
|
val request = requestGetter.invoke(this)
|
||||||
|
if (request.size == 1) {
|
||||||
|
performDownload(request.first())
|
||||||
|
} else if (request.isNotEmpty()) {
|
||||||
|
performFailQueueDownload(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DownloadStatusTell.Paused -> {
|
||||||
|
resumeDownload()
|
||||||
|
}
|
||||||
|
DownloadStatusTell.Active -> {
|
||||||
|
pauseDownload()
|
||||||
|
}
|
||||||
|
DownloadStatusTell.Error -> {
|
||||||
|
redownload()
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/** Also sets currentStatus */
|
||||||
|
override fun setStatus(status: DownloadStatusTell?) {
|
||||||
|
currentStatus = status
|
||||||
|
|
||||||
|
//progressBar.isVisible =
|
||||||
|
// status != null && status != DownloadStatusTell.Complete && status != DownloadStatusTell.Error
|
||||||
|
//progressBarBackground.isVisible = status != null && status != DownloadStatusTell.Complete
|
||||||
|
val isPreActive = isZeroBytes && status == DownloadStatusTell.IsDownloading
|
||||||
|
|
||||||
|
if (animateWaiting && (status == DownloadStatusTell.IsPending || isPreActive)) {
|
||||||
|
val animation = AnimationUtils.loadAnimation(context, waitingAnimation)
|
||||||
|
progressBarBackground.startAnimation(animation)
|
||||||
|
} else {
|
||||||
|
progressBarBackground.clearAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
val progressDrawable =
|
||||||
|
if (status == DownloadStatusTell.IsDownloading && !isPreActive) activeOutline else nonActiveOutline
|
||||||
|
|
||||||
|
progressBarBackground.background =
|
||||||
|
ContextCompat.getDrawable(context, progressDrawable)
|
||||||
|
|
||||||
|
val drawable = getDrawableFromStatus(status)
|
||||||
|
statusView.setImageDrawable(drawable)
|
||||||
|
val isDrawable = drawable != null
|
||||||
|
|
||||||
|
statusView.isVisible = isDrawable
|
||||||
|
val hide = hideWhenIcon && isDrawable
|
||||||
|
if (hide) {
|
||||||
|
progressBar.clearAnimation()
|
||||||
|
progressBarBackground.clearAnimation()
|
||||||
|
}
|
||||||
|
progressBarBackground.isGone = hide
|
||||||
|
progressBar.isGone = hide
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun resetView() {
|
||||||
|
setStatus(null)
|
||||||
|
currentMetaData = DownloadMetadata(0, 0, 0, null)
|
||||||
|
isZeroBytes = true
|
||||||
|
progressBar.progress = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateViewOnDownload(metadata: DownloadMetadata) {
|
||||||
|
|
||||||
|
val newStatus = metadata.status
|
||||||
|
|
||||||
|
if (newStatus == null) {
|
||||||
|
resetView()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val isDone =
|
||||||
|
newStatus == DownloadStatusTell.IsDone || (metadata.downloadedLength > 1024 && metadata.downloadedLength + 1024 >= metadata.totalLength)
|
||||||
|
|
||||||
|
if (isDone)
|
||||||
|
setStatus(DownloadStatusTell.IsDone)
|
||||||
|
else {
|
||||||
|
setProgress(metadata.downloadedLength, metadata.totalLength)
|
||||||
|
setStatus(newStatus)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun getDrawableFromStatus(status: DownloadStatusTell?): Drawable? {
|
||||||
|
val drawableInt = when (status) {
|
||||||
|
DownloadStatusTell.IsPaused -> iconPaused
|
||||||
|
DownloadStatusTell.IsPending -> iconWaiting
|
||||||
|
DownloadStatusTell.IsDownloading -> iconActive
|
||||||
|
DownloadStatusTell.IsFailed -> iconError
|
||||||
|
DownloadStatusTell.IsDone -> iconComplete
|
||||||
|
DownloadStatusTell.IsStopped -> iconRemoved
|
||||||
|
null -> iconInit
|
||||||
|
}
|
||||||
|
if (drawableInt == 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return ContextCompat.getDrawable(this.context, drawableInt)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.lagradost.cloudstream3.ui.download.button
|
||||||
|
|
||||||
|
import android.view.animation.Animation
|
||||||
|
import android.view.animation.Transformation
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
|
||||||
|
class ProgressBarAnimation(
|
||||||
|
private val progressBar: ProgressBar,
|
||||||
|
private val from: Float,
|
||||||
|
private val to: Float
|
||||||
|
) :
|
||||||
|
Animation() {
|
||||||
|
override fun applyTransformation(interpolatedTime: Float, t: Transformation?) {
|
||||||
|
super.applyTransformation(interpolatedTime, t)
|
||||||
|
val value = from + (to - from) * interpolatedTime
|
||||||
|
progressBar.progress = value.toInt()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,23 @@
|
||||||
package com.lagradost.cloudstream3.ui.home
|
package com.lagradost.cloudstream3.ui.home
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.SearchResponse
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
|
import com.lagradost.cloudstream3.databinding.HomeResultGridBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.HomeResultGridExpandedBinding
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.isRtl
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.IsBottomLayout
|
import com.lagradost.cloudstream3.utils.UIHelper.IsBottomLayout
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
import kotlinx.android.synthetic.main.home_result_grid.view.background_card
|
|
||||||
import kotlinx.android.synthetic.main.home_result_grid_expanded.view.*
|
|
||||||
|
|
||||||
class HomeChildItemAdapter(
|
class HomeChildItemAdapter(
|
||||||
val cardList: MutableList<SearchResponse>,
|
val cardList: MutableList<SearchResponse>,
|
||||||
private val overrideLayout: Int? = null,
|
|
||||||
private val nextFocusUp: Int? = null,
|
private val nextFocusUp: Int? = null,
|
||||||
private val nextFocusDown: Int? = null,
|
private val nextFocusDown: Int? = null,
|
||||||
private val clickCallback: (SearchClickCallback) -> Unit,
|
private val clickCallback: (SearchClickCallback) -> Unit,
|
||||||
|
@ -26,16 +27,28 @@ class HomeChildItemAdapter(
|
||||||
var hasNext: Boolean = false
|
var hasNext: Boolean = false
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
val layout = overrideLayout
|
val expanded = parent.context.IsBottomLayout()
|
||||||
?: if (parent.context.IsBottomLayout()) R.layout.home_result_grid_expanded else R.layout.home_result_grid
|
/* val layout = if (bottom) R.layout.home_result_grid_expanded else R.layout.home_result_grid
|
||||||
|
|
||||||
|
val root = LayoutInflater.from(parent.context).inflate(layout, parent, false)
|
||||||
|
val binding = HomeResultGridBinding.bind(root)*/
|
||||||
|
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
val binding = if (expanded) HomeResultGridExpandedBinding.inflate(
|
||||||
|
inflater,
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
) else HomeResultGridBinding.inflate(inflater, parent, false)
|
||||||
|
|
||||||
|
|
||||||
return CardViewHolder(
|
return CardViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(layout, parent, false),
|
binding,
|
||||||
clickCallback,
|
clickCallback,
|
||||||
itemCount,
|
itemCount,
|
||||||
nextFocusUp,
|
nextFocusUp,
|
||||||
nextFocusDown,
|
nextFocusDown,
|
||||||
isHorizontal
|
isHorizontal,
|
||||||
|
parent.isRtl()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,14 +82,15 @@ class HomeChildItemAdapter(
|
||||||
|
|
||||||
class CardViewHolder
|
class CardViewHolder
|
||||||
constructor(
|
constructor(
|
||||||
itemView: View,
|
val binding: ViewBinding,
|
||||||
private val clickCallback: (SearchClickCallback) -> Unit,
|
private val clickCallback: (SearchClickCallback) -> Unit,
|
||||||
var itemCount: Int,
|
var itemCount: Int,
|
||||||
private val nextFocusUp: Int? = null,
|
private val nextFocusUp: Int? = null,
|
||||||
private val nextFocusDown: Int? = null,
|
private val nextFocusDown: Int? = null,
|
||||||
private val isHorizontal: Boolean = false
|
private val isHorizontal: Boolean = false,
|
||||||
|
private val isRtl : Boolean
|
||||||
) :
|
) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
fun bind(card: SearchResponse, position: Int) {
|
fun bind(card: SearchResponse, position: Int) {
|
||||||
|
|
||||||
|
@ -87,26 +101,71 @@ class HomeChildItemAdapter(
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
(itemView.image_holder ?: itemView.background_card)?.apply {
|
if (position == 0) { // to fix tv
|
||||||
val min = 114.toPx
|
if (isRtl) {
|
||||||
val max = 180.toPx
|
itemView.nextFocusRightId = R.id.nav_rail_view
|
||||||
|
itemView.nextFocusLeftId = -1
|
||||||
layoutParams =
|
}
|
||||||
layoutParams.apply {
|
else {
|
||||||
width = if (!isHorizontal) {
|
itemView.nextFocusLeftId = R.id.nav_rail_view
|
||||||
min
|
itemView.nextFocusRightId = -1
|
||||||
} else {
|
}
|
||||||
max
|
} else {
|
||||||
}
|
itemView.nextFocusRightId = -1
|
||||||
height = if (!isHorizontal) {
|
itemView.nextFocusLeftId = -1
|
||||||
max
|
|
||||||
} else {
|
|
||||||
min
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
when (binding) {
|
||||||
|
is HomeResultGridBinding -> {
|
||||||
|
binding.backgroundCard.apply {
|
||||||
|
val min = 114.toPx
|
||||||
|
val max = 180.toPx
|
||||||
|
|
||||||
|
layoutParams =
|
||||||
|
layoutParams.apply {
|
||||||
|
width = if (!isHorizontal) {
|
||||||
|
min
|
||||||
|
} else {
|
||||||
|
max
|
||||||
|
}
|
||||||
|
height = if (!isHorizontal) {
|
||||||
|
max
|
||||||
|
} else {
|
||||||
|
min
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
is HomeResultGridExpandedBinding -> {
|
||||||
|
binding.backgroundCard.apply {
|
||||||
|
val min = 114.toPx
|
||||||
|
val max = 180.toPx
|
||||||
|
|
||||||
|
layoutParams =
|
||||||
|
layoutParams.apply {
|
||||||
|
width = if (!isHorizontal) {
|
||||||
|
min
|
||||||
|
} else {
|
||||||
|
max
|
||||||
|
}
|
||||||
|
height = if (!isHorizontal) {
|
||||||
|
max
|
||||||
|
} else {
|
||||||
|
min
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position == 0) { // to fix tv
|
||||||
|
binding.backgroundCard.nextFocusLeftId = R.id.nav_rail_view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SearchResultBuilder.bind(
|
SearchResultBuilder.bind(
|
||||||
clickCallback,
|
clickCallback,
|
||||||
card,
|
card,
|
||||||
|
@ -118,9 +177,6 @@ class HomeChildItemAdapter(
|
||||||
)
|
)
|
||||||
itemView.tag = position
|
itemView.tag = position
|
||||||
|
|
||||||
if (position == 0) { // to fix tv
|
|
||||||
itemView.background_card?.nextFocusLeftId = R.id.nav_rail_view
|
|
||||||
}
|
|
||||||
//val ani = ScaleAnimation(0.9f, 1.0f, 0.9f, 1f)
|
//val ani = ScaleAnimation(0.9f, 1.0f, 0.9f, 1f)
|
||||||
//ani.fillAfter = true
|
//ani.fillAfter = true
|
||||||
//ani.duration = 200
|
//ani.duration = 200
|
||||||
|
|
|
@ -31,17 +31,23 @@ import com.lagradost.cloudstream3.APIHolder.apis
|
||||||
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
|
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
|
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||||
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.bookmarksUpdatedEvent
|
import com.lagradost.cloudstream3.MainActivity.Companion.bookmarksUpdatedEvent
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.mainPluginsLoadedEvent
|
import com.lagradost.cloudstream3.MainActivity.Companion.mainPluginsLoadedEvent
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentHomeBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.HomeEpisodesExpandedBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.HomeSelectMainpageBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.TvtypesChipsBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
|
import com.lagradost.cloudstream3.mvvm.observeNullable
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
|
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
|
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.WatchType
|
||||||
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
|
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
|
||||||
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
import com.lagradost.cloudstream3.ui.search.*
|
import com.lagradost.cloudstream3.ui.search.*
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
|
@ -64,24 +70,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
|
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
|
||||||
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
|
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
|
||||||
import kotlinx.android.synthetic.main.activity_main_tv.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.home_api_fab
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.home_change_api_loading
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.home_loading
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.home_loading_error
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.home_loading_shimmer
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.home_loading_statusbar
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.home_master_recycler
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.home_reload_connection_open_in_browser
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.home_reload_connectionerror
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.result_error_text
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home_tv.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_result.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_search.*
|
|
||||||
import kotlinx.android.synthetic.main.home_episodes_expanded.*
|
|
||||||
import kotlinx.android.synthetic.main.tvtypes_chips.*
|
|
||||||
import kotlinx.android.synthetic.main.tvtypes_chips.view.*
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
|
@ -125,22 +114,26 @@ class HomeFragment : Fragment() {
|
||||||
expand: HomeViewModel.ExpandableHomepageList,
|
expand: HomeViewModel.ExpandableHomepageList,
|
||||||
deleteCallback: (() -> Unit)? = null,
|
deleteCallback: (() -> Unit)? = null,
|
||||||
expandCallback: (suspend (String) -> HomeViewModel.ExpandableHomepageList?)? = null,
|
expandCallback: (suspend (String) -> HomeViewModel.ExpandableHomepageList?)? = null,
|
||||||
dismissCallback : (() -> Unit),
|
dismissCallback: (() -> Unit),
|
||||||
): BottomSheetDialog {
|
): BottomSheetDialog {
|
||||||
val context = this
|
val context = this
|
||||||
val bottomSheetDialogBuilder = BottomSheetDialog(context)
|
val bottomSheetDialogBuilder = BottomSheetDialog(context)
|
||||||
|
val binding: HomeEpisodesExpandedBinding = HomeEpisodesExpandedBinding.inflate(
|
||||||
bottomSheetDialogBuilder.setContentView(R.layout.home_episodes_expanded)
|
bottomSheetDialogBuilder.layoutInflater,
|
||||||
val title = bottomSheetDialogBuilder.findViewById<TextView>(R.id.home_expanded_text)!!
|
null,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
bottomSheetDialogBuilder.setContentView(binding.root)
|
||||||
|
//val title = bottomSheetDialogBuilder.findViewById<TextView>(R.id.home_expanded_text)!!
|
||||||
|
|
||||||
//title.findViewTreeLifecycleOwner().lifecycle.addObserver()
|
//title.findViewTreeLifecycleOwner().lifecycle.addObserver()
|
||||||
|
|
||||||
val item = expand.list
|
val item = expand.list
|
||||||
title.text = item.name
|
binding.homeExpandedText.text = item.name
|
||||||
val recycle =
|
// val recycle =
|
||||||
bottomSheetDialogBuilder.findViewById<AutofitRecyclerView>(R.id.home_expanded_recycler)!!
|
// bottomSheetDialogBuilder.findViewById<AutofitRecyclerView>(R.id.home_expanded_recycler)!!
|
||||||
val titleHolder =
|
//val titleHolder =
|
||||||
bottomSheetDialogBuilder.findViewById<FrameLayout>(R.id.home_expanded_drag_down)!!
|
// bottomSheetDialogBuilder.findViewById<FrameLayout>(R.id.home_expanded_drag_down)!!
|
||||||
|
|
||||||
// main {
|
// main {
|
||||||
//(bottomSheetDialogBuilder.ownerActivity as androidx.fragment.app.FragmentActivity?)?.supportFragmentManager?.fragments?.lastOrNull()?.viewLifecycleOwner?.apply {
|
//(bottomSheetDialogBuilder.ownerActivity as androidx.fragment.app.FragmentActivity?)?.supportFragmentManager?.fragments?.lastOrNull()?.viewLifecycleOwner?.apply {
|
||||||
|
@ -159,10 +152,10 @@ class HomeFragment : Fragment() {
|
||||||
// })
|
// })
|
||||||
//}
|
//}
|
||||||
// }
|
// }
|
||||||
val delete = bottomSheetDialogBuilder.home_expanded_delete
|
//val delete = bottomSheetDialogBuilder.home_expanded_delete
|
||||||
delete.isGone = deleteCallback == null
|
binding.homeExpandedDelete.isGone = deleteCallback == null
|
||||||
if (deleteCallback != null) {
|
if (deleteCallback != null) {
|
||||||
delete.setOnClickListener {
|
binding.homeExpandedDelete.setOnClickListener {
|
||||||
try {
|
try {
|
||||||
val builder: AlertDialog.Builder = AlertDialog.Builder(context)
|
val builder: AlertDialog.Builder = AlertDialog.Builder(context)
|
||||||
val dialogClickListener =
|
val dialogClickListener =
|
||||||
|
@ -172,6 +165,7 @@ class HomeFragment : Fragment() {
|
||||||
deleteCallback.invoke()
|
deleteCallback.invoke()
|
||||||
bottomSheetDialogBuilder.dismissSafe(this)
|
bottomSheetDialogBuilder.dismissSafe(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
DialogInterface.BUTTON_NEGATIVE -> {}
|
DialogInterface.BUTTON_NEGATIVE -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,26 +185,27 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding.homeExpandedDragDown.setOnClickListener {
|
||||||
titleHolder.setOnClickListener {
|
|
||||||
bottomSheetDialogBuilder.dismissSafe(this)
|
bottomSheetDialogBuilder.dismissSafe(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Span settings
|
// Span settings
|
||||||
recycle.spanCount = currentSpan
|
binding.homeExpandedRecycler.spanCount = currentSpan
|
||||||
|
|
||||||
recycle.adapter = SearchAdapter(item.list.toMutableList(), recycle) { callback ->
|
binding.homeExpandedRecycler.adapter =
|
||||||
handleSearchClickCallback(this, callback)
|
SearchAdapter(item.list.toMutableList(), binding.homeExpandedRecycler) { callback ->
|
||||||
if (callback.action == SEARCH_ACTION_LOAD || callback.action == SEARCH_ACTION_PLAY_FILE) {
|
handleSearchClickCallback(callback)
|
||||||
bottomSheetDialogBuilder.ownHide() // we hide here because we want to resume it later
|
if (callback.action == SEARCH_ACTION_LOAD || callback.action == SEARCH_ACTION_PLAY_FILE) {
|
||||||
//bottomSheetDialogBuilder.dismissSafe(this)
|
bottomSheetDialogBuilder.ownHide() // we hide here because we want to resume it later
|
||||||
|
//bottomSheetDialogBuilder.dismissSafe(this)
|
||||||
|
}
|
||||||
|
}.apply {
|
||||||
|
hasNext = expand.hasNext
|
||||||
}
|
}
|
||||||
}.apply {
|
|
||||||
hasNext = expand.hasNext
|
|
||||||
}
|
|
||||||
|
|
||||||
recycle.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
binding.homeExpandedRecycler.addOnScrollListener(object :
|
||||||
|
RecyclerView.OnScrollListener() {
|
||||||
var expandCount = 0
|
var expandCount = 0
|
||||||
val name = expand.list.name
|
val name = expand.list.name
|
||||||
|
|
||||||
|
@ -238,7 +233,7 @@ class HomeFragment : Fragment() {
|
||||||
})
|
})
|
||||||
|
|
||||||
val spanListener = { span: Int ->
|
val spanListener = { span: Int ->
|
||||||
recycle.spanCount = span
|
binding.homeExpandedRecycler.spanCount = span
|
||||||
//(recycle.adapter as SearchAdapter).notifyDataSetChanged()
|
//(recycle.adapter as SearchAdapter).notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,19 +275,19 @@ class HomeFragment : Fragment() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPairList(header: ChipGroup) = getPairList(
|
private fun getPairList(header: TvtypesChipsBinding) = getPairList(
|
||||||
header.home_select_anime,
|
header.homeSelectAnime,
|
||||||
header.home_select_cartoons,
|
header.homeSelectCartoons,
|
||||||
header.home_select_tv_series,
|
header.homeSelectTvSeries,
|
||||||
header.home_select_documentaries,
|
header.homeSelectDocumentaries,
|
||||||
header.home_select_movies,
|
header.homeSelectMovies,
|
||||||
header.home_select_asian,
|
header.homeSelectAsian,
|
||||||
header.home_select_livestreams,
|
header.homeSelectLivestreams,
|
||||||
header.home_select_nsfw,
|
header.homeSelectNsfw,
|
||||||
header.home_select_others
|
header.homeSelectOthers
|
||||||
)
|
)
|
||||||
|
|
||||||
fun validateChips(header: ChipGroup?, validTypes: List<TvType>) {
|
fun validateChips(header: TvtypesChipsBinding?, validTypes: List<TvType>) {
|
||||||
if (header == null) return
|
if (header == null) return
|
||||||
val pairList = getPairList(header)
|
val pairList = getPairList(header)
|
||||||
for ((button, types) in pairList) {
|
for ((button, types) in pairList) {
|
||||||
|
@ -301,7 +296,7 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateChips(header: ChipGroup?, selectedTypes: List<TvType>) {
|
fun updateChips(header: TvtypesChipsBinding?, selectedTypes: List<TvType>) {
|
||||||
if (header == null) return
|
if (header == null) return
|
||||||
val pairList = getPairList(header)
|
val pairList = getPairList(header)
|
||||||
for ((button, types) in pairList) {
|
for ((button, types) in pairList) {
|
||||||
|
@ -311,7 +306,7 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bindChips(
|
fun bindChips(
|
||||||
header: ChipGroup?,
|
header: TvtypesChipsBinding?,
|
||||||
selectedTypes: List<TvType>,
|
selectedTypes: List<TvType>,
|
||||||
validTypes: List<TvType>,
|
validTypes: List<TvType>,
|
||||||
callback: (List<TvType>) -> Unit
|
callback: (List<TvType>) -> Unit
|
||||||
|
@ -344,7 +339,13 @@ class HomeFragment : Fragment() {
|
||||||
BottomSheetDialog(this)
|
BottomSheetDialog(this)
|
||||||
|
|
||||||
builder.behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
builder.behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
builder.setContentView(R.layout.home_select_mainpage)
|
val binding: HomeSelectMainpageBinding = HomeSelectMainpageBinding.inflate(
|
||||||
|
builder.layoutInflater,
|
||||||
|
null,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.setContentView(binding.root)
|
||||||
builder.show()
|
builder.show()
|
||||||
builder.let { dialog ->
|
builder.let { dialog ->
|
||||||
val isMultiLang = getApiProviderLangSettings().let { set ->
|
val isMultiLang = getApiProviderLangSettings().let { set ->
|
||||||
|
@ -360,14 +361,11 @@ class HomeFragment : Fragment() {
|
||||||
?.toMutableList()
|
?.toMutableList()
|
||||||
?: mutableListOf(TvType.Movie, TvType.TvSeries)
|
?: mutableListOf(TvType.Movie, TvType.TvSeries)
|
||||||
|
|
||||||
val cancelBtt = dialog.findViewById<MaterialButton>(R.id.cancel_btt)
|
binding.cancelBtt.setOnClickListener {
|
||||||
val applyBtt = dialog.findViewById<MaterialButton>(R.id.apply_btt)
|
|
||||||
|
|
||||||
cancelBtt?.setOnClickListener {
|
|
||||||
dialog.dismissSafe()
|
dialog.dismissSafe()
|
||||||
}
|
}
|
||||||
|
|
||||||
applyBtt?.setOnClickListener {
|
binding.applyBtt.setOnClickListener {
|
||||||
if (currentApiName != selectedApiName) {
|
if (currentApiName != selectedApiName) {
|
||||||
currentApiName?.let(callback)
|
currentApiName?.let(callback)
|
||||||
}
|
}
|
||||||
|
@ -408,7 +406,7 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bindChips(
|
bindChips(
|
||||||
dialog.home_select_group,
|
binding.tvtypesChipsScroll.tvtypesChips,
|
||||||
preSelectedTypes,
|
preSelectedTypes,
|
||||||
validAPIs.flatMap { it.supportedTypes }.distinct()
|
validAPIs.flatMap { it.supportedTypes }.distinct()
|
||||||
) { list ->
|
) { list ->
|
||||||
|
@ -423,6 +421,9 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
private val homeViewModel: HomeViewModel by activityViewModels()
|
private val homeViewModel: HomeViewModel by activityViewModels()
|
||||||
|
|
||||||
|
var binding: FragmentHomeBinding? = null
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
@ -430,14 +431,25 @@ class HomeFragment : Fragment() {
|
||||||
): View? {
|
): View? {
|
||||||
//homeViewModel =
|
//homeViewModel =
|
||||||
// ViewModelProvider(this).get(HomeViewModel::class.java)
|
// ViewModelProvider(this).get(HomeViewModel::class.java)
|
||||||
|
|
||||||
bottomSheetDialog?.ownShow()
|
bottomSheetDialog?.ownShow()
|
||||||
val layout =
|
val layout =
|
||||||
if (isTvSettings()) R.layout.fragment_home_tv else R.layout.fragment_home
|
if (isTvSettings()) R.layout.fragment_home_tv else R.layout.fragment_home
|
||||||
return inflater.inflate(layout, container, false)
|
val root = inflater.inflate(layout, container, false)
|
||||||
|
binding = try {
|
||||||
|
FragmentHomeBinding.bind(root)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
showToast(txt(R.string.unable_to_inflate, t.message ?: ""), Toast.LENGTH_LONG)
|
||||||
|
logError(t)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
bottomSheetDialog?.ownHide()
|
bottomSheetDialog?.ownHide()
|
||||||
|
binding = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -450,7 +462,7 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
private val apiChangeClickListener = View.OnClickListener { view ->
|
private val apiChangeClickListener = View.OnClickListener { view ->
|
||||||
view.context.selectHomepage(currentApiName) { api ->
|
view.context.selectHomepage(currentApiName) { api ->
|
||||||
homeViewModel.loadAndCancel(api)
|
homeViewModel.loadAndCancel(api, forceReload = true,fromUI = true)
|
||||||
}
|
}
|
||||||
/*val validAPIs = view.context?.filterProviderByPreferredMedia()?.toMutableList() ?: mutableListOf()
|
/*val validAPIs = view.context?.filterProviderByPreferredMedia()?.toMutableList() ?: mutableListOf()
|
||||||
|
|
||||||
|
@ -467,196 +479,149 @@ class HomeFragment : Fragment() {
|
||||||
fixGrid()
|
fixGrid()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bookmarksUpdated(_data : Boolean) {
|
|
||||||
reloadStored()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
super.onResume()
|
|
||||||
reloadStored()
|
|
||||||
bookmarksUpdatedEvent += ::bookmarksUpdated
|
|
||||||
afterPluginsLoadedEvent += ::afterPluginsLoaded
|
|
||||||
mainPluginsLoadedEvent += ::afterMainPluginsLoaded
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
bookmarksUpdatedEvent -= ::bookmarksUpdated
|
|
||||||
afterPluginsLoadedEvent -= ::afterPluginsLoaded
|
|
||||||
mainPluginsLoadedEvent -= ::afterMainPluginsLoaded
|
|
||||||
super.onStop()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reloadStored() {
|
|
||||||
homeViewModel.loadResumeWatching()
|
|
||||||
val list = EnumSet.noneOf(WatchType::class.java)
|
|
||||||
getKey<IntArray>(HOME_BOOKMARK_VALUE_LIST)?.map { WatchType.fromInternalId(it) }?.let {
|
|
||||||
list.addAll(it)
|
|
||||||
}
|
|
||||||
homeViewModel.loadStoredData(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun afterMainPluginsLoaded(unused: Boolean = false) {
|
|
||||||
loadHomePage(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun afterPluginsLoaded(forceReload: Boolean) {
|
|
||||||
loadHomePage(forceReload)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadHomePage(forceReload: Boolean) {
|
|
||||||
val apiName = context?.getKey<String>(USER_SELECTED_HOMEPAGE_API)
|
|
||||||
|
|
||||||
if (homeViewModel.apiName.value != apiName || apiName == null || forceReload) {
|
|
||||||
//println("Caught home: " + homeViewModel.apiName.value + " at " + apiName)
|
|
||||||
homeViewModel.loadAndCancel(apiName, forceReload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun homeHandleSearch(callback: SearchClickCallback) {
|
|
||||||
if (callback.action == SEARCH_ACTION_FOCUSED) {
|
|
||||||
//focusCallback(callback.card)
|
|
||||||
} else {
|
|
||||||
handleSearchClickCallback(activity, callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var currentApiName: String? = null
|
private var currentApiName: String? = null
|
||||||
private var toggleRandomButton = false
|
private var toggleRandomButton = false
|
||||||
|
|
||||||
private var bottomSheetDialog: BottomSheetDialog? = null
|
private var bottomSheetDialog: BottomSheetDialog? = null
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
fixGrid()
|
fixGrid()
|
||||||
|
|
||||||
home_change_api_loading?.setOnClickListener(apiChangeClickListener)
|
binding?.apply {
|
||||||
home_api_fab?.setOnClickListener(apiChangeClickListener)
|
homeChangeApiLoading.setOnClickListener(apiChangeClickListener)
|
||||||
home_random?.setOnClickListener {
|
//homeChangeApiLoading.setOnClickListener(apiChangeClickListener)
|
||||||
if (listHomepageItems.isNotEmpty()) {
|
homeApiFab.setOnClickListener(apiChangeClickListener)
|
||||||
activity.loadSearchResult(listHomepageItems.random())
|
homeRandom.setOnClickListener {
|
||||||
|
if (listHomepageItems.isNotEmpty()) {
|
||||||
|
activity.loadSearchResult(listHomepageItems.random())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
homeMasterRecycler.adapter =
|
||||||
|
HomeParentItemAdapterPreview(
|
||||||
|
mutableListOf(),
|
||||||
|
homeViewModel
|
||||||
|
)
|
||||||
|
fixPaddingStatusbar(homeLoadingStatusbar)
|
||||||
|
|
||||||
|
if (isTvSettings()) {
|
||||||
|
homeApiFab.isVisible = false
|
||||||
|
if (isTrueTvSettings()) {
|
||||||
|
homeChangeApiLoading.isVisible = true
|
||||||
|
homeChangeApiLoading.isFocusable = true
|
||||||
|
homeChangeApiLoading.isFocusableInTouchMode = true
|
||||||
|
}
|
||||||
|
// home_bookmark_select?.isFocusable = true
|
||||||
|
// home_bookmark_select?.isFocusableInTouchMode = true
|
||||||
|
} else {
|
||||||
|
homeApiFab.isVisible = true
|
||||||
|
homeChangeApiLoading.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
homeMasterRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
if (dy > 0) { //check for scroll down
|
||||||
|
homeApiFab.shrink() // hide
|
||||||
|
homeRandom.shrink()
|
||||||
|
} else if (dy < -5) {
|
||||||
|
if (!isTvSettings()) {
|
||||||
|
homeApiFab.extend() // show
|
||||||
|
homeRandom.extend()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Load value for toggling Random button. Hide at startup
|
//Load value for toggling Random button. Hide at startup
|
||||||
context?.let {
|
context?.let {
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(it)
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(it)
|
||||||
toggleRandomButton =
|
toggleRandomButton =
|
||||||
settingsManager.getBoolean(getString(R.string.random_button_key), false)
|
settingsManager.getBoolean(
|
||||||
home_random?.visibility = View.GONE
|
getString(R.string.random_button_key),
|
||||||
}
|
false
|
||||||
|
) && !isTvSettings()
|
||||||
observe(homeViewModel.preview) { preview ->
|
binding?.homeRandom?.visibility = View.GONE
|
||||||
(home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setPreviewData(
|
|
||||||
preview
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(homeViewModel.apiName) { apiName ->
|
observe(homeViewModel.apiName) { apiName ->
|
||||||
currentApiName = apiName
|
currentApiName = apiName
|
||||||
home_api_fab?.text = apiName
|
binding?.homeApiFab?.text = apiName
|
||||||
(home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setApiName(
|
|
||||||
apiName
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(homeViewModel.page) { data ->
|
observe(homeViewModel.page) { data ->
|
||||||
when (data) {
|
binding?.apply {
|
||||||
is Resource.Success -> {
|
when (data) {
|
||||||
home_loading_shimmer?.stopShimmer()
|
is Resource.Success -> {
|
||||||
|
homeLoadingShimmer.stopShimmer()
|
||||||
|
|
||||||
val d = data.value
|
val d = data.value
|
||||||
val mutableListOfResponse = mutableListOf<SearchResponse>()
|
val mutableListOfResponse = mutableListOf<SearchResponse>()
|
||||||
listHomepageItems.clear()
|
listHomepageItems.clear()
|
||||||
|
|
||||||
(home_master_recycler?.adapter as? ParentItemAdapter)?.updateList(
|
(homeMasterRecycler.adapter as? ParentItemAdapter)?.updateList(
|
||||||
d.values.toMutableList(),
|
d.values.toMutableList(),
|
||||||
home_master_recycler
|
homeMasterRecycler
|
||||||
)
|
)
|
||||||
|
|
||||||
home_loading?.isVisible = false
|
homeLoading.isVisible = false
|
||||||
home_loading_error?.isVisible = false
|
homeLoadingError.isVisible = false
|
||||||
home_master_recycler?.isVisible = true
|
homeMasterRecycler.isVisible = true
|
||||||
//home_loaded?.isVisible = true
|
//home_loaded?.isVisible = true
|
||||||
if (toggleRandomButton) {
|
if (toggleRandomButton) {
|
||||||
//Flatten list
|
//Flatten list
|
||||||
d.values.forEach { dlist ->
|
d.values.forEach { dlist ->
|
||||||
mutableListOfResponse.addAll(dlist.list.list)
|
mutableListOfResponse.addAll(dlist.list.list)
|
||||||
|
}
|
||||||
|
listHomepageItems.addAll(mutableListOfResponse.distinctBy { it.url })
|
||||||
|
|
||||||
|
homeRandom.isVisible = listHomepageItems.isNotEmpty()
|
||||||
|
} else {
|
||||||
|
homeRandom.isGone = true
|
||||||
}
|
}
|
||||||
listHomepageItems.addAll(mutableListOfResponse.distinctBy { it.url })
|
|
||||||
home_random?.isVisible = listHomepageItems.isNotEmpty()
|
|
||||||
} else {
|
|
||||||
home_random?.isGone = true
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
is Resource.Failure -> {
|
|
||||||
home_loading_shimmer?.stopShimmer()
|
|
||||||
|
|
||||||
result_error_text.text = data.errorString
|
is Resource.Failure -> {
|
||||||
|
homeLoadingShimmer.stopShimmer()
|
||||||
|
resultErrorText.text = data.errorString
|
||||||
|
homeReloadConnectionerror.setOnClickListener(apiChangeClickListener)
|
||||||
|
homeReloadConnectionOpenInBrowser.setOnClickListener { view ->
|
||||||
|
val validAPIs = apis//.filter { api -> api.hasMainPage }
|
||||||
|
|
||||||
home_reload_connectionerror.setOnClickListener(apiChangeClickListener)
|
view.popupMenuNoIconsAndNoStringRes(validAPIs.mapIndexed { index, api ->
|
||||||
|
Pair(
|
||||||
home_reload_connection_open_in_browser.setOnClickListener { view ->
|
index,
|
||||||
val validAPIs = apis//.filter { api -> api.hasMainPage }
|
api.name
|
||||||
|
)
|
||||||
view.popupMenuNoIconsAndNoStringRes(validAPIs.mapIndexed { index, api ->
|
}) {
|
||||||
Pair(
|
try {
|
||||||
index,
|
val i = Intent(Intent.ACTION_VIEW)
|
||||||
api.name
|
i.data = Uri.parse(validAPIs[itemId].mainUrl)
|
||||||
)
|
startActivity(i)
|
||||||
}) {
|
} catch (e: Exception) {
|
||||||
try {
|
logError(e)
|
||||||
val i = Intent(Intent.ACTION_VIEW)
|
}
|
||||||
i.data = Uri.parse(validAPIs[itemId].mainUrl)
|
|
||||||
startActivity(i)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logError(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
homeLoading.isVisible = false
|
||||||
|
homeLoadingError.isVisible = true
|
||||||
|
homeMasterRecycler.isVisible = false
|
||||||
|
//home_loaded?.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
home_loading?.isVisible = false
|
is Resource.Loading -> {
|
||||||
home_loading_error?.isVisible = true
|
(homeMasterRecycler.adapter as? ParentItemAdapter)?.updateList(listOf())
|
||||||
home_master_recycler?.isVisible = false
|
homeLoadingShimmer.startShimmer()
|
||||||
//home_loaded?.isVisible = false
|
homeLoading.isVisible = true
|
||||||
}
|
homeLoadingError.isVisible = false
|
||||||
is Resource.Loading -> {
|
homeMasterRecycler.isVisible = false
|
||||||
(home_master_recycler?.adapter as? ParentItemAdapter)?.updateList(listOf())
|
//home_loaded?.isVisible = false
|
||||||
home_loading_shimmer?.startShimmer()
|
|
||||||
home_loading?.isVisible = true
|
|
||||||
home_loading_error?.isVisible = false
|
|
||||||
home_master_recycler?.isVisible = false
|
|
||||||
//home_loaded?.isVisible = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
observe(homeViewModel.availableWatchStatusTypes) { availableWatchStatusTypes ->
|
|
||||||
context?.setKey(
|
|
||||||
HOME_BOOKMARK_VALUE_LIST,
|
|
||||||
availableWatchStatusTypes.first.map { it.internalId }.toIntArray()
|
|
||||||
)
|
|
||||||
(home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setAvailableWatchStatusTypes(
|
|
||||||
availableWatchStatusTypes
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
observe(homeViewModel.bookmarks) { data ->
|
|
||||||
(home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setBookmarkData(
|
|
||||||
data
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
observe(homeViewModel.resumeWatching) { resumeWatching ->
|
|
||||||
(home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setResumeWatchingData(
|
|
||||||
resumeWatching
|
|
||||||
)
|
|
||||||
if (isTrueTvSettings()) {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
ioSafe {
|
|
||||||
activity?.addProgramsToContinueWatching(resumeWatching.mapNotNull { it as? DataStoreHelper.ResumeWatchingResult })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -665,72 +630,35 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
//context?.fixPaddingStatusbarView(home_statusbar)
|
//context?.fixPaddingStatusbarView(home_statusbar)
|
||||||
//context?.fixPaddingStatusbar(home_padding)
|
//context?.fixPaddingStatusbar(home_padding)
|
||||||
context?.fixPaddingStatusbar(home_loading_statusbar)
|
|
||||||
|
|
||||||
home_master_recycler?.adapter =
|
observeNullable(homeViewModel.popup) { item ->
|
||||||
HomeParentItemAdapterPreview(mutableListOf(), { callback ->
|
if (item == null) {
|
||||||
homeHandleSearch(callback)
|
bottomSheetDialog?.dismissSafe()
|
||||||
}, { item ->
|
bottomSheetDialog = null
|
||||||
bottomSheetDialog = activity?.loadHomepageList(item, expandCallback = {
|
return@observeNullable
|
||||||
homeViewModel.expandAndReturn(it)
|
|
||||||
}, dismissCallback = {
|
|
||||||
bottomSheetDialog = null
|
|
||||||
})
|
|
||||||
}, { name ->
|
|
||||||
homeViewModel.expand(name)
|
|
||||||
}, { load ->
|
|
||||||
activity?.loadResult(load.response.url, load.response.apiName, load.action)
|
|
||||||
}, {
|
|
||||||
homeViewModel.loadMoreHomeScrollResponses()
|
|
||||||
}, {
|
|
||||||
apiChangeClickListener.onClick(it)
|
|
||||||
}, reloadStored = {
|
|
||||||
reloadStored()
|
|
||||||
}, loadStoredData = {
|
|
||||||
homeViewModel.loadStoredData(it)
|
|
||||||
}, { (isQuickSearch, text) ->
|
|
||||||
if (!isQuickSearch) {
|
|
||||||
QuickSearchFragment.pushSearch(
|
|
||||||
activity,
|
|
||||||
text,
|
|
||||||
currentApiName?.let { arrayOf(it) })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
reloadStored()
|
|
||||||
loadHomePage(false)
|
|
||||||
home_master_recycler?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
|
||||||
if (dy > 0) { //check for scroll down
|
|
||||||
home_api_fab?.shrink() // hide
|
|
||||||
home_random?.shrink()
|
|
||||||
} else if (dy < -5) {
|
|
||||||
if (!isTvSettings()) {
|
|
||||||
home_api_fab?.extend() // show
|
|
||||||
home_random?.extend()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
// don't recreate
|
||||||
|
if (bottomSheetDialog != null) {
|
||||||
|
return@observeNullable
|
||||||
|
}
|
||||||
|
|
||||||
|
bottomSheetDialog = activity?.loadHomepageList(item, expandCallback = {
|
||||||
|
homeViewModel.expandAndReturn(it)
|
||||||
|
}, dismissCallback = {
|
||||||
|
homeViewModel.popup(null)
|
||||||
|
bottomSheetDialog = null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
homeViewModel.reloadStored()
|
||||||
|
homeViewModel.loadAndCancel(getKey(USER_SELECTED_HOMEPAGE_API), false)
|
||||||
|
//loadHomePage(false)
|
||||||
|
|
||||||
// nice profile pic on homepage
|
// nice profile pic on homepage
|
||||||
//home_profile_picture_holder?.isVisible = false
|
//home_profile_picture_holder?.isVisible = false
|
||||||
// just in case
|
// just in case
|
||||||
if (isTvSettings()) {
|
|
||||||
home_api_fab?.isVisible = false
|
|
||||||
if (isTrueTvSettings()) {
|
|
||||||
home_change_api_loading?.isVisible = true
|
|
||||||
home_change_api_loading?.isFocusable = true
|
|
||||||
home_change_api_loading?.isFocusableInTouchMode = true
|
|
||||||
}
|
|
||||||
// home_bookmark_select?.isFocusable = true
|
|
||||||
// home_bookmark_select?.isFocusableInTouchMode = true
|
|
||||||
} else {
|
|
||||||
home_api_fab?.isVisible = true
|
|
||||||
home_change_api_loading?.isVisible = false
|
|
||||||
}
|
|
||||||
//TODO READD THIS
|
//TODO READD THIS
|
||||||
/*for (syncApi in OAuth2Apis) {
|
/*for (syncApi in OAuth2Apis) {
|
||||||
val login = syncApi.loginInfo()
|
val login = syncApi.loginInfo()
|
||||||
|
|
|
@ -3,50 +3,19 @@ package com.lagradost.cloudstream3.ui.home
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.view.isGone
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.ListUpdateCallback
|
import androidx.recyclerview.widget.ListUpdateCallback
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.transition.ChangeBounds
|
|
||||||
import androidx.transition.TransitionManager
|
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
|
||||||
import com.google.android.material.chip.Chip
|
|
||||||
import com.google.android.material.chip.ChipDrawable
|
|
||||||
import com.lagradost.cloudstream3.APIHolder.getId
|
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity
|
|
||||||
import com.lagradost.cloudstream3.HomePageList
|
import com.lagradost.cloudstream3.HomePageList
|
||||||
import com.lagradost.cloudstream3.LoadResponse
|
import com.lagradost.cloudstream3.LoadResponse
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.SearchResponse
|
import com.lagradost.cloudstream3.databinding.HomepageParentBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
|
||||||
import com.lagradost.cloudstream3.ui.WatchType
|
|
||||||
import com.lagradost.cloudstream3.ui.result.LinearListLayout
|
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultViewModel2
|
|
||||||
import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
|
|
||||||
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
|
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
|
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable
|
import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView
|
|
||||||
import kotlinx.android.synthetic.main.activity_main_tv.*
|
|
||||||
import kotlinx.android.synthetic.main.activity_main_tv.view.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home.view.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home_head_tv.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home_head_tv.view.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_viewpager
|
|
||||||
import kotlinx.android.synthetic.main.homepage_parent.view.*
|
|
||||||
|
|
||||||
class LoadClickCallback(
|
class LoadClickCallback(
|
||||||
val action: Int = 0,
|
val action: Int = 0,
|
||||||
|
@ -57,17 +26,23 @@ class LoadClickCallback(
|
||||||
|
|
||||||
open class ParentItemAdapter(
|
open class ParentItemAdapter(
|
||||||
private var items: MutableList<HomeViewModel.ExpandableHomepageList>,
|
private var items: MutableList<HomeViewModel.ExpandableHomepageList>,
|
||||||
|
//private val viewModel: HomeViewModel,
|
||||||
private val clickCallback: (SearchClickCallback) -> Unit,
|
private val clickCallback: (SearchClickCallback) -> Unit,
|
||||||
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
|
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
|
||||||
private val expandCallback: ((String) -> Unit)? = null,
|
private val expandCallback: ((String) -> Unit)? = null,
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
val root = LayoutInflater.from(parent.context).inflate(
|
||||||
|
if (isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent,
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
val binding = HomepageParentBinding.bind(root)
|
||||||
|
|
||||||
return ParentViewHolder(
|
return ParentViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(
|
binding,
|
||||||
if (isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent,
|
|
||||||
parent,
|
|
||||||
false
|
|
||||||
),
|
|
||||||
clickCallback,
|
clickCallback,
|
||||||
moreInfoClickCallback,
|
moreInfoClickCallback,
|
||||||
expandCallback
|
expandCallback
|
||||||
|
@ -178,14 +153,15 @@ open class ParentItemAdapter(
|
||||||
|
|
||||||
class ParentViewHolder
|
class ParentViewHolder
|
||||||
constructor(
|
constructor(
|
||||||
itemView: View,
|
val binding: HomepageParentBinding,
|
||||||
|
// val viewModel: HomeViewModel,
|
||||||
private val clickCallback: (SearchClickCallback) -> Unit,
|
private val clickCallback: (SearchClickCallback) -> Unit,
|
||||||
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
|
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
|
||||||
private val expandCallback: ((String) -> Unit)? = null,
|
private val expandCallback: ((String) -> Unit)? = null,
|
||||||
) :
|
) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
val title: TextView = itemView.home_child_more_info
|
val title: TextView = binding.homeChildMoreInfo
|
||||||
private val recyclerView: RecyclerView = itemView.home_child_recyclerview
|
private val recyclerView: RecyclerView = binding.homeChildRecyclerview
|
||||||
|
|
||||||
fun update(expand: HomeViewModel.ExpandableHomepageList) {
|
fun update(expand: HomeViewModel.ExpandableHomepageList) {
|
||||||
val info = expand.list
|
val info = expand.list
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,24 +2,18 @@ package com.lagradost.cloudstream3.ui.home
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.annotation.LayoutRes
|
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.lagradost.cloudstream3.LoadResponse
|
import com.lagradost.cloudstream3.LoadResponse
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.databinding.HomeScrollViewBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.HomeScrollViewTvBinding
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import kotlinx.android.synthetic.main.fragment_home_head_tv.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_home_head_tv.view.*
|
|
||||||
import kotlinx.android.synthetic.main.home_scroll_view.view.*
|
|
||||||
|
|
||||||
|
class HomeScrollAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
class HomeScrollAdapter(
|
|
||||||
@LayoutRes val layout: Int = R.layout.home_scroll_view,
|
|
||||||
private val forceHorizontalPosters: Boolean? = null
|
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
||||||
private var items: MutableList<LoadResponse> = mutableListOf()
|
private var items: MutableList<LoadResponse> = mutableListOf()
|
||||||
var hasMoreItems: Boolean = false
|
var hasMoreItems: Boolean = false
|
||||||
|
|
||||||
|
@ -45,9 +39,16 @@ class HomeScrollAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
val binding = if (isTvSettings()) {
|
||||||
|
HomeScrollViewTvBinding.inflate(inflater, parent, false)
|
||||||
|
} else {
|
||||||
|
HomeScrollViewBinding.inflate(inflater, parent, false)
|
||||||
|
}
|
||||||
|
|
||||||
return CardViewHolder(
|
return CardViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(layout, parent, false),
|
binding,
|
||||||
forceHorizontalPosters
|
//forceHorizontalPosters
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,22 +62,32 @@ class HomeScrollAdapter(
|
||||||
|
|
||||||
class CardViewHolder
|
class CardViewHolder
|
||||||
constructor(
|
constructor(
|
||||||
itemView: View,
|
val binding: ViewBinding,
|
||||||
private val forceHorizontalPosters: Boolean? = null
|
//private val forceHorizontalPosters: Boolean? = null
|
||||||
) :
|
) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
fun bind(card: LoadResponse) {
|
fun bind(card: LoadResponse) {
|
||||||
card.apply {
|
val isHorizontal =
|
||||||
val isHorizontal =
|
binding is HomeScrollViewTvBinding || itemView.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||||
(forceHorizontalPosters == true) || ((forceHorizontalPosters != false) && itemView.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
|
||||||
|
|
||||||
val posterUrl = if (isHorizontal) backgroundPosterUrl ?: posterUrl else posterUrl
|
val posterUrl =
|
||||||
?: backgroundPosterUrl
|
if (isHorizontal) card.backgroundPosterUrl ?: card.posterUrl else card.posterUrl
|
||||||
itemView.home_scroll_preview_tags?.text = tags?.joinToString(" • ") ?: ""
|
?: card.backgroundPosterUrl
|
||||||
itemView.home_scroll_preview_tags?.isGone = tags.isNullOrEmpty()
|
|
||||||
itemView.home_scroll_preview?.setImage(posterUrl, posterHeaders)
|
when (binding) {
|
||||||
itemView.home_scroll_preview_title?.text = name
|
is HomeScrollViewBinding -> {
|
||||||
|
binding.homeScrollPreview.setImage(posterUrl)
|
||||||
|
binding.homeScrollPreviewTags.apply {
|
||||||
|
text = card.tags?.joinToString(" • ") ?: ""
|
||||||
|
isGone = card.tags.isNullOrEmpty()
|
||||||
|
}
|
||||||
|
binding.homeScrollPreviewTitle.text = card.name
|
||||||
|
}
|
||||||
|
|
||||||
|
is HomeScrollViewTvBinding -> {
|
||||||
|
binding.homeScrollPreview.setImage(posterUrl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package com.lagradost.cloudstream3.ui.home
|
package com.lagradost.cloudstream3.ui.home
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.lagradost.cloudstream3.*
|
|
||||||
import com.lagradost.cloudstream3.APIHolder.apis
|
import com.lagradost.cloudstream3.APIHolder.apis
|
||||||
import com.lagradost.cloudstream3.APIHolder.filterHomePageListByFilmQuality
|
import com.lagradost.cloudstream3.APIHolder.filterHomePageListByFilmQuality
|
||||||
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
|
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
|
||||||
|
@ -13,11 +13,30 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.context
|
import com.lagradost.cloudstream3.AcraApplication.Companion.context
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
import com.lagradost.cloudstream3.mvvm.*
|
import com.lagradost.cloudstream3.CommonActivity.activity
|
||||||
|
import com.lagradost.cloudstream3.HomePageList
|
||||||
|
import com.lagradost.cloudstream3.LoadResponse
|
||||||
|
import com.lagradost.cloudstream3.MainAPI
|
||||||
|
import com.lagradost.cloudstream3.MainActivity
|
||||||
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
|
import com.lagradost.cloudstream3.amap
|
||||||
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
|
import com.lagradost.cloudstream3.mvvm.debugAssert
|
||||||
|
import com.lagradost.cloudstream3.mvvm.debugWarning
|
||||||
|
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||||
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
|
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
|
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
|
||||||
import com.lagradost.cloudstream3.ui.WatchType
|
import com.lagradost.cloudstream3.ui.WatchType
|
||||||
|
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
|
||||||
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED
|
||||||
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
|
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.addProgramsToContinueWatching
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
|
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
|
@ -32,7 +51,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.*
|
import java.util.EnumSet
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
|
||||||
class HomeViewModel : ViewModel() {
|
class HomeViewModel : ViewModel() {
|
||||||
|
@ -72,7 +91,7 @@ class HomeViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var repo: APIRepository? = null
|
var repo: APIRepository? = null
|
||||||
|
|
||||||
private val _apiName = MutableLiveData<String>()
|
private val _apiName = MutableLiveData<String>()
|
||||||
val apiName: LiveData<String> = _apiName
|
val apiName: LiveData<String> = _apiName
|
||||||
|
@ -83,7 +102,7 @@ class HomeViewModel : ViewModel() {
|
||||||
private var currentShuffledList: List<SearchResponse> = listOf()
|
private var currentShuffledList: List<SearchResponse> = listOf()
|
||||||
|
|
||||||
private fun autoloadRepo(): APIRepository {
|
private fun autoloadRepo(): APIRepository {
|
||||||
return APIRepository(apis.first { it.hasMainPage })
|
return APIRepository(synchronized(apis) { apis.first { it.hasMainPage }})
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _availableWatchStatusTypes =
|
private val _availableWatchStatusTypes =
|
||||||
|
@ -101,8 +120,14 @@ class HomeViewModel : ViewModel() {
|
||||||
val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching
|
val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching
|
||||||
val preview: LiveData<Resource<Pair<Boolean, List<LoadResponse>>>> = _preview
|
val preview: LiveData<Resource<Pair<Boolean, List<LoadResponse>>>> = _preview
|
||||||
|
|
||||||
fun loadResumeWatching() = viewModelScope.launchSafe {
|
private fun loadResumeWatching() = viewModelScope.launchSafe {
|
||||||
val resumeWatchingResult = getResumeWatching()
|
val resumeWatchingResult = getResumeWatching()
|
||||||
|
if (isTrueTvSettings() && resumeWatchingResult != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
ioSafe {
|
||||||
|
// this WILL crash on non tvs, so keep this inside a try catch
|
||||||
|
activity?.addProgramsToContinueWatching(resumeWatchingResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
resumeWatchingResult?.let {
|
resumeWatchingResult?.let {
|
||||||
_resumeWatching.postValue(it)
|
_resumeWatching.postValue(it)
|
||||||
}
|
}
|
||||||
|
@ -128,6 +153,10 @@ class HomeViewModel : ViewModel() {
|
||||||
currentWatchTypes.remove(WatchType.NONE)
|
currentWatchTypes.remove(WatchType.NONE)
|
||||||
|
|
||||||
if (currentWatchTypes.size <= 0) {
|
if (currentWatchTypes.size <= 0) {
|
||||||
|
setKey(
|
||||||
|
HOME_BOOKMARK_VALUE_LIST,
|
||||||
|
intArrayOf()
|
||||||
|
)
|
||||||
_availableWatchStatusTypes.postValue(setOf<WatchType>() to setOf())
|
_availableWatchStatusTypes.postValue(setOf<WatchType>() to setOf())
|
||||||
_bookmarks.postValue(Pair(false, ArrayList()))
|
_bookmarks.postValue(Pair(false, ArrayList()))
|
||||||
return@launchSafe
|
return@launchSafe
|
||||||
|
@ -135,7 +164,10 @@ class HomeViewModel : ViewModel() {
|
||||||
|
|
||||||
val watchPrefNotNull = preferredWatchStatus ?: EnumSet.of(currentWatchTypes.first())
|
val watchPrefNotNull = preferredWatchStatus ?: EnumSet.of(currentWatchTypes.first())
|
||||||
//if (currentWatchTypes.any { watchPrefNotNull.contains(it) }) watchPrefNotNull else listOf(currentWatchTypes.first())
|
//if (currentWatchTypes.any { watchPrefNotNull.contains(it) }) watchPrefNotNull else listOf(currentWatchTypes.first())
|
||||||
|
setKey(
|
||||||
|
HOME_BOOKMARK_VALUE_LIST,
|
||||||
|
watchPrefNotNull.map { it.internalId }.toIntArray()
|
||||||
|
)
|
||||||
_availableWatchStatusTypes.postValue(
|
_availableWatchStatusTypes.postValue(
|
||||||
Pair(
|
Pair(
|
||||||
watchPrefNotNull,
|
watchPrefNotNull,
|
||||||
|
@ -152,8 +184,10 @@ class HomeViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var onGoingLoad: Job? = null
|
private var onGoingLoad: Job? = null
|
||||||
private fun loadAndCancel(api: MainAPI?) {
|
private var isCurrentlyLoadingName : String? = null
|
||||||
|
private fun loadAndCancel(api: MainAPI) {
|
||||||
onGoingLoad?.cancel()
|
onGoingLoad?.cancel()
|
||||||
|
isCurrentlyLoadingName = api.name
|
||||||
onGoingLoad = load(api)
|
onGoingLoad = load(api)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,12 +289,12 @@ class HomeViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun load(api: MainAPI?) = ioSafe {
|
private fun load(api: MainAPI) : Job = ioSafe {
|
||||||
repo = if (api != null) {
|
repo = //if (api != null) {
|
||||||
APIRepository(api)
|
APIRepository(api)
|
||||||
} else {
|
//} else {
|
||||||
autoloadRepo()
|
// autoloadRepo()
|
||||||
}
|
//}
|
||||||
|
|
||||||
_apiName.postValue(repo?.name)
|
_apiName.postValue(repo?.name)
|
||||||
_randomItems.postValue(listOf())
|
_randomItems.postValue(listOf())
|
||||||
|
@ -274,6 +308,7 @@ class HomeViewModel : ViewModel() {
|
||||||
|
|
||||||
_page.postValue(Resource.Loading())
|
_page.postValue(Resource.Loading())
|
||||||
_preview.postValue(Resource.Loading())
|
_preview.postValue(Resource.Loading())
|
||||||
|
// cancel the current preview expand as that is no longer relevant
|
||||||
addJob?.cancel()
|
addJob?.cancel()
|
||||||
|
|
||||||
when (val data = repo?.getMainPage(1, null)) {
|
when (val data = repo?.getMainPage(1, null)) {
|
||||||
|
@ -337,41 +372,126 @@ class HomeViewModel : ViewModel() {
|
||||||
logError(e)
|
logError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Failure -> {
|
is Resource.Failure -> {
|
||||||
_page.postValue(data!!)
|
_page.postValue(data!!)
|
||||||
_preview.postValue(data!!)
|
_preview.postValue(data!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
|
isCurrentlyLoadingName = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun click(callback: SearchClickCallback) {
|
||||||
|
if (callback.action == SEARCH_ACTION_FOCUSED) {
|
||||||
|
//focusCallback(callback.card)
|
||||||
|
} else {
|
||||||
|
SearchHelper.handleSearchClickCallback(callback)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadAndCancel(preferredApiName: String?, forceReload: Boolean = true) =
|
|
||||||
viewModelScope.launchSafe {
|
private val _popup = MutableLiveData<ExpandableHomepageList?>(null)
|
||||||
|
val popup: LiveData<ExpandableHomepageList?> = _popup
|
||||||
|
|
||||||
|
fun popup(list: ExpandableHomepageList?) {
|
||||||
|
_popup.postValue(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bookmarksUpdated(unused: Boolean) {
|
||||||
|
reloadStored()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun afterPluginsLoaded(forceReload: Boolean) {
|
||||||
|
loadAndCancel(getKey(USER_SELECTED_HOMEPAGE_API), forceReload)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun afterMainPluginsLoaded(unused: Boolean = false) {
|
||||||
|
loadAndCancel(getKey(USER_SELECTED_HOMEPAGE_API), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
MainActivity.bookmarksUpdatedEvent += ::bookmarksUpdated
|
||||||
|
MainActivity.afterPluginsLoadedEvent += ::afterPluginsLoaded
|
||||||
|
MainActivity.mainPluginsLoadedEvent += ::afterMainPluginsLoaded
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
MainActivity.bookmarksUpdatedEvent -= ::bookmarksUpdated
|
||||||
|
MainActivity.afterPluginsLoadedEvent -= ::afterPluginsLoaded
|
||||||
|
MainActivity.mainPluginsLoadedEvent -= ::afterMainPluginsLoaded
|
||||||
|
super.onCleared()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun queryTextSubmit(query: String) {
|
||||||
|
QuickSearchFragment.pushSearch(
|
||||||
|
query,
|
||||||
|
repo?.name?.let { arrayOf(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun queryTextChange(newText: String) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reloadStored() {
|
||||||
|
loadResumeWatching()
|
||||||
|
val list = EnumSet.noneOf(WatchType::class.java)
|
||||||
|
getKey<IntArray>(HOME_BOOKMARK_VALUE_LIST)?.map { WatchType.fromInternalId(it) }?.let {
|
||||||
|
list.addAll(it)
|
||||||
|
}
|
||||||
|
loadStoredData(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun click(load: LoadClickCallback) {
|
||||||
|
loadResult(load.response.url, load.response.apiName, load.action)
|
||||||
|
}
|
||||||
|
|
||||||
|
// only save the key if it is from UI, as we don't want internal functions changing the setting
|
||||||
|
fun loadAndCancel(
|
||||||
|
preferredApiName: String?,
|
||||||
|
forceReload: Boolean = true,
|
||||||
|
fromUI: Boolean = false
|
||||||
|
) =
|
||||||
|
ioSafe {
|
||||||
// Since plugins are loaded in stages this function can get called multiple times.
|
// Since plugins are loaded in stages this function can get called multiple times.
|
||||||
// The issue with this is that the homepage may be fetched multiple times while the first request is loading
|
// The issue with this is that the homepage may be fetched multiple times while the first request is loading
|
||||||
val api = getApiFromNameNull(preferredApiName)
|
val api = getApiFromNameNull(preferredApiName)
|
||||||
if (!forceReload && api?.let { expandable[it.name]?.list?.list?.isNotEmpty() } == true) {
|
// api?.let { expandable[it.name]?.list?.list?.isNotEmpty() } == true
|
||||||
return@launchSafe
|
val currentPage = page.value
|
||||||
|
|
||||||
|
// if we don't need to reload and we have a valid homepage or currently loading the same thing then return
|
||||||
|
val currentLoading = isCurrentlyLoadingName
|
||||||
|
if (!forceReload && (currentPage is Resource.Success && currentPage.value.isNotEmpty() || (currentLoading != null && currentLoading == preferredApiName))) {
|
||||||
|
return@ioSafe
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preferredApiName == noneApi.name) {
|
if (preferredApiName == noneApi.name) {
|
||||||
setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name)
|
// just set to random
|
||||||
|
if (fromUI) setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name)
|
||||||
loadAndCancel(noneApi)
|
loadAndCancel(noneApi)
|
||||||
} else if (preferredApiName == randomApi.name) {
|
} else if (preferredApiName == randomApi.name) {
|
||||||
|
// randomize the api, if none exist like if not loaded or not installed
|
||||||
|
// then use nothing
|
||||||
val validAPIs = context?.filterProviderByPreferredMedia()
|
val validAPIs = context?.filterProviderByPreferredMedia()
|
||||||
if (validAPIs.isNullOrEmpty()) {
|
if (validAPIs.isNullOrEmpty()) {
|
||||||
// Do not set USER_SELECTED_HOMEPAGE_API when there is no plugins loaded
|
|
||||||
loadAndCancel(noneApi)
|
loadAndCancel(noneApi)
|
||||||
} else {
|
} else {
|
||||||
val apiRandom = validAPIs.random()
|
val apiRandom = validAPIs.random()
|
||||||
loadAndCancel(apiRandom)
|
loadAndCancel(apiRandom)
|
||||||
setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name)
|
if (fromUI) setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name)
|
||||||
}
|
}
|
||||||
// If the plugin isn't loaded yet. (Does not set the key)
|
|
||||||
} else if (api == null) {
|
} else if (api == null) {
|
||||||
loadAndCancel(noneApi)
|
// API is not found aka not loaded or removed, post the loading
|
||||||
|
// progress if waiting for plugins, otherwise nothing
|
||||||
|
if(PluginManager.loadedLocalPlugins) {
|
||||||
|
loadAndCancel(noneApi)
|
||||||
|
} else {
|
||||||
|
_page.postValue(Resource.Loading())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setKey(USER_SELECTED_HOMEPAGE_API, api.name)
|
// if the api is found, then set it to it and save key
|
||||||
|
if (fromUI) setKey(USER_SELECTED_HOMEPAGE_API, api.name)
|
||||||
loadAndCancel(api)
|
loadAndCancel(api)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
@ -14,6 +13,7 @@ import android.view.animation.AlphaAnimation
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.appcompat.widget.SearchView
|
import androidx.appcompat.widget.SearchView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import com.lagradost.cloudstream3.APIHolder
|
import com.lagradost.cloudstream3.APIHolder
|
||||||
|
@ -22,6 +22,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentLibraryBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.debugAssert
|
import com.lagradost.cloudstream3.mvvm.debugAssert
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
|
@ -37,7 +38,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.reduceDragSensitivity
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||||
import kotlinx.android.synthetic.main.fragment_library.*
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
const val LIBRARY_FOLDER = "library_folder"
|
const val LIBRARY_FOLDER = "library_folder"
|
||||||
|
@ -73,14 +73,25 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
private val libraryViewModel: LibraryViewModel by activityViewModels()
|
||||||
|
|
||||||
|
var binding: FragmentLibraryBinding? = null
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
return inflater.inflate(R.layout.fragment_library, container, false)
|
val localBinding = FragmentLibraryBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root
|
||||||
|
|
||||||
|
//return inflater.inflate(R.layout.fragment_library, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
viewpager?.currentItem?.let { currentItem ->
|
binding?.viewpager?.currentItem?.let { currentItem ->
|
||||||
outState.putInt(VIEWPAGER_ITEM_KEY, currentItem)
|
outState.putInt(VIEWPAGER_ITEM_KEY, currentItem)
|
||||||
}
|
}
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
|
@ -88,9 +99,9 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
context?.fixPaddingStatusbar(search_status_bar_padding)
|
fixPaddingStatusbar(binding?.searchStatusBarPadding)
|
||||||
|
|
||||||
sort_fab?.setOnClickListener {
|
binding?.sortFab?.setOnClickListener {
|
||||||
val methods = libraryViewModel.sortingMethods.map {
|
val methods = libraryViewModel.sortingMethods.map {
|
||||||
txt(it.stringRes).asString(view.context)
|
txt(it.stringRes).asString(view.context)
|
||||||
}
|
}
|
||||||
|
@ -106,7 +117,7 @@ class LibraryFragment : Fragment() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
main_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
binding?.mainSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||||
libraryViewModel.sort(ListSorting.Query, query)
|
libraryViewModel.sort(ListSorting.Query, query)
|
||||||
return true
|
return true
|
||||||
|
@ -129,7 +140,7 @@ class LibraryFragment : Fragment() {
|
||||||
|
|
||||||
libraryViewModel.reloadPages(false)
|
libraryViewModel.reloadPages(false)
|
||||||
|
|
||||||
list_selector?.setOnClickListener {
|
binding?.listSelector?.setOnClickListener {
|
||||||
val items = libraryViewModel.availableApiNames
|
val items = libraryViewModel.availableApiNames
|
||||||
val currentItem = libraryViewModel.currentApiName.value
|
val currentItem = libraryViewModel.currentApiName.value
|
||||||
|
|
||||||
|
@ -152,12 +163,14 @@ class LibraryFragment : Fragment() {
|
||||||
syncId: SyncIdName,
|
syncId: SyncIdName,
|
||||||
apiName: String? = null,
|
apiName: String? = null,
|
||||||
) {
|
) {
|
||||||
val availableProviders = allProviders.filter {
|
val availableProviders = synchronized(allProviders) {
|
||||||
it.supportedSyncNames.contains(syncId)
|
allProviders.filter {
|
||||||
}.map { it.name } +
|
it.supportedSyncNames.contains(syncId)
|
||||||
// Add the api if it exists
|
}.map { it.name } +
|
||||||
(APIHolder.getApiFromNameNull(apiName)?.let { listOf(it.name) } ?: emptyList())
|
// Add the api if it exists
|
||||||
|
(APIHolder.getApiFromNameNull(apiName)?.let { listOf(it.name) }
|
||||||
|
?: emptyList())
|
||||||
|
}
|
||||||
val baseOptions = listOf(
|
val baseOptions = listOf(
|
||||||
LibraryOpenerType.Default,
|
LibraryOpenerType.Default,
|
||||||
LibraryOpenerType.None,
|
LibraryOpenerType.None,
|
||||||
|
@ -209,20 +222,22 @@ class LibraryFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
provider_selector?.setOnClickListener {
|
binding?.providerSelector?.setOnClickListener {
|
||||||
val syncName = libraryViewModel.currentSyncApi?.syncIdName ?: return@setOnClickListener
|
val syncName = libraryViewModel.currentSyncApi?.syncIdName ?: return@setOnClickListener
|
||||||
activity?.showPluginSelectionDialog(syncName.name, syncName)
|
activity?.showPluginSelectionDialog(syncName.name, syncName)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewpager?.setPageTransformer(LibraryScrollTransformer())
|
binding?.viewpager?.setPageTransformer(LibraryScrollTransformer())
|
||||||
viewpager?.adapter =
|
binding?.viewpager?.adapter =
|
||||||
viewpager.adapter ?: ViewpagerAdapter(mutableListOf(), { isScrollingDown: Boolean ->
|
binding?.viewpager?.adapter ?: ViewpagerAdapter(
|
||||||
if (isScrollingDown) {
|
mutableListOf(),
|
||||||
sort_fab?.shrink()
|
{ isScrollingDown: Boolean ->
|
||||||
} else {
|
if (isScrollingDown) {
|
||||||
sort_fab?.extend()
|
binding?.sortFab?.shrink()
|
||||||
}
|
} else {
|
||||||
}) callback@{ searchClickCallback ->
|
binding?.sortFab?.extend()
|
||||||
|
}
|
||||||
|
}) callback@{ searchClickCallback ->
|
||||||
// To prevent future accidents
|
// To prevent future accidents
|
||||||
debugAssert({
|
debugAssert({
|
||||||
searchClickCallback.card !is SyncAPI.LibraryItem
|
searchClickCallback.card !is SyncAPI.LibraryItem
|
||||||
|
@ -267,6 +282,7 @@ class LibraryFragment : Fragment() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LibraryOpenerType.None -> {}
|
LibraryOpenerType.None -> {}
|
||||||
LibraryOpenerType.Provider ->
|
LibraryOpenerType.Provider ->
|
||||||
savedSelection.providerData?.apiName?.let { apiName ->
|
savedSelection.providerData?.apiName?.let { apiName ->
|
||||||
|
@ -275,8 +291,10 @@ class LibraryFragment : Fragment() {
|
||||||
apiName,
|
apiName,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LibraryOpenerType.Browser ->
|
LibraryOpenerType.Browser ->
|
||||||
openBrowser(searchClickCallback.card.url)
|
openBrowser(searchClickCallback.card.url)
|
||||||
|
|
||||||
LibraryOpenerType.Search -> {
|
LibraryOpenerType.Search -> {
|
||||||
QuickSearchFragment.pushSearch(
|
QuickSearchFragment.pushSearch(
|
||||||
activity,
|
activity,
|
||||||
|
@ -288,22 +306,28 @@ class LibraryFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewpager?.offscreenPageLimit = 2
|
binding?.apply {
|
||||||
viewpager?.reduceDragSensitivity()
|
viewpager.offscreenPageLimit = 2
|
||||||
|
viewpager.reduceDragSensitivity()
|
||||||
|
}
|
||||||
|
|
||||||
val startLoading = Runnable {
|
val startLoading = Runnable {
|
||||||
gridview?.numColumns = context?.getSpanCount() ?: 3
|
binding?.apply {
|
||||||
gridview?.adapter =
|
gridview.numColumns = context?.getSpanCount() ?: 3
|
||||||
context?.let { LoadingPosterAdapter(it, 6 * 3) }
|
gridview.adapter =
|
||||||
library_loading_overlay?.isVisible = true
|
context?.let { LoadingPosterAdapter(it, 6 * 3) }
|
||||||
library_loading_shimmer?.startShimmer()
|
libraryLoadingOverlay.isVisible = true
|
||||||
empty_list_textview?.isVisible = false
|
libraryLoadingShimmer.startShimmer()
|
||||||
|
emptyListTextview.isVisible = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val stopLoading = Runnable {
|
val stopLoading = Runnable {
|
||||||
gridview?.adapter = null
|
binding?.apply {
|
||||||
library_loading_overlay?.isVisible = false
|
gridview.adapter = null
|
||||||
library_loading_shimmer?.stopShimmer()
|
libraryLoadingOverlay.isVisible = false
|
||||||
|
libraryLoadingShimmer.stopShimmer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val handler = Handler(Looper.getMainLooper())
|
val handler = Handler(Looper.getMainLooper())
|
||||||
|
@ -314,65 +338,75 @@ class LibraryFragment : Fragment() {
|
||||||
handler.removeCallbacks(startLoading)
|
handler.removeCallbacks(startLoading)
|
||||||
val pages = resource.value
|
val pages = resource.value
|
||||||
val showNotice = pages.all { it.items.isEmpty() }
|
val showNotice = pages.all { it.items.isEmpty() }
|
||||||
empty_list_textview?.isVisible = showNotice
|
|
||||||
if (showNotice) {
|
|
||||||
if (libraryViewModel.availableApiNames.size > 1) {
|
binding?.apply {
|
||||||
empty_list_textview?.setText(R.string.empty_library_logged_in_message)
|
emptyListTextview.isVisible = showNotice
|
||||||
} else {
|
if (showNotice) {
|
||||||
empty_list_textview?.setText(R.string.empty_library_no_accounts_message)
|
if (libraryViewModel.availableApiNames.size > 1) {
|
||||||
|
emptyListTextview.setText(R.string.empty_library_logged_in_message)
|
||||||
|
} else {
|
||||||
|
emptyListTextview.setText(R.string.empty_library_no_accounts_message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(viewpager.adapter as? ViewpagerAdapter)?.pages = pages
|
||||||
|
// Using notifyItemRangeChanged keeps the animations when sorting
|
||||||
|
viewpager.adapter?.notifyItemRangeChanged(
|
||||||
|
0,
|
||||||
|
viewpager.adapter?.itemCount ?: 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// Only stop loading after 300ms to hide the fade effect the viewpager produces when updating
|
||||||
|
// Without this there would be a flashing effect:
|
||||||
|
// loading -> show old viewpager -> black screen -> show new viewpager
|
||||||
|
handler.postDelayed(stopLoading, 300)
|
||||||
|
|
||||||
|
savedInstanceState?.getInt(VIEWPAGER_ITEM_KEY)?.let { currentPos ->
|
||||||
|
if (currentPos < 0) return@let
|
||||||
|
viewpager.setCurrentItem(currentPos, false)
|
||||||
|
// Using remove() sets the key to 0 instead of removing it
|
||||||
|
savedInstanceState.putInt(VIEWPAGER_ITEM_KEY, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the animation to scroll multiple items is so much its better to just hide
|
||||||
|
// the viewpager a bit while the fastest animation is running
|
||||||
|
fun hideViewpager(distance: Int) {
|
||||||
|
if (distance < 3) return
|
||||||
|
|
||||||
|
val hideAnimation = AlphaAnimation(1f, 0f).apply {
|
||||||
|
duration = distance * 50L
|
||||||
|
fillAfter = true
|
||||||
|
}
|
||||||
|
val showAnimation = AlphaAnimation(0f, 1f).apply {
|
||||||
|
duration = distance * 50L
|
||||||
|
startOffset = distance * 100L
|
||||||
|
fillAfter = true
|
||||||
|
}
|
||||||
|
viewpager.startAnimation(hideAnimation)
|
||||||
|
viewpager.startAnimation(showAnimation)
|
||||||
|
}
|
||||||
|
|
||||||
|
TabLayoutMediator(
|
||||||
|
libraryTabLayout,
|
||||||
|
viewpager,
|
||||||
|
) { tab, position ->
|
||||||
|
tab.text = pages.getOrNull(position)?.title?.asStringNull(context)
|
||||||
|
tab.view.setOnClickListener {
|
||||||
|
val currentItem =
|
||||||
|
binding?.viewpager?.currentItem ?: return@setOnClickListener
|
||||||
|
val distance = abs(position - currentItem)
|
||||||
|
hideViewpager(distance)
|
||||||
|
}
|
||||||
|
}.attach()
|
||||||
}
|
}
|
||||||
|
|
||||||
(viewpager.adapter as? ViewpagerAdapter)?.pages = pages
|
|
||||||
// Using notifyItemRangeChanged keeps the animations when sorting
|
|
||||||
viewpager.adapter?.notifyItemRangeChanged(0, viewpager.adapter?.itemCount ?: 0)
|
|
||||||
|
|
||||||
// Only stop loading after 300ms to hide the fade effect the viewpager produces when updating
|
|
||||||
// Without this there would be a flashing effect:
|
|
||||||
// loading -> show old viewpager -> black screen -> show new viewpager
|
|
||||||
handler.postDelayed(stopLoading, 300)
|
|
||||||
|
|
||||||
savedInstanceState?.getInt(VIEWPAGER_ITEM_KEY)?.let { currentPos ->
|
|
||||||
if (currentPos < 0) return@let
|
|
||||||
viewpager?.setCurrentItem(currentPos, false)
|
|
||||||
// Using remove() sets the key to 0 instead of removing it
|
|
||||||
savedInstanceState.putInt(VIEWPAGER_ITEM_KEY, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since the animation to scroll multiple items is so much its better to just hide
|
|
||||||
// the viewpager a bit while the fastest animation is running
|
|
||||||
fun hideViewpager(distance: Int) {
|
|
||||||
if (distance < 3) return
|
|
||||||
|
|
||||||
val hideAnimation = AlphaAnimation(1f, 0f).apply {
|
|
||||||
duration = distance * 50L
|
|
||||||
fillAfter = true
|
|
||||||
}
|
|
||||||
val showAnimation = AlphaAnimation(0f, 1f).apply {
|
|
||||||
duration = distance * 50L
|
|
||||||
startOffset = distance * 100L
|
|
||||||
fillAfter = true
|
|
||||||
}
|
|
||||||
viewpager?.startAnimation(hideAnimation)
|
|
||||||
viewpager?.startAnimation(showAnimation)
|
|
||||||
}
|
|
||||||
|
|
||||||
TabLayoutMediator(
|
|
||||||
library_tab_layout,
|
|
||||||
viewpager,
|
|
||||||
) { tab, position ->
|
|
||||||
tab.text = pages.getOrNull(position)?.title?.asStringNull(context)
|
|
||||||
tab.view.setOnClickListener {
|
|
||||||
val currentItem = viewpager?.currentItem ?: return@setOnClickListener
|
|
||||||
val distance = abs(position - currentItem)
|
|
||||||
hideViewpager(distance)
|
|
||||||
}
|
|
||||||
}.attach()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Loading -> {
|
is Resource.Loading -> {
|
||||||
// Only start loading after 200ms to prevent loading cached lists
|
// Only start loading after 200ms to prevent loading cached lists
|
||||||
handler.postDelayed(startLoading, 200)
|
handler.postDelayed(startLoading, 200)
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Failure -> {
|
is Resource.Failure -> {
|
||||||
stopLoading.run()
|
stopLoading.run()
|
||||||
// No user indication it failed :(
|
// No user indication it failed :(
|
||||||
|
@ -383,7 +417,7 @@ class LibraryFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
(viewpager.adapter as? ViewpagerAdapter)?.rebind()
|
(binding?.viewpager?.adapter as? ViewpagerAdapter)?.rebind()
|
||||||
super.onConfigurationChanged(newConfig)
|
super.onConfigurationChanged(newConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,13 @@ package com.lagradost.cloudstream3.ui.library
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
import kotlinx.android.synthetic.main.library_viewpager_page.view.*
|
import com.lagradost.cloudstream3.R
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class LibraryScrollTransformer : ViewPager2.PageTransformer {
|
class LibraryScrollTransformer : ViewPager2.PageTransformer {
|
||||||
override fun transformPage(page: View, position: Float) {
|
override fun transformPage(page: View, position: Float) {
|
||||||
val padding = (-position * page.width).roundToInt()
|
val padding = (-position * page.width).roundToInt()
|
||||||
page.page_recyclerview.setPadding(
|
page.findViewById<View>(R.id.page_recyclerview).setPadding(
|
||||||
padding, 0,
|
padding, 0,
|
||||||
-padding, 0
|
-padding, 0
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,7 +11,6 @@ import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
|
|
||||||
enum class ListSorting(@StringRes val stringRes: Int) {
|
enum class ListSorting(@StringRes val stringRes: Int) {
|
||||||
Query(R.string.none),
|
Query(R.string.none),
|
||||||
|
|
|
@ -5,15 +5,7 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.BaseAdapter
|
import android.widget.BaseAdapter
|
||||||
import android.widget.FrameLayout
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.ListPopupWindow.MATCH_PARENT
|
|
||||||
import android.widget.RelativeLayout
|
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
|
||||||
import kotlinx.android.synthetic.main.loading_poster_dynamic.view.*
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
import kotlin.math.sqrt
|
|
||||||
|
|
||||||
class LoadingPosterAdapter(context: Context, private val itemCount: Int) :
|
class LoadingPosterAdapter(context: Context, private val itemCount: Int) :
|
||||||
BaseAdapter() {
|
BaseAdapter() {
|
||||||
|
|
|
@ -3,23 +3,21 @@ package com.lagradost.cloudstream3.ui.library
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.ImageView
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.AcraApplication
|
import com.lagradost.cloudstream3.AcraApplication
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.SearchResultGridExpandedBinding
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils
|
import com.lagradost.cloudstream3.utils.AppUtils
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
import kotlinx.android.synthetic.main.search_result_grid_expanded.view.*
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,8 +30,11 @@ class PageAdapter(
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return LibraryItemViewHolder(
|
return LibraryItemViewHolder(
|
||||||
LayoutInflater.from(parent.context)
|
SearchResultGridExpandedBinding.inflate(
|
||||||
.inflate(R.layout.search_result_grid_expanded, parent, false)
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,8 +58,8 @@ class PageAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class LibraryItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
inner class LibraryItemViewHolder(val binding: SearchResultGridExpandedBinding) :
|
||||||
val cardView: ImageView = itemView.imageView
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
private val compactView = false//itemView.context.getGridIsCompact()
|
private val compactView = false//itemView.context.getGridIsCompact()
|
||||||
private val coverHeight: Int =
|
private val coverHeight: Int =
|
||||||
|
@ -85,11 +86,12 @@ class PageAdapter(
|
||||||
|
|
||||||
val fg =
|
val fg =
|
||||||
getDifferentColor(bg)//palette.getVibrantColor(ContextCompat.getColor(ctx,R.color.ratingColor))
|
getDifferentColor(bg)//palette.getVibrantColor(ContextCompat.getColor(ctx,R.color.ratingColor))
|
||||||
itemView.text_rating.apply {
|
binding.textRating.apply {
|
||||||
setTextColor(ColorStateList.valueOf(fg))
|
setTextColor(ColorStateList.valueOf(fg))
|
||||||
}
|
}
|
||||||
itemView.text_rating_holder?.backgroundTintList = ColorStateList.valueOf(bg)
|
binding.textRating.compoundDrawables.getOrNull(0)?.setTint(fg)
|
||||||
itemView.watchProgress?.apply {
|
binding.textRating.backgroundTintList = ColorStateList.valueOf(bg)
|
||||||
|
binding.watchProgress.apply {
|
||||||
progressTintList = ColorStateList.valueOf(fg)
|
progressTintList = ColorStateList.valueOf(fg)
|
||||||
progressBackgroundTintList = ColorStateList.valueOf(bg)
|
progressBackgroundTintList = ColorStateList.valueOf(bg)
|
||||||
}
|
}
|
||||||
|
@ -99,7 +101,7 @@ class PageAdapter(
|
||||||
|
|
||||||
// See searchAdaptor for this, it basically fixes the height
|
// See searchAdaptor for this, it basically fixes the height
|
||||||
if (!compactView) {
|
if (!compactView) {
|
||||||
cardView.apply {
|
binding.imageView.apply {
|
||||||
layoutParams = FrameLayout.LayoutParams(
|
layoutParams = FrameLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
coverHeight
|
coverHeight
|
||||||
|
@ -108,23 +110,13 @@ class PageAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
val showProgress = item.episodesCompleted != null && item.episodesTotal != null
|
val showProgress = item.episodesCompleted != null && item.episodesTotal != null
|
||||||
itemView.watchProgress.isVisible = showProgress
|
binding.watchProgress.isVisible = showProgress
|
||||||
if (showProgress) {
|
if (showProgress) {
|
||||||
itemView.watchProgress.max = item.episodesTotal!!
|
binding.watchProgress.max = item.episodesTotal!!
|
||||||
itemView.watchProgress.progress = item.episodesCompleted!!
|
binding.watchProgress.progress = item.episodesCompleted!!
|
||||||
}
|
}
|
||||||
|
|
||||||
itemView.imageText.text = item.name
|
binding.imageText.text = item.name
|
||||||
|
|
||||||
val showRating = (item.personalRating ?: 0) != 0
|
|
||||||
itemView.text_rating_holder.isVisible = showRating
|
|
||||||
if (showRating) {
|
|
||||||
// We want to show 8.5 but not 8.0 hence the replace
|
|
||||||
val rating = ((item.personalRating ?: 0).toDouble() / 10).toString()
|
|
||||||
.replace(".0", "")
|
|
||||||
|
|
||||||
itemView.text_rating.text = "★ $rating"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,16 +2,14 @@ package com.lagradost.cloudstream3.ui.library
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.doOnAttach
|
import androidx.core.view.doOnAttach
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.RecyclerView.OnFlingListener
|
import androidx.recyclerview.widget.RecyclerView.OnFlingListener
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.databinding.LibraryViewpagerPageBinding
|
||||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||||
import kotlinx.android.synthetic.main.library_viewpager_page.view.*
|
|
||||||
|
|
||||||
class ViewpagerAdapter(
|
class ViewpagerAdapter(
|
||||||
var pages: List<SyncAPI.Page>,
|
var pages: List<SyncAPI.Page>,
|
||||||
|
@ -20,8 +18,7 @@ class ViewpagerAdapter(
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return PageViewHolder(
|
return PageViewHolder(
|
||||||
LayoutInflater.from(parent.context)
|
LibraryViewpagerPageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
.inflate(R.layout.library_viewpager_page, parent, false)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +31,7 @@ class ViewpagerAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
private val unbound = mutableSetOf<Int>()
|
private val unbound = mutableSetOf<Int>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to mark all pages for re-binding and forces all items to be refreshed
|
* Used to mark all pages for re-binding and forces all items to be refreshed
|
||||||
* Without this the pages will still use the same adapters
|
* Without this the pages will still use the same adapters
|
||||||
|
@ -43,44 +41,46 @@ class ViewpagerAdapter(
|
||||||
this.notifyItemRangeChanged(0, pages.size)
|
this.notifyItemRangeChanged(0, pages.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class PageViewHolder(private val itemViewTest: View) :
|
inner class PageViewHolder(private val binding: LibraryViewpagerPageBinding) :
|
||||||
RecyclerView.ViewHolder(itemViewTest) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
fun bind(page: SyncAPI.Page, rebind: Boolean) {
|
fun bind(page: SyncAPI.Page, rebind: Boolean) {
|
||||||
itemView.page_recyclerview?.spanCount =
|
binding.pageRecyclerview.apply {
|
||||||
this@PageViewHolder.itemView.context.getSpanCount() ?: 3
|
spanCount =
|
||||||
|
this@PageViewHolder.itemView.context.getSpanCount() ?: 3
|
||||||
if (itemViewTest.page_recyclerview?.adapter == null || rebind) {
|
if (adapter == null || rebind) {
|
||||||
// Only add the items after it has been attached since the items rely on ItemWidth
|
// Only add the items after it has been attached since the items rely on ItemWidth
|
||||||
// Which is only determined after the recyclerview is attached.
|
// Which is only determined after the recyclerview is attached.
|
||||||
// If this fails then item height becomes 0 when there is only one item
|
// If this fails then item height becomes 0 when there is only one item
|
||||||
itemViewTest.page_recyclerview?.doOnAttach {
|
doOnAttach {
|
||||||
itemViewTest.page_recyclerview?.adapter = PageAdapter(
|
adapter = PageAdapter(
|
||||||
page.items.toMutableList(),
|
page.items.toMutableList(),
|
||||||
itemViewTest.page_recyclerview,
|
this,
|
||||||
clickCallback
|
clickCallback
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(adapter as? PageAdapter)?.updateList(page.items)
|
||||||
|
scrollToPosition(0)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
(itemViewTest.page_recyclerview?.adapter as? PageAdapter)?.updateList(page.items)
|
|
||||||
itemViewTest.page_recyclerview?.scrollToPosition(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
itemViewTest.page_recyclerview.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
|
setOnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||||
val diff = scrollY - oldScrollY
|
val diff = scrollY - oldScrollY
|
||||||
if (diff == 0) return@setOnScrollChangeListener
|
if (diff == 0) return@setOnScrollChangeListener
|
||||||
|
|
||||||
scrollCallback.invoke(diff > 0)
|
scrollCallback.invoke(diff > 0)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
itemViewTest.page_recyclerview.onFlingListener = object : OnFlingListener() {
|
onFlingListener = object : OnFlingListener() {
|
||||||
override fun onFling(velocityX: Int, velocityY: Int): Boolean {
|
override fun onFling(velocityX: Int, velocityY: Int): Boolean {
|
||||||
scrollCallback.invoke(velocityY > 0)
|
scrollCallback.invoke(velocityY > 0)
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,9 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.ProgressBar
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.LayoutRes
|
import androidx.annotation.LayoutRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
@ -41,8 +44,6 @@ import com.lagradost.cloudstream3.utils.EpisodeSkip
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper
|
import com.lagradost.cloudstream3.utils.UIHelper
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
|
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
||||||
import kotlinx.android.synthetic.main.fragment_player.*
|
|
||||||
import kotlinx.android.synthetic.main.player_custom_layout.*
|
|
||||||
|
|
||||||
enum class PlayerResize(@StringRes val nameRes: Int) {
|
enum class PlayerResize(@StringRes val nameRes: Int) {
|
||||||
Fit(R.string.resize_fit),
|
Fit(R.string.resize_fit),
|
||||||
|
@ -71,9 +72,15 @@ abstract class AbstractPlayerFragment(
|
||||||
var isBuffering = true
|
var isBuffering = true
|
||||||
protected open var hasPipModeSupport = true
|
protected open var hasPipModeSupport = true
|
||||||
|
|
||||||
|
var playerPausePlayHolderHolder : FrameLayout? = null
|
||||||
|
var playerPausePlay : ImageView? = null
|
||||||
|
var playerBuffering : ProgressBar? = null
|
||||||
|
var playerView : PlayerView? = null
|
||||||
|
var piphide : FrameLayout? = null
|
||||||
|
var subtitleHolder : FrameLayout? = null
|
||||||
|
|
||||||
@LayoutRes
|
@LayoutRes
|
||||||
protected var layout: Int = R.layout.fragment_player
|
protected open var layout: Int = R.layout.fragment_player
|
||||||
|
|
||||||
open fun nextEpisode() {
|
open fun nextEpisode() {
|
||||||
throw NotImplementedError()
|
throw NotImplementedError()
|
||||||
|
@ -132,15 +139,15 @@ abstract class AbstractPlayerFragment(
|
||||||
|
|
||||||
isBuffering = CSPlayerLoading.IsBuffering == isPlaying
|
isBuffering = CSPlayerLoading.IsBuffering == isPlaying
|
||||||
if (isBuffering) {
|
if (isBuffering) {
|
||||||
player_pause_play_holder_holder?.isVisible = false
|
playerPausePlayHolderHolder?.isVisible = false
|
||||||
player_buffering?.isVisible = true
|
playerBuffering?.isVisible = true
|
||||||
} else {
|
} else {
|
||||||
player_pause_play_holder_holder?.isVisible = true
|
playerPausePlayHolderHolder?.isVisible = true
|
||||||
player_buffering?.isVisible = false
|
playerBuffering?.isVisible = false
|
||||||
|
|
||||||
if (wasPlaying != isPlaying) {
|
if (wasPlaying != isPlaying) {
|
||||||
player_pause_play?.setImageResource(if (isPlayingRightNow) R.drawable.play_to_pause else R.drawable.pause_to_play)
|
playerPausePlay?.setImageResource(if (isPlayingRightNow) R.drawable.play_to_pause else R.drawable.pause_to_play)
|
||||||
val drawable = player_pause_play?.drawable
|
val drawable = playerPausePlay?.drawable
|
||||||
|
|
||||||
var startedAnimation = false
|
var startedAnimation = false
|
||||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
|
||||||
|
@ -162,10 +169,10 @@ abstract class AbstractPlayerFragment(
|
||||||
|
|
||||||
// somehow the phone is wacked
|
// somehow the phone is wacked
|
||||||
if (!startedAnimation) {
|
if (!startedAnimation) {
|
||||||
player_pause_play?.setImageResource(if (isPlayingRightNow) R.drawable.netflix_pause else R.drawable.netflix_play)
|
playerPausePlay?.setImageResource(if (isPlayingRightNow) R.drawable.netflix_pause else R.drawable.netflix_play)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
player_pause_play?.setImageResource(if (isPlayingRightNow) R.drawable.netflix_pause else R.drawable.netflix_play)
|
playerPausePlay?.setImageResource(if (isPlayingRightNow) R.drawable.netflix_pause else R.drawable.netflix_play)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,14 +251,12 @@ abstract class AbstractPlayerFragment(
|
||||||
fun showToast(message: String, gotoNext: Boolean = false) {
|
fun showToast(message: String, gotoNext: Boolean = false) {
|
||||||
if (gotoNext && hasNextMirror()) {
|
if (gotoNext && hasNextMirror()) {
|
||||||
showToast(
|
showToast(
|
||||||
activity,
|
|
||||||
message,
|
message,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
)
|
)
|
||||||
nextMirror()
|
nextMirror()
|
||||||
} else {
|
} else {
|
||||||
showToast(
|
showToast(
|
||||||
activity,
|
|
||||||
context?.getString(R.string.no_links_found_toast) + "\n" + message,
|
context?.getString(R.string.no_links_found_toast) + "\n" + message,
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
)
|
)
|
||||||
|
@ -327,9 +332,9 @@ abstract class AbstractPlayerFragment(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Necessary for multiple combined videos
|
// Necessary for multiple combined videos
|
||||||
player_view?.setShowMultiWindowTimeBar(true)
|
playerView?.setShowMultiWindowTimeBar(true)
|
||||||
player_view?.player = player
|
playerView?.player = player
|
||||||
player_view?.performClick()
|
playerView?.performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,9 +391,9 @@ abstract class AbstractPlayerFragment(
|
||||||
)
|
)
|
||||||
|
|
||||||
if (player is CS3IPlayer) {
|
if (player is CS3IPlayer) {
|
||||||
subView = player_view?.findViewById(R.id.exo_subtitles)
|
subView = playerView?.findViewById(R.id.exo_subtitles)
|
||||||
subStyle = SubtitlesFragment.getCurrentSavedStyle()
|
subStyle = SubtitlesFragment.getCurrentSavedStyle()
|
||||||
player.initSubtitles(subView, subtitle_holder, subStyle)
|
player.initSubtitles(subView, subtitleHolder, subStyle)
|
||||||
|
|
||||||
SubtitlesFragment.applyStyleEvent += ::onSubStyleChanged
|
SubtitlesFragment.applyStyleEvent += ::onSubStyleChanged
|
||||||
|
|
||||||
|
@ -457,10 +462,10 @@ abstract class AbstractPlayerFragment(
|
||||||
PlayerResize.Fit -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
PlayerResize.Fit -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||||
PlayerResize.Zoom -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
PlayerResize.Zoom -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
||||||
}
|
}
|
||||||
player_view?.resizeMode = type
|
playerView?.resizeMode = type
|
||||||
|
|
||||||
if (showToast)
|
if (showToast)
|
||||||
showToast(activity, resize.nameRes, Toast.LENGTH_SHORT)
|
showToast(resize.nameRes, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
|
@ -481,6 +486,13 @@ abstract class AbstractPlayerFragment(
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
return inflater.inflate(layout, container, false)
|
val root = inflater.inflate(layout, container, false)
|
||||||
|
playerPausePlayHolderHolder = root.findViewById(R.id.player_pause_play_holder_holder)
|
||||||
|
playerPausePlay = root.findViewById(R.id.player_pause_play)
|
||||||
|
playerBuffering = root.findViewById(R.id.player_buffering)
|
||||||
|
playerView = root.findViewById(R.id.player_view)
|
||||||
|
piphide = root.findViewById(R.id.piphide)
|
||||||
|
subtitleHolder = root.findViewById(R.id.subtitle_holder)
|
||||||
|
return root
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -63,7 +63,15 @@ import javax.net.ssl.SSLSession
|
||||||
const val TAG = "CS3ExoPlayer"
|
const val TAG = "CS3ExoPlayer"
|
||||||
const val PREFERRED_AUDIO_LANGUAGE_KEY = "preferred_audio_language"
|
const val PREFERRED_AUDIO_LANGUAGE_KEY = "preferred_audio_language"
|
||||||
|
|
||||||
/** Cache */
|
/** toleranceBeforeUs – The maximum time that the actual position seeked to may precede the
|
||||||
|
* requested seek position, in microseconds. Must be non-negative. */
|
||||||
|
const val toleranceBeforeUs = 300_000L
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toleranceAfterUs – The maximum time that the actual position seeked to may exceed the requested
|
||||||
|
* seek position, in microseconds. Must be non-negative.
|
||||||
|
*/
|
||||||
|
const val toleranceAfterUs = 300_000L
|
||||||
|
|
||||||
class CS3IPlayer : IPlayer {
|
class CS3IPlayer : IPlayer {
|
||||||
private var isPlaying = false
|
private var isPlaying = false
|
||||||
|
@ -721,7 +729,7 @@ class CS3IPlayer : IPlayer {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
// Allows any seeking to be +- 0.3s to allow for faster seeking
|
// Allows any seeking to be +- 0.3s to allow for faster seeking
|
||||||
.setSeekParameters(SeekParameters(300_000, 300_000))
|
.setSeekParameters(SeekParameters(toleranceBeforeUs, toleranceAfterUs))
|
||||||
.setLoadControl(
|
.setLoadControl(
|
||||||
DefaultLoadControl.Builder()
|
DefaultLoadControl.Builder()
|
||||||
.setTargetBufferBytes(
|
.setTargetBufferBytes(
|
||||||
|
@ -788,7 +796,7 @@ class CS3IPlayer : IPlayer {
|
||||||
private fun getCurrentTimestamp(writePosition: Long? = null): EpisodeSkip.SkipStamp? {
|
private fun getCurrentTimestamp(writePosition: Long? = null): EpisodeSkip.SkipStamp? {
|
||||||
val position = writePosition ?: this@CS3IPlayer.getPosition() ?: return null
|
val position = writePosition ?: this@CS3IPlayer.getPosition() ?: return null
|
||||||
for (lastTimeStamp in lastTimeStamps) {
|
for (lastTimeStamp in lastTimeStamps) {
|
||||||
if (lastTimeStamp.startMs <= position && position < lastTimeStamp.endMs) {
|
if (lastTimeStamp.startMs <= position && (position + (toleranceBeforeUs / 1000L) + 1) < lastTimeStamp.endMs) {
|
||||||
return lastTimeStamp
|
return lastTimeStamp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -796,11 +804,12 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updatedTime(writePosition: Long? = null) {
|
fun updatedTime(writePosition: Long? = null) {
|
||||||
getCurrentTimestamp(writePosition)?.let { timestamp ->
|
val position = writePosition ?: exoPlayer?.currentPosition
|
||||||
|
|
||||||
|
getCurrentTimestamp(position)?.let { timestamp ->
|
||||||
onTimestampInvoked?.invoke(timestamp)
|
onTimestampInvoked?.invoke(timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
val position = writePosition ?: exoPlayer?.currentPosition
|
|
||||||
val duration = exoPlayer?.contentDuration
|
val duration = exoPlayer?.contentDuration
|
||||||
if (duration != null && position != null) {
|
if (duration != null && position != null) {
|
||||||
playerPositionChanged?.invoke(Pair(position, duration))
|
playerPositionChanged?.invoke(Pair(position, duration))
|
||||||
|
@ -1086,9 +1095,9 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRenderedFirstFrame() {
|
override fun onRenderedFirstFrame() {
|
||||||
updatedTime()
|
|
||||||
super.onRenderedFirstFrame()
|
super.onRenderedFirstFrame()
|
||||||
onRenderFirst()
|
onRenderFirst()
|
||||||
|
updatedTime()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -1116,42 +1125,43 @@ class CS3IPlayer : IPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onRenderFirst() {
|
fun onRenderFirst() {
|
||||||
if (!hasUsedFirstRender) { // this insures that we only call this once per player load
|
if (hasUsedFirstRender) { // this insures that we only call this once per player load
|
||||||
Log.i(TAG, "Rendered first frame")
|
return
|
||||||
val invalid = exoPlayer?.duration?.let { duration ->
|
}
|
||||||
// Only errors short playback when not playing downloaded files
|
Log.i(TAG, "Rendered first frame")
|
||||||
duration < 20_000L && currentDownloadedFile == null
|
hasUsedFirstRender = true
|
||||||
// Concatenated sources (non 1 periodCount) bypasses the invalid check as exoPlayer.duration gives only the current period
|
val invalid = exoPlayer?.duration?.let { duration ->
|
||||||
// If you can get the total time that'd be better, but this is already niche.
|
// Only errors short playback when not playing downloaded files
|
||||||
&& exoPlayer?.currentTimeline?.periodCount == 1
|
duration < 20_000L && currentDownloadedFile == null
|
||||||
&& exoPlayer?.isCurrentMediaItemLive != true
|
// Concatenated sources (non 1 periodCount) bypasses the invalid check as exoPlayer.duration gives only the current period
|
||||||
} ?: false
|
// If you can get the total time that'd be better, but this is already niche.
|
||||||
|
&& exoPlayer?.currentTimeline?.periodCount == 1
|
||||||
|
&& exoPlayer?.isCurrentMediaItemLive != true
|
||||||
|
} ?: false
|
||||||
|
|
||||||
if (invalid) {
|
if (invalid) {
|
||||||
releasePlayer(saveTime = false)
|
releasePlayer(saveTime = false)
|
||||||
playerError?.invoke(InvalidFileException("Too short playback"))
|
playerError?.invoke(InvalidFileException("Too short playback"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setPreferredSubtitles(currentSubtitles)
|
setPreferredSubtitles(currentSubtitles)
|
||||||
hasUsedFirstRender = true
|
val format = exoPlayer?.videoFormat
|
||||||
val format = exoPlayer?.videoFormat
|
val width = format?.width
|
||||||
val width = format?.width
|
val height = format?.height
|
||||||
val height = format?.height
|
if (height != null && width != null) {
|
||||||
if (height != null && width != null) {
|
playerDimensionsLoaded?.invoke(Pair(width, height))
|
||||||
playerDimensionsLoaded?.invoke(Pair(width, height))
|
updatedTime()
|
||||||
updatedTime()
|
exoPlayer?.apply {
|
||||||
exoPlayer?.apply {
|
requestedListeningPercentages?.forEach { percentage ->
|
||||||
requestedListeningPercentages?.forEach { percentage ->
|
createMessage { _, _ ->
|
||||||
createMessage { _, _ ->
|
updatedTime()
|
||||||
updatedTime()
|
|
||||||
}
|
|
||||||
.setLooper(Looper.getMainLooper())
|
|
||||||
.setPosition( /* positionMs= */contentDuration * percentage / 100)
|
|
||||||
// .setPayload(customPayloadData)
|
|
||||||
.setDeleteAfterDelivery(false)
|
|
||||||
.send()
|
|
||||||
}
|
}
|
||||||
|
.setLooper(Looper.getMainLooper())
|
||||||
|
.setPosition(contentDuration * percentage / 100)
|
||||||
|
// .setPayload(customPayloadData)
|
||||||
|
.setDeleteAfterDelivery(false)
|
||||||
|
.send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -25,6 +25,11 @@ import com.hippo.unifile.UniFile
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
|
import com.lagradost.cloudstream3.databinding.DialogOnlineSubtitlesBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.PlayerSelectSourceAndSubsBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.PlayerSelectTracksBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.*
|
import com.lagradost.cloudstream3.mvvm.*
|
||||||
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
|
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subtitleProviders
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subtitleProviders
|
||||||
|
@ -33,8 +38,6 @@ import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.updateForced
|
||||||
import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType
|
import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType
|
||||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
||||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
|
import com.lagradost.cloudstream3.ui.player.source_priority.QualityProfileDialog
|
||||||
import com.lagradost.cloudstream3.ui.player.source_priority.SourcePriority
|
|
||||||
import com.lagradost.cloudstream3.ui.player.source_priority.SourcePriorityDialog
|
|
||||||
import com.lagradost.cloudstream3.ui.result.*
|
import com.lagradost.cloudstream3.ui.result.*
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1
|
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment.Companion.getAutoSelectLanguageISO639_1
|
||||||
|
@ -49,18 +52,8 @@ import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
|
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
import kotlinx.android.synthetic.main.dialog_online_subtitles.*
|
|
||||||
import kotlinx.android.synthetic.main.dialog_online_subtitles.apply_btt
|
|
||||||
import kotlinx.android.synthetic.main.dialog_online_subtitles.cancel_btt
|
|
||||||
import kotlinx.android.synthetic.main.fragment_player.*
|
|
||||||
import kotlinx.android.synthetic.main.player_custom_layout.*
|
|
||||||
import kotlinx.android.synthetic.main.player_select_source_and_subs.*
|
|
||||||
import kotlinx.android.synthetic.main.player_select_source_and_subs.subtitles_click_settings
|
|
||||||
import kotlinx.android.synthetic.main.player_select_tracks.*
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
import kotlin.collections.HashMap
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
class GeneratorPlayer : FullScreenPlayer() {
|
class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
@ -98,12 +91,14 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
private var preferredAutoSelectSubtitles: String? = null // null means do nothing, "" means none
|
private var preferredAutoSelectSubtitles: String? = null // null means do nothing, "" means none
|
||||||
|
|
||||||
|
private var binding: FragmentPlayerBinding? = null
|
||||||
|
|
||||||
private fun startLoading() {
|
private fun startLoading() {
|
||||||
player.release()
|
player.release()
|
||||||
currentSelectedSubtitles = null
|
currentSelectedSubtitles = null
|
||||||
isActive = false
|
isActive = false
|
||||||
overlay_loading_skip_button?.isVisible = false
|
binding?.overlayLoadingSkipButton?.isVisible = false
|
||||||
player_loading_overlay?.isVisible = true
|
binding?.playerLoadingOverlay?.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setSubtitles(sub: SubtitleData?): Boolean {
|
private fun setSubtitles(sub: SubtitleData?): Boolean {
|
||||||
|
@ -118,7 +113,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
override fun onTracksInfoChanged() {
|
override fun onTracksInfoChanged() {
|
||||||
val tracks = player.getVideoTracks()
|
val tracks = player.getVideoTracks()
|
||||||
player_tracks_btt?.isVisible =
|
playerBinding?.playerTracksBtt?.isVisible =
|
||||||
tracks.allVideoTracks.size > 1 || tracks.allAudioTracks.size > 1
|
tracks.allVideoTracks.size > 1 || tracks.allAudioTracks.size > 1
|
||||||
// Only set the preferred language if it is available.
|
// Only set the preferred language if it is available.
|
||||||
// Otherwise it may give some users audio track init failed!
|
// Otherwise it may give some users audio track init failed!
|
||||||
|
@ -158,12 +153,12 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
if (link == null) return
|
if (link == null) return
|
||||||
|
|
||||||
// manage UI
|
// manage UI
|
||||||
player_loading_overlay?.isVisible = false
|
binding?.playerLoadingOverlay?.isVisible = false
|
||||||
uiReset()
|
uiReset()
|
||||||
currentSelectedLink = link
|
currentSelectedLink = link
|
||||||
currentMeta = viewModel.getMeta()
|
currentMeta = viewModel.getMeta()
|
||||||
nextMeta = viewModel.getNextMeta()
|
nextMeta = viewModel.getNextMeta()
|
||||||
setEpisodes(viewModel.getAllMeta() ?: emptyList())
|
// setEpisodes(viewModel.getAllMeta() ?: emptyList())
|
||||||
isActive = true
|
isActive = true
|
||||||
setPlayerDimen(null)
|
setPlayerDimen(null)
|
||||||
setTitle()
|
setTitle()
|
||||||
|
@ -209,7 +204,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
closestQuality(linkData?.quality)
|
closestQuality(linkData?.quality)
|
||||||
)
|
)
|
||||||
val sourcePriority =
|
val sourcePriority =
|
||||||
QualityDataHelper.getSourcePriority(qualityProfile, linkData?.name)
|
QualityDataHelper.getSourcePriority(qualityProfile, linkData?.source)
|
||||||
|
|
||||||
// negative because we want to sort highest quality first
|
// negative because we want to sort highest quality first
|
||||||
return qualityPriority + sourcePriority
|
return qualityPriority + sourcePriority
|
||||||
|
@ -257,7 +252,9 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
val isSingleProvider = subsProviders.size == 1
|
val isSingleProvider = subsProviders.size == 1
|
||||||
|
|
||||||
val dialog = Dialog(context, R.style.AlertDialogCustomBlack)
|
val dialog = Dialog(context, R.style.AlertDialogCustomBlack)
|
||||||
dialog.setContentView(R.layout.dialog_online_subtitles)
|
val binding =
|
||||||
|
DialogOnlineSubtitlesBinding.inflate(LayoutInflater.from(context), null, false)
|
||||||
|
dialog.setContentView(binding.root)
|
||||||
|
|
||||||
var currentSubtitles: List<AbstractSubtitleEntities.SubtitleEntity> = emptyList()
|
var currentSubtitles: List<AbstractSubtitleEntities.SubtitleEntity> = emptyList()
|
||||||
var currentSubtitle: AbstractSubtitleEntities.SubtitleEntity? = null
|
var currentSubtitle: AbstractSubtitleEntities.SubtitleEntity? = null
|
||||||
|
@ -295,6 +292,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
imageViewEnd.setImageDrawable(drawableEnd)
|
imageViewEnd.setImageDrawable(drawableEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
val view = convertView ?: LayoutInflater.from(context).inflate(layout, null)
|
val view = convertView ?: LayoutInflater.from(context).inflate(layout, null)
|
||||||
|
|
||||||
|
@ -318,16 +316,16 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.show()
|
dialog.show()
|
||||||
dialog.cancel_btt.setOnClickListener {
|
binding.cancelBtt.setOnClickListener {
|
||||||
dialog.dismissSafe()
|
dialog.dismissSafe()
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.subtitle_adapter.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
binding.subtitleAdapter.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
||||||
dialog.subtitle_adapter.adapter = arrayAdapter
|
binding.subtitleAdapter.adapter = arrayAdapter
|
||||||
val adapter =
|
val adapter =
|
||||||
dialog.subtitle_adapter.adapter as? ArrayAdapter<AbstractSubtitleEntities.SubtitleEntity>
|
binding.subtitleAdapter.adapter as? ArrayAdapter<AbstractSubtitleEntities.SubtitleEntity>
|
||||||
|
|
||||||
dialog.subtitle_adapter.setOnItemClickListener { _, _, position, _ ->
|
binding.subtitleAdapter.setOnItemClickListener { _, _, position, _ ->
|
||||||
currentSubtitle = currentSubtitles.getOrNull(position) ?: return@setOnItemClickListener
|
currentSubtitle = currentSubtitles.getOrNull(position) ?: return@setOnItemClickListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,16 +341,16 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
val currentTempMeta = getMetaData()
|
val currentTempMeta = getMetaData()
|
||||||
// bruh idk why it is not correct
|
// bruh idk why it is not correct
|
||||||
val color = ColorStateList.valueOf(context.colorFromAttribute(R.attr.colorAccent))
|
val color = ColorStateList.valueOf(context.colorFromAttribute(R.attr.colorAccent))
|
||||||
dialog.search_loading_bar.progressTintList = color
|
binding.searchLoadingBar.progressTintList = color
|
||||||
dialog.search_loading_bar.indeterminateTintList = color
|
binding.searchLoadingBar.indeterminateTintList = color
|
||||||
|
|
||||||
observeNullable(viewModel.currentSubtitleYear) {
|
observeNullable(viewModel.currentSubtitleYear) {
|
||||||
// When year is changed search again
|
// When year is changed search again
|
||||||
dialog.subtitles_search.setQuery(dialog.subtitles_search.query, true)
|
binding.subtitlesSearch.setQuery(binding.subtitlesSearch.query, true)
|
||||||
dialog.year_btt.text = it?.toString() ?: txt(R.string.none).asString(context)
|
binding.yearBtt.text = it?.toString() ?: txt(R.string.none).asString(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.year_btt?.setOnClickListener {
|
binding.yearBtt.setOnClickListener {
|
||||||
val none = txt(R.string.none).asString(context)
|
val none = txt(R.string.none).asString(context)
|
||||||
val currentYear = Calendar.getInstance().get(Calendar.YEAR)
|
val currentYear = Calendar.getInstance().get(Calendar.YEAR)
|
||||||
val earliestYear = 1900
|
val earliestYear = 1900
|
||||||
|
@ -380,10 +378,10 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.subtitles_search.setOnQueryTextListener(object :
|
binding.subtitlesSearch.setOnQueryTextListener(object :
|
||||||
androidx.appcompat.widget.SearchView.OnQueryTextListener {
|
androidx.appcompat.widget.SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||||
dialog.search_loading_bar?.show()
|
binding.searchLoadingBar.show()
|
||||||
ioSafe {
|
ioSafe {
|
||||||
val search =
|
val search =
|
||||||
AbstractSubtitleEntities.SubtitleSearch(
|
AbstractSubtitleEntities.SubtitleSearch(
|
||||||
|
@ -415,7 +413,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
// ugly ik
|
// ugly ik
|
||||||
activity?.runOnUiThread {
|
activity?.runOnUiThread {
|
||||||
setSubtitlesList(items)
|
setSubtitlesList(items)
|
||||||
dialog.search_loading_bar?.hide()
|
binding.searchLoadingBar.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,7 +425,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
dialog.search_filter.setOnClickListener { view ->
|
binding.searchFilter.setOnClickListener { view ->
|
||||||
val lang639_1 = languages.map { it.ISO_639_1 }
|
val lang639_1 = languages.map { it.ISO_639_1 }
|
||||||
activity?.showDialog(languages.map { it.languageName },
|
activity?.showDialog(languages.map { it.languageName },
|
||||||
lang639_1.indexOf(currentLanguageTwoLetters),
|
lang639_1.indexOf(currentLanguageTwoLetters),
|
||||||
|
@ -436,11 +434,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
true,
|
true,
|
||||||
{ }) { index ->
|
{ }) { index ->
|
||||||
currentLanguageTwoLetters = lang639_1[index]
|
currentLanguageTwoLetters = lang639_1[index]
|
||||||
dialog.subtitles_search.setQuery(dialog.subtitles_search.query, true)
|
binding.subtitlesSearch.setQuery(binding.subtitlesSearch.query, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.apply_btt.setOnClickListener {
|
binding.applyBtt.setOnClickListener {
|
||||||
currentSubtitle?.let { currentSubtitle ->
|
currentSubtitle?.let { currentSubtitle ->
|
||||||
providers.firstOrNull { it.idPrefix == currentSubtitle.idPrefix }?.let { api ->
|
providers.firstOrNull { it.idPrefix == currentSubtitle.idPrefix }?.let { api ->
|
||||||
ioSafe {
|
ioSafe {
|
||||||
|
@ -466,7 +464,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.show()
|
dialog.show()
|
||||||
dialog.subtitles_search.setQuery(currentTempMeta.name, true)
|
binding.subtitlesSearch.setQuery(currentTempMeta.name, true)
|
||||||
//TODO: Set year text from currently loaded movie on Player
|
//TODO: Set year text from currently loaded movie on Player
|
||||||
//dialog.subtitles_search_year?.setText(currentTempMeta.year)
|
//dialog.subtitles_search_year?.setText(currentTempMeta.year)
|
||||||
}
|
}
|
||||||
|
@ -509,7 +507,6 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
selectSourceDialog?.dismissSafe()
|
selectSourceDialog?.dismissSafe()
|
||||||
|
|
||||||
showToast(
|
showToast(
|
||||||
activity,
|
|
||||||
String.format(ctx.getString(R.string.player_loaded_subtitles), subtitleData.name),
|
String.format(ctx.getString(R.string.player_loaded_subtitles), subtitleData.name),
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
)
|
)
|
||||||
|
@ -558,13 +555,15 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
val currentSubtitles = sortSubs(currentSubs)
|
val currentSubtitles = sortSubs(currentSubs)
|
||||||
|
|
||||||
val sourceDialog = Dialog(ctx, R.style.AlertDialogCustomBlack)
|
val sourceDialog = Dialog(ctx, R.style.AlertDialogCustomBlack)
|
||||||
sourceDialog.setContentView(R.layout.player_select_source_and_subs)
|
val binding =
|
||||||
|
PlayerSelectSourceAndSubsBinding.inflate(LayoutInflater.from(ctx), null, false)
|
||||||
|
sourceDialog.setContentView(binding.root)
|
||||||
|
|
||||||
selectSourceDialog = sourceDialog
|
selectSourceDialog = sourceDialog
|
||||||
|
|
||||||
sourceDialog.show()
|
sourceDialog.show()
|
||||||
val providerList = sourceDialog.sort_providers
|
val providerList = binding.sortProviders
|
||||||
val subtitleList = sourceDialog.sort_subtitles
|
val subtitleList = binding.sortSubtitles
|
||||||
|
|
||||||
val loadFromFileFooter: TextView =
|
val loadFromFileFooter: TextView =
|
||||||
layoutInflater.inflate(R.layout.sort_bottom_footer_add_choice, null) as TextView
|
layoutInflater.inflate(R.layout.sort_bottom_footer_add_choice, null) as TextView
|
||||||
|
@ -672,12 +671,12 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceDialog.cancel_btt?.setOnClickListener {
|
binding.cancelBtt.setOnClickListener {
|
||||||
sourceDialog.dismissSafe(activity)
|
sourceDialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setProfileName(profile: Int) {
|
fun setProfileName(profile: Int) {
|
||||||
sourceDialog.source_settings_btt.setText(
|
binding.sourceSettingsBtt.setText(
|
||||||
QualityDataHelper.getProfileName(
|
QualityDataHelper.getProfileName(
|
||||||
profile
|
profile
|
||||||
)
|
)
|
||||||
|
@ -685,7 +684,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
setProfileName(currentQualityProfile)
|
setProfileName(currentQualityProfile)
|
||||||
|
|
||||||
sourceDialog.profiles_click_settings.setOnClickListener {
|
binding.profilesClickSettings.setOnClickListener {
|
||||||
val activity = activity ?: return@setOnClickListener
|
val activity = activity ?: return@setOnClickListener
|
||||||
QualityProfileDialog(
|
QualityProfileDialog(
|
||||||
activity,
|
activity,
|
||||||
|
@ -699,7 +698,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceDialog.subtitles_encoding_format?.apply {
|
binding.subtitlesEncodingFormat.apply {
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
|
||||||
val prefNames = ctx.resources.getStringArray(R.array.subtitles_encoding_list)
|
val prefNames = ctx.resources.getStringArray(R.array.subtitles_encoding_list)
|
||||||
|
@ -712,7 +711,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
text = prefNames[if (index == -1) 0 else index]
|
text = prefNames[if (index == -1) 0 else index]
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceDialog.subtitles_click_settings?.setOnClickListener {
|
binding.subtitlesClickSettings.setOnClickListener {
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
|
||||||
val prefNames = ctx.resources.getStringArray(R.array.subtitles_encoding_list)
|
val prefNames = ctx.resources.getStringArray(R.array.subtitles_encoding_list)
|
||||||
|
@ -741,7 +740,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceDialog.apply_btt?.setOnClickListener {
|
binding.applyBtt.setOnClickListener {
|
||||||
var init = false
|
var init = false
|
||||||
if (sourceIndex != startSource) {
|
if (sourceIndex != startSource) {
|
||||||
init = true
|
init = true
|
||||||
|
@ -781,18 +780,19 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
it.height?.times(-1)
|
it.height?.times(-1)
|
||||||
}
|
}
|
||||||
val currentAudioTracks = tracks.allAudioTracks
|
val currentAudioTracks = tracks.allAudioTracks
|
||||||
|
val binding: PlayerSelectTracksBinding =
|
||||||
|
PlayerSelectTracksBinding.inflate(LayoutInflater.from(ctx), null, false)
|
||||||
val trackDialog = Dialog(ctx, R.style.AlertDialogCustomBlack)
|
val trackDialog = Dialog(ctx, R.style.AlertDialogCustomBlack)
|
||||||
trackDialog.setContentView(R.layout.player_select_tracks)
|
trackDialog.setContentView(binding.root)
|
||||||
trackDialog.show()
|
trackDialog.show()
|
||||||
|
|
||||||
// selectTracksDialog = tracksDialog
|
// selectTracksDialog = tracksDialog
|
||||||
|
|
||||||
val videosList = trackDialog.video_tracks_list
|
val videosList = binding.videoTracksList
|
||||||
val audioList = trackDialog.auto_tracks_list
|
val audioList = binding.autoTracksList
|
||||||
|
|
||||||
trackDialog.video_tracks_holder.isVisible = currentVideoTracks.size > 1
|
binding.videoTracksHolder.isVisible = currentVideoTracks.size > 1
|
||||||
trackDialog.audio_tracks_holder.isVisible = currentAudioTracks.size > 1
|
binding.audioTracksHolder.isVisible = currentAudioTracks.size > 1
|
||||||
|
|
||||||
fun dismiss() {
|
fun dismiss() {
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
|
@ -857,11 +857,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
audioList.setItemChecked(which, true)
|
audioList.setItemChecked(which, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
trackDialog.cancel_btt?.setOnClickListener {
|
binding.cancelBtt.setOnClickListener {
|
||||||
trackDialog.dismissSafe(activity)
|
trackDialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
trackDialog.apply_btt?.setOnClickListener {
|
binding.applyBtt.setOnClickListener {
|
||||||
val currentTrack = currentAudioTracks.getOrNull(audioIndexStart)
|
val currentTrack = currentAudioTracks.getOrNull(audioIndexStart)
|
||||||
player.setPreferredAudioTrack(
|
player.setPreferredAudioTrack(
|
||||||
currentTrack?.language, currentTrack?.id
|
currentTrack?.language, currentTrack?.id
|
||||||
|
@ -889,7 +889,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun noLinksFound() {
|
private fun noLinksFound() {
|
||||||
showToast(activity, R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
||||||
activity?.popCurrentPage()
|
activity?.popCurrentPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1030,8 +1030,10 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
if (meta.tvType.isAnimeOp()) isOpVisible = percentage < SKIP_OP_VIDEO_PERCENTAGE
|
if (meta.tvType.isAnimeOp()) isOpVisible = percentage < SKIP_OP_VIDEO_PERCENTAGE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
player_skip_op?.isVisible = isOpVisible
|
|
||||||
player_skip_episode?.isVisible = !isOpVisible && viewModel.hasNextEpisode() == true
|
playerBinding?.playerSkipOp?.isVisible = isOpVisible
|
||||||
|
playerBinding?.playerSkipEpisode?.isVisible =
|
||||||
|
!isOpVisible && viewModel.hasNextEpisode() == true
|
||||||
|
|
||||||
if (percentage >= PRELOAD_NEXT_EPISODE_PERCENTAGE) {
|
if (percentage >= PRELOAD_NEXT_EPISODE_PERCENTAGE) {
|
||||||
viewModel.preLoadNextLinks()
|
viewModel.preLoadNextLinks()
|
||||||
|
@ -1168,7 +1170,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
//Hide title, if set in setting
|
//Hide title, if set in setting
|
||||||
if (limitTitle < 0) {
|
if (limitTitle < 0) {
|
||||||
player_video_title?.visibility = View.GONE
|
playerBinding?.playerVideoTitle?.visibility = View.GONE
|
||||||
} else {
|
} else {
|
||||||
//Truncate video title if it exceeds limit
|
//Truncate video title if it exceeds limit
|
||||||
val differenceInLength = playerVideoTitle.length - limitTitle
|
val differenceInLength = playerVideoTitle.length - limitTitle
|
||||||
|
@ -1179,8 +1181,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
val isFiller: Boolean? = (currentMeta as? ResultEpisode)?.isFiller
|
val isFiller: Boolean? = (currentMeta as? ResultEpisode)?.isFiller
|
||||||
|
|
||||||
player_episode_filler_holder?.isVisible = isFiller ?: false
|
playerBinding?.playerEpisodeFillerHolder?.isVisible = isFiller ?: false
|
||||||
player_video_title?.text = playerVideoTitle
|
playerBinding?.playerVideoTitle?.text = playerVideoTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
|
@ -1201,8 +1203,10 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
3 -> "$source - $extra"
|
3 -> "$source - $extra"
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
player_video_title_rez?.text = title
|
playerBinding?.playerVideoTitleRez?.apply {
|
||||||
player_video_title_rez?.isVisible = title.isNotBlank()
|
text = title
|
||||||
|
isVisible = title.isNotBlank()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun playerDimensionsLoaded(widthHeight: Pair<Int, Int>) {
|
override fun playerDimensionsLoaded(widthHeight: Pair<Int, Int>) {
|
||||||
|
@ -1230,7 +1234,14 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
unwrapBundle(savedInstanceState)
|
unwrapBundle(savedInstanceState)
|
||||||
unwrapBundle(arguments)
|
unwrapBundle(arguments)
|
||||||
|
|
||||||
return super.onCreateView(inflater, container, savedInstanceState)
|
val root = super.onCreateView(inflater, container, savedInstanceState) ?: return null
|
||||||
|
binding = FragmentPlayerBinding.bind(root)
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
var timestampShowState = false
|
var timestampShowState = false
|
||||||
|
@ -1243,7 +1254,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
skipIndex++
|
skipIndex++
|
||||||
println("displayTimeStamp = $show")
|
println("displayTimeStamp = $show")
|
||||||
timestampShowState = show
|
timestampShowState = show
|
||||||
skip_chapter_button?.apply {
|
playerBinding?.skipChapterButton?.apply {
|
||||||
val showWidth = 170.toPx
|
val showWidth = 170.toPx
|
||||||
val noShowWidth = 10.toPx
|
val noShowWidth = 10.toPx
|
||||||
//if((show && width == showWidth) || (!show && width == noShowWidth)) {
|
//if((show && width == showWidth) || (!show && width == noShowWidth)) {
|
||||||
|
@ -1263,7 +1274,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
from, to
|
from, to
|
||||||
).apply {
|
).apply {
|
||||||
addListener(onEnd = {
|
addListener(onEnd = {
|
||||||
if (!show) skip_chapter_button?.isVisible = false
|
if (!show) playerBinding?.skipChapterButton?.isVisible = false
|
||||||
})
|
})
|
||||||
addUpdateListener { valueAnimator ->
|
addUpdateListener { valueAnimator ->
|
||||||
val value = valueAnimator.animatedValue as Int
|
val value = valueAnimator.animatedValue as Int
|
||||||
|
@ -1283,10 +1294,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
|
|
||||||
override fun onTimestamp(timestamp: EpisodeSkip.SkipStamp?) {
|
override fun onTimestamp(timestamp: EpisodeSkip.SkipStamp?) {
|
||||||
if (timestamp != null) {
|
if (timestamp != null) {
|
||||||
skip_chapter_button.setText(timestamp.uiText)
|
println("timestamp: $timestamp")
|
||||||
|
playerBinding?.skipChapterButton?.setText(timestamp.uiText)
|
||||||
displayTimeStamp(true)
|
displayTimeStamp(true)
|
||||||
val currentIndex = skipIndex
|
val currentIndex = skipIndex
|
||||||
skip_chapter_button?.handler?.postDelayed({
|
playerBinding?.skipChapterButton?.handler?.postDelayed({
|
||||||
if (skipIndex == currentIndex)
|
if (skipIndex == currentIndex)
|
||||||
displayTimeStamp(false)
|
displayTimeStamp(false)
|
||||||
}, 6000)
|
}, 6000)
|
||||||
|
@ -1329,11 +1341,11 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
viewModel.loadLinks()
|
viewModel.loadLinks()
|
||||||
}
|
}
|
||||||
|
|
||||||
overlay_loading_skip_button?.setOnClickListener {
|
binding?.overlayLoadingSkipButton?.setOnClickListener {
|
||||||
startPlayer()
|
startPlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
player_loading_go_back?.setOnClickListener {
|
binding?.playerLoadingGoBack?.setOnClickListener {
|
||||||
player.release()
|
player.release()
|
||||||
activity?.popCurrentPage()
|
activity?.popCurrentPage()
|
||||||
}
|
}
|
||||||
|
@ -1357,7 +1369,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Failure -> {
|
is Resource.Failure -> {
|
||||||
showToast(activity, it.errorString, Toast.LENGTH_LONG)
|
showToast(it.errorString, Toast.LENGTH_LONG)
|
||||||
startPlayer()
|
startPlayer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1366,8 +1378,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
observe(viewModel.currentLinks) {
|
observe(viewModel.currentLinks) {
|
||||||
currentLinks = it
|
currentLinks = it
|
||||||
val turnVisible = it.isNotEmpty()
|
val turnVisible = it.isNotEmpty()
|
||||||
val wasGone = overlay_loading_skip_button?.isGone == true
|
val wasGone = binding?.overlayLoadingSkipButton?.isGone == true
|
||||||
overlay_loading_skip_button?.isVisible = turnVisible
|
binding?.overlayLoadingSkipButton?.isVisible = turnVisible
|
||||||
|
|
||||||
normalSafeApiCall {
|
normalSafeApiCall {
|
||||||
if (currentLinks.any { link ->
|
if (currentLinks.any { link ->
|
||||||
|
@ -1380,7 +1392,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (turnVisible && wasGone) {
|
if (turnVisible && wasGone) {
|
||||||
overlay_loading_skip_button?.requestFocus()
|
binding?.overlayLoadingSkipButton?.requestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1410,4 +1422,4 @@ class GeneratorPlayer : FullScreenPlayer() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,158 +0,0 @@
|
||||||
package com.lagradost.cloudstream3.ui.player
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.core.view.isGone
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.core.widget.ContentLoadingProgressBar
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.google.android.material.button.MaterialButton
|
|
||||||
import com.lagradost.cloudstream3.R
|
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultEpisode
|
|
||||||
import com.lagradost.cloudstream3.ui.result.getDisplayPosition
|
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
|
||||||
import kotlinx.android.synthetic.main.player_episodes_large.view.episode_holder_large
|
|
||||||
import kotlinx.android.synthetic.main.player_episodes_large.view.episode_progress
|
|
||||||
import kotlinx.android.synthetic.main.player_episodes_small.view.episode_holder
|
|
||||||
import kotlinx.android.synthetic.main.result_episode_large.view.*
|
|
||||||
|
|
||||||
|
|
||||||
data class PlayerEpisodeClickEvent(val action: Int, val data: Any)
|
|
||||||
|
|
||||||
class PlayerEpisodeAdapter(
|
|
||||||
private val items: MutableList<Any> = mutableListOf(),
|
|
||||||
private val clickCallback: (PlayerEpisodeClickEvent) -> Unit,
|
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
|
||||||
return PlayerEpisodeCardViewHolder(
|
|
||||||
LayoutInflater.from(parent.context)
|
|
||||||
.inflate(R.layout.player_episodes, parent, false),
|
|
||||||
clickCallback,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
|
||||||
println("HOLDER $holder $position")
|
|
||||||
|
|
||||||
when (holder) {
|
|
||||||
is PlayerEpisodeCardViewHolder -> {
|
|
||||||
holder.bind(items[position])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
return items.size
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateList(newList: List<Any>) {
|
|
||||||
println("Updated list $newList")
|
|
||||||
val diffResult = DiffUtil.calculateDiff(EpisodeDiffCallback(this.items, newList))
|
|
||||||
items.clear()
|
|
||||||
items.addAll(newList)
|
|
||||||
|
|
||||||
diffResult.dispatchUpdatesTo(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
class PlayerEpisodeCardViewHolder
|
|
||||||
constructor(
|
|
||||||
itemView: View,
|
|
||||||
private val clickCallback: (PlayerEpisodeClickEvent) -> Unit,
|
|
||||||
) : RecyclerView.ViewHolder(itemView) {
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
fun bind(card: Any) {
|
|
||||||
if (card is ResultEpisode) {
|
|
||||||
val (parentView, otherView) = if (card.poster == null) {
|
|
||||||
itemView.episode_holder to itemView.episode_holder_large
|
|
||||||
} else {
|
|
||||||
itemView.episode_holder_large to itemView.episode_holder
|
|
||||||
}
|
|
||||||
|
|
||||||
val episodeText: TextView? = parentView.episode_text
|
|
||||||
val episodeFiller: MaterialButton? = parentView.episode_filler
|
|
||||||
val episodeRating: TextView? = parentView.episode_rating
|
|
||||||
val episodeDescript: TextView? = parentView.episode_descript
|
|
||||||
val episodeProgress: ContentLoadingProgressBar? = parentView.episode_progress
|
|
||||||
val episodePoster: ImageView? = parentView.episode_poster
|
|
||||||
|
|
||||||
parentView.isVisible = true
|
|
||||||
otherView.isVisible = false
|
|
||||||
|
|
||||||
|
|
||||||
episodeText?.apply {
|
|
||||||
val name =
|
|
||||||
if (card.name == null) "${context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}"
|
|
||||||
|
|
||||||
text = name
|
|
||||||
isSelected = true
|
|
||||||
}
|
|
||||||
|
|
||||||
episodeFiller?.isVisible = card.isFiller == true
|
|
||||||
|
|
||||||
val displayPos = card.getDisplayPosition()
|
|
||||||
episodeProgress?.max = (card.duration / 1000).toInt()
|
|
||||||
episodeProgress?.progress = (displayPos / 1000).toInt()
|
|
||||||
episodeProgress?.isVisible = displayPos > 0L
|
|
||||||
episodePoster?.isVisible = episodePoster?.setImage(card.poster) == true
|
|
||||||
|
|
||||||
if (card.rating != null) {
|
|
||||||
episodeRating?.text = episodeRating?.context?.getString(R.string.rated_format)
|
|
||||||
?.format(card.rating.toFloat() / 10f)
|
|
||||||
} else {
|
|
||||||
episodeRating?.text = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
episodeRating?.isGone = episodeRating?.text.isNullOrBlank()
|
|
||||||
|
|
||||||
episodeDescript?.apply {
|
|
||||||
text = card.description.html()
|
|
||||||
isGone = text.isNullOrBlank()
|
|
||||||
//setOnClickListener {
|
|
||||||
// clickCallback.invoke(PlayerEpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
parentView.setOnClickListener {
|
|
||||||
clickCallback.invoke(PlayerEpisodeClickEvent(0, card))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTrueTvSettings()) {
|
|
||||||
parentView.isFocusable = true
|
|
||||||
parentView.isFocusableInTouchMode = true
|
|
||||||
parentView.touchscreenBlocksFocus = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EpisodeDiffCallback(
|
|
||||||
private val oldList: List<Any>,
|
|
||||||
private val newList: List<Any>
|
|
||||||
) :
|
|
||||||
DiffUtil.Callback() {
|
|
||||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
|
||||||
val a = oldList[oldItemPosition]
|
|
||||||
val b = newList[newItemPosition]
|
|
||||||
return if (a is ResultEpisode && b is ResultEpisode) {
|
|
||||||
a.id == b.id
|
|
||||||
} else {
|
|
||||||
a == b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getOldListSize() = oldList.size
|
|
||||||
|
|
||||||
override fun getNewListSize() = newList.size
|
|
||||||
|
|
||||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
|
||||||
oldList[oldItemPosition] == newList[newItemPosition]
|
|
||||||
}
|
|
|
@ -1,14 +1,10 @@
|
||||||
package com.lagradost.cloudstream3.ui.player.source_priority
|
package com.lagradost.cloudstream3.ui.player.source_priority
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.databinding.PlayerPrioritizeItemBinding
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils
|
import com.lagradost.cloudstream3.utils.AppUtils
|
||||||
import kotlinx.android.synthetic.main.player_prioritize_item.view.*
|
|
||||||
|
|
||||||
data class SourcePriority<T>(
|
data class SourcePriority<T>(
|
||||||
val data: T,
|
val data: T,
|
||||||
|
@ -20,7 +16,8 @@ class PriorityAdapter<T>(override val items: MutableList<SourcePriority<T>>) :
|
||||||
AppUtils.DiffAdapter<SourcePriority<T>>(items) {
|
AppUtils.DiffAdapter<SourcePriority<T>>(items) {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return PriorityViewHolder(
|
return PriorityViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(R.layout.player_prioritize_item, parent, false)
|
PlayerPrioritizeItemBinding.inflate(LayoutInflater.from(parent.context),parent,false),
|
||||||
|
//LayoutInflater.from(parent.context).inflate(R.layout.player_prioritize_item, parent, false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,27 +28,27 @@ class PriorityAdapter<T>(override val items: MutableList<SourcePriority<T>>) :
|
||||||
}
|
}
|
||||||
|
|
||||||
class PriorityViewHolder(
|
class PriorityViewHolder(
|
||||||
itemView: View,
|
val binding: PlayerPrioritizeItemBinding,
|
||||||
) : RecyclerView.ViewHolder(itemView) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
fun <T> bind(item: SourcePriority<T>) {
|
fun <T> bind(item: SourcePriority<T>) {
|
||||||
val plusButton: ImageView = itemView.add_button
|
/* val plusButton: ImageView = itemView.add_button
|
||||||
val subtractButton: ImageView = itemView.subtract_button
|
val subtractButton: ImageView = itemView.subtract_button
|
||||||
val priorityText: TextView = itemView.priority_text
|
val priorityText: TextView = itemView.priority_text
|
||||||
val priorityNumber: TextView = itemView.priority_number
|
val priorityNumber: TextView = itemView.priority_number*/
|
||||||
priorityText.text = item.name
|
binding.priorityText.text = item.name
|
||||||
|
|
||||||
fun updatePriority() {
|
fun updatePriority() {
|
||||||
priorityNumber.text = item.priority.toString()
|
binding.priorityNumber.text = item.priority.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePriority()
|
updatePriority()
|
||||||
plusButton.setOnClickListener {
|
binding.addButton.setOnClickListener {
|
||||||
// If someone clicks til the integer limit then they deserve to crash.
|
// If someone clicks til the integer limit then they deserve to crash.
|
||||||
item.priority++
|
item.priority++
|
||||||
updatePriority()
|
updatePriority()
|
||||||
}
|
}
|
||||||
|
|
||||||
subtractButton.setOnClickListener {
|
binding.subtractButton.setOnClickListener {
|
||||||
item.priority--
|
item.priority--
|
||||||
updatePriority()
|
updatePriority()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,13 @@ import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.res.ResourcesCompat
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.PlayerQualityProfileItemBinding
|
||||||
import com.lagradost.cloudstream3.ui.result.UiImage
|
import com.lagradost.cloudstream3.ui.result.UiImage
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils
|
import com.lagradost.cloudstream3.utils.AppUtils
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import kotlinx.android.synthetic.main.player_quality_profile_item.view.card_view
|
|
||||||
import kotlinx.android.synthetic.main.player_quality_profile_item.view.outline
|
|
||||||
import kotlinx.android.synthetic.main.player_quality_profile_item.view.profile_image_background
|
|
||||||
import kotlinx.android.synthetic.main.player_quality_profile_item.view.profile_text
|
|
||||||
import kotlinx.android.synthetic.main.player_quality_profile_item.view.text_is_mobile_data
|
|
||||||
import kotlinx.android.synthetic.main.player_quality_profile_item.view.text_is_wifi
|
|
||||||
|
|
||||||
class ProfilesAdapter(
|
class ProfilesAdapter(
|
||||||
override val items: MutableList<QualityDataHelper.QualityProfile>,
|
override val items: MutableList<QualityDataHelper.QualityProfile>,
|
||||||
|
@ -34,8 +28,9 @@ class ProfilesAdapter(
|
||||||
}) {
|
}) {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return ProfilesViewHolder(
|
return ProfilesViewHolder(
|
||||||
LayoutInflater.from(parent.context)
|
PlayerQualityProfileItemBinding.inflate(LayoutInflater.from(parent.context),parent,false)
|
||||||
.inflate(R.layout.player_quality_profile_item, parent, false)
|
//LayoutInflater.from(parent.context)
|
||||||
|
// .inflate(R.layout.player_quality_profile_item, parent, false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,8 +47,8 @@ class ProfilesAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ProfilesViewHolder(
|
inner class ProfilesViewHolder(
|
||||||
itemView: View,
|
val binding: PlayerQualityProfileItemBinding,
|
||||||
) : RecyclerView.ViewHolder(itemView) {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
private val art = listOf(
|
private val art = listOf(
|
||||||
R.drawable.profile_bg_teal,
|
R.drawable.profile_bg_teal,
|
||||||
R.drawable.profile_bg_blue,
|
R.drawable.profile_bg_blue,
|
||||||
|
@ -65,12 +60,12 @@ class ProfilesAdapter(
|
||||||
)
|
)
|
||||||
|
|
||||||
fun bind(item: QualityDataHelper.QualityProfile, index: Int) {
|
fun bind(item: QualityDataHelper.QualityProfile, index: Int) {
|
||||||
val priorityText: TextView = itemView.profile_text
|
val priorityText: TextView = binding.profileText
|
||||||
val profileBg: ImageView = itemView.profile_image_background
|
val profileBg: ImageView = binding.profileImageBackground
|
||||||
val wifiText: TextView = itemView.text_is_wifi
|
val wifiText: TextView = binding.textIsWifi
|
||||||
val dataText: TextView = itemView.text_is_mobile_data
|
val dataText: TextView = binding.textIsMobileData
|
||||||
val outline: View = itemView.outline
|
val outline: View = binding.outline
|
||||||
val cardView: View = itemView.card_view
|
val cardView: View = binding.cardView
|
||||||
|
|
||||||
priorityText.text = item.name.asString(itemView.context)
|
priorityText.text = item.name.asString(itemView.context)
|
||||||
dataText.isVisible = item.type == QualityDataHelper.QualityProfileType.Data
|
dataText.isVisible = item.type == QualityDataHelper.QualityProfileType.Data
|
||||||
|
|
|
@ -1,20 +1,16 @@
|
||||||
package com.lagradost.cloudstream3.ui.player.source_priority
|
package com.lagradost.cloudstream3.ui.player.source_priority
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.view.View
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.annotation.StyleRes
|
import androidx.annotation.StyleRes
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.PlayerQualityProfileDialogBinding
|
||||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getProfileName
|
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getProfileName
|
||||||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getProfiles
|
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getProfiles
|
||||||
import com.lagradost.cloudstream3.ui.result.txt
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
import kotlinx.android.synthetic.main.player_quality_profile_dialog.*
|
|
||||||
|
|
||||||
class QualityProfileDialog(
|
class QualityProfileDialog(
|
||||||
val activity: FragmentActivity,
|
val activity: FragmentActivity,
|
||||||
|
@ -24,83 +20,86 @@ class QualityProfileDialog(
|
||||||
private val profileSelectionCallback: (QualityDataHelper.QualityProfile) -> Unit
|
private val profileSelectionCallback: (QualityDataHelper.QualityProfile) -> Unit
|
||||||
) : Dialog(activity, themeRes) {
|
) : Dialog(activity, themeRes) {
|
||||||
override fun show() {
|
override fun show() {
|
||||||
setContentView(R.layout.player_quality_profile_dialog)
|
|
||||||
val profilesRecyclerView: RecyclerView = profiles_recyclerview
|
val binding = PlayerQualityProfileDialogBinding.inflate(this.layoutInflater, null, false)
|
||||||
|
|
||||||
|
setContentView(binding.root)//R.layout.player_quality_profile_dialog)
|
||||||
|
/*val profilesRecyclerView: RecyclerView = profiles_recyclerview
|
||||||
val useBtt: View = use_btt
|
val useBtt: View = use_btt
|
||||||
val editBtt: View = edit_btt
|
val editBtt: View = edit_btt
|
||||||
val cancelBtt: View = cancel_btt
|
val cancelBtt: View = cancel_btt
|
||||||
val defaultBtt: View = set_default_btt
|
val defaultBtt: View = set_default_btt
|
||||||
val currentProfileText: TextView = currently_selected_profile_text
|
val currentProfileText: TextView = currently_selected_profile_text
|
||||||
val selectedItemActionsHolder: View = selected_item_holder
|
val selectedItemActionsHolder: View = selected_item_holder*/
|
||||||
|
binding.apply {
|
||||||
fun getCurrentProfile(): QualityDataHelper.QualityProfile? {
|
fun getCurrentProfile(): QualityDataHelper.QualityProfile? {
|
||||||
return (profilesRecyclerView.adapter as? ProfilesAdapter)?.getCurrentProfile()
|
return (profilesRecyclerview.adapter as? ProfilesAdapter)?.getCurrentProfile()
|
||||||
}
|
|
||||||
|
|
||||||
fun refreshProfiles() {
|
|
||||||
currentProfileText.text = getProfileName(usedProfile).asString(context)
|
|
||||||
(profilesRecyclerView.adapter as? ProfilesAdapter)?.updateList(getProfiles())
|
|
||||||
}
|
|
||||||
|
|
||||||
profilesRecyclerView.adapter = ProfilesAdapter(
|
|
||||||
mutableListOf(),
|
|
||||||
usedProfile,
|
|
||||||
) { oldIndex: Int?, newIndex: Int ->
|
|
||||||
profilesRecyclerView.adapter?.notifyItemChanged(newIndex)
|
|
||||||
selectedItemActionsHolder.alpha = 1f
|
|
||||||
if (oldIndex != null) {
|
|
||||||
profilesRecyclerView.adapter?.notifyItemChanged(oldIndex)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
refreshProfiles()
|
fun refreshProfiles() {
|
||||||
|
currentlySelectedProfileText.text = getProfileName(usedProfile).asString(context)
|
||||||
editBtt.setOnClickListener {
|
(profilesRecyclerview.adapter as? ProfilesAdapter)?.updateList(getProfiles())
|
||||||
getCurrentProfile()?.let { profile ->
|
}
|
||||||
SourcePriorityDialog(context, themeRes, links, profile) {
|
|
||||||
refreshProfiles()
|
profilesRecyclerview.adapter = ProfilesAdapter(
|
||||||
}.show()
|
mutableListOf(),
|
||||||
|
usedProfile,
|
||||||
|
) { oldIndex: Int?, newIndex: Int ->
|
||||||
|
profilesRecyclerview.adapter?.notifyItemChanged(newIndex)
|
||||||
|
selectedItemHolder.alpha = 1f
|
||||||
|
if (oldIndex != null) {
|
||||||
|
profilesRecyclerview.adapter?.notifyItemChanged(oldIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshProfiles()
|
||||||
|
|
||||||
|
editBtt.setOnClickListener {
|
||||||
|
getCurrentProfile()?.let { profile ->
|
||||||
|
SourcePriorityDialog(context, themeRes, links, profile) {
|
||||||
|
refreshProfiles()
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
defaultBtt.setOnClickListener {
|
setDefaultBtt.setOnClickListener {
|
||||||
val currentProfile = getCurrentProfile() ?: return@setOnClickListener
|
val currentProfile = getCurrentProfile() ?: return@setOnClickListener
|
||||||
val choices = QualityDataHelper.QualityProfileType.values()
|
val choices = QualityDataHelper.QualityProfileType.values()
|
||||||
.filter { it != QualityDataHelper.QualityProfileType.None }
|
.filter { it != QualityDataHelper.QualityProfileType.None }
|
||||||
val choiceNames = choices.map { txt(it.stringRes).asString(context) }
|
val choiceNames = choices.map { txt(it.stringRes).asString(context) }
|
||||||
|
|
||||||
activity.showBottomDialog(
|
activity.showBottomDialog(
|
||||||
choiceNames,
|
choiceNames,
|
||||||
choices.indexOf(currentProfile.type),
|
choices.indexOf(currentProfile.type),
|
||||||
txt(R.string.set_default).asString(context),
|
txt(R.string.set_default).asString(context),
|
||||||
false,
|
false,
|
||||||
{},
|
{},
|
||||||
{ index ->
|
{ index ->
|
||||||
val pickedChoice = choices.getOrNull(index) ?: return@showBottomDialog
|
val pickedChoice = choices.getOrNull(index) ?: return@showBottomDialog
|
||||||
// Remove previous picks
|
// Remove previous picks
|
||||||
if (pickedChoice.unique) {
|
if (pickedChoice.unique) {
|
||||||
getProfiles().filter { it.type == pickedChoice }.forEach {
|
getProfiles().filter { it.type == pickedChoice }.forEach {
|
||||||
QualityDataHelper.setQualityProfileType(it.id, null)
|
QualityDataHelper.setQualityProfileType(it.id, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
QualityDataHelper.setQualityProfileType(currentProfile.id, pickedChoice)
|
QualityDataHelper.setQualityProfileType(currentProfile.id, pickedChoice)
|
||||||
refreshProfiles()
|
refreshProfiles()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelBtt.setOnClickListener {
|
cancelBtt.setOnClickListener {
|
||||||
this.dismissSafe()
|
this@QualityProfileDialog.dismissSafe()
|
||||||
}
|
}
|
||||||
|
|
||||||
useBtt.setOnClickListener {
|
useBtt.setOnClickListener {
|
||||||
getCurrentProfile()?.let {
|
getCurrentProfile()?.let {
|
||||||
profileSelectionCallback.invoke(it)
|
profileSelectionCallback.invoke(it)
|
||||||
this.dismissSafe()
|
this@QualityProfileDialog.dismissSafe()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.show()
|
super.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,24 +2,18 @@ package com.lagradost.cloudstream3.ui.player.source_priority
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.LayoutInflater
|
||||||
import android.widget.EditText
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.annotation.StyleRes
|
import androidx.annotation.StyleRes
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import androidx.work.impl.constraints.controllers.ConstraintController
|
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.PlayerSelectSourcePriorityBinding
|
||||||
import com.lagradost.cloudstream3.ui.result.txt
|
import com.lagradost.cloudstream3.ui.result.txt
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
import kotlinx.android.synthetic.main.player_select_source_priority.*
|
|
||||||
|
|
||||||
class SourcePriorityDialog(
|
class SourcePriorityDialog(
|
||||||
ctx: Context,
|
val ctx: Context,
|
||||||
@StyleRes themeRes: Int,
|
@StyleRes themeRes: Int,
|
||||||
val links: List<ExtractorLink>,
|
val links: List<ExtractorLink>,
|
||||||
private val profile: QualityDataHelper.QualityProfile,
|
private val profile: QualityDataHelper.QualityProfile,
|
||||||
|
@ -30,13 +24,14 @@ class SourcePriorityDialog(
|
||||||
private val updatedCallback: () -> Unit
|
private val updatedCallback: () -> Unit
|
||||||
) : Dialog(ctx, themeRes) {
|
) : Dialog(ctx, themeRes) {
|
||||||
override fun show() {
|
override fun show() {
|
||||||
setContentView(R.layout.player_select_source_priority)
|
val binding = PlayerSelectSourcePriorityBinding.inflate(LayoutInflater.from(ctx), null, false)
|
||||||
val sourcesRecyclerView: RecyclerView = sort_sources
|
setContentView(binding.root)
|
||||||
val qualitiesRecyclerView: RecyclerView = sort_qualities
|
val sourcesRecyclerView = binding.sortSources
|
||||||
val profileText: EditText = profile_text_editable
|
val qualitiesRecyclerView = binding.sortQualities
|
||||||
val saveBtt: View = save_btt
|
val profileText = binding.profileTextEditable
|
||||||
val exitBtt: View = close_btt
|
val saveBtt = binding.saveBtt
|
||||||
val helpBtt: View = help_btt
|
val exitBtt = binding.closeBtt
|
||||||
|
val helpBtt = binding.helpBtt
|
||||||
|
|
||||||
profileText.setText(QualityDataHelper.getProfileName(profile.id).asString(context))
|
profileText.setText(QualityDataHelper.getProfileName(profile.id).asString(context))
|
||||||
profileText.hint = txt(R.string.profile_number, profile.id).asString(context)
|
profileText.hint = txt(R.string.profile_number, profile.id).asString(context)
|
||||||
|
|
|
@ -19,8 +19,10 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
|
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
|
||||||
import com.lagradost.cloudstream3.APIHolder.filterSearchResultByFilmQuality
|
import com.lagradost.cloudstream3.APIHolder.filterSearchResultByFilmQuality
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||||
|
import com.lagradost.cloudstream3.CommonActivity.activity
|
||||||
import com.lagradost.cloudstream3.HomePageList
|
import com.lagradost.cloudstream3.HomePageList
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.QuickSearchBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
|
@ -37,7 +39,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
||||||
import kotlinx.android.synthetic.main.quick_search.*
|
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
|
||||||
class QuickSearchFragment : Fragment() {
|
class QuickSearchFragment : Fragment() {
|
||||||
|
@ -45,6 +46,13 @@ class QuickSearchFragment : Fragment() {
|
||||||
const val AUTOSEARCH_KEY = "autosearch"
|
const val AUTOSEARCH_KEY = "autosearch"
|
||||||
const val PROVIDER_KEY = "providers"
|
const val PROVIDER_KEY = "providers"
|
||||||
|
|
||||||
|
fun pushSearch(
|
||||||
|
autoSearch: String? = null,
|
||||||
|
providers: Array<String>? = null
|
||||||
|
) {
|
||||||
|
pushSearch(activity, autoSearch, providers)
|
||||||
|
}
|
||||||
|
|
||||||
fun pushSearch(
|
fun pushSearch(
|
||||||
activity: Activity?,
|
activity: Activity?,
|
||||||
autoSearch: String? = null,
|
autoSearch: String? = null,
|
||||||
|
@ -72,6 +80,8 @@ class QuickSearchFragment : Fragment() {
|
||||||
|
|
||||||
private var providers: Set<String>? = null
|
private var providers: Set<String>? = null
|
||||||
private lateinit var searchViewModel: SearchViewModel
|
private lateinit var searchViewModel: SearchViewModel
|
||||||
|
var binding: QuickSearchBinding? = null
|
||||||
|
|
||||||
|
|
||||||
private var bottomSheetDialog: BottomSheetDialog? = null
|
private var bottomSheetDialog: BottomSheetDialog? = null
|
||||||
|
|
||||||
|
@ -79,13 +89,21 @@ class QuickSearchFragment : Fragment() {
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
activity?.window?.setSoftInputMode(
|
activity?.window?.setSoftInputMode(
|
||||||
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
|
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
|
||||||
)
|
)
|
||||||
searchViewModel = ViewModelProvider(this)[SearchViewModel::class.java]
|
searchViewModel = ViewModelProvider(this)[SearchViewModel::class.java]
|
||||||
bottomSheetDialog?.ownShow()
|
bottomSheetDialog?.ownShow()
|
||||||
return inflater.inflate(R.layout.quick_search, container, false)
|
val localBinding = QuickSearchBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root
|
||||||
|
//return inflater.inflate(R.layout.quick_search, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
@ -111,7 +129,7 @@ class QuickSearchFragment : Fragment() {
|
||||||
activity?.getSpanCount()?.let {
|
activity?.getSpanCount()?.let {
|
||||||
HomeFragment.currentSpan = it
|
HomeFragment.currentSpan = it
|
||||||
}
|
}
|
||||||
quick_search_autofit_results.spanCount = HomeFragment.currentSpan
|
binding?.quickSearchAutofitResults?.spanCount = HomeFragment.currentSpan
|
||||||
HomeFragment.currentSpan = HomeFragment.currentSpan
|
HomeFragment.currentSpan = HomeFragment.currentSpan
|
||||||
HomeFragment.configEvent.invoke(HomeFragment.currentSpan)
|
HomeFragment.configEvent.invoke(HomeFragment.currentSpan)
|
||||||
}
|
}
|
||||||
|
@ -123,7 +141,7 @@ class QuickSearchFragment : Fragment() {
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
context?.fixPaddingStatusbar(quick_search_root)
|
fixPaddingStatusbar(binding?.quickSearchRoot)
|
||||||
fixGrid()
|
fixGrid()
|
||||||
|
|
||||||
arguments?.getStringArray(PROVIDER_KEY)?.let {
|
arguments?.getStringArray(PROVIDER_KEY)?.let {
|
||||||
|
@ -136,23 +154,25 @@ class QuickSearchFragment : Fragment() {
|
||||||
} else false
|
} else false
|
||||||
|
|
||||||
if (isSingleProvider) {
|
if (isSingleProvider) {
|
||||||
quick_search_autofit_results.adapter = activity?.let {
|
binding?.quickSearchAutofitResults?.apply {
|
||||||
SearchAdapter(
|
adapter = SearchAdapter(
|
||||||
ArrayList(),
|
ArrayList(),
|
||||||
quick_search_autofit_results,
|
this,
|
||||||
) { callback ->
|
) { callback ->
|
||||||
SearchHelper.handleSearchClickCallback(activity, callback)
|
SearchHelper.handleSearchClickCallback(callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
quick_search?.queryHint = getString(R.string.search_hint_site).format(providers?.first())
|
binding?.quickSearch?.queryHint =
|
||||||
|
getString(R.string.search_hint_site).format(providers?.first())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quick_search_master_recycler?.adapter =
|
binding?.quickSearchMasterRecycler?.adapter =
|
||||||
ParentItemAdapter(mutableListOf(), { callback ->
|
ParentItemAdapter(mutableListOf(), { callback ->
|
||||||
SearchHelper.handleSearchClickCallback(activity, callback)
|
SearchHelper.handleSearchClickCallback(callback)
|
||||||
//when (callback.action) {
|
//when (callback.action) {
|
||||||
//SEARCH_ACTION_LOAD -> {
|
//SEARCH_ACTION_LOAD -> {
|
||||||
// clickCallback?.invoke(callback)
|
// clickCallback?.invoke(callback)
|
||||||
|
@ -164,18 +184,17 @@ class QuickSearchFragment : Fragment() {
|
||||||
bottomSheetDialog = null
|
bottomSheetDialog = null
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
quick_search_master_recycler?.layoutManager = GridLayoutManager(context, 1)
|
binding?.quickSearchMasterRecycler?.layoutManager = GridLayoutManager(context, 1)
|
||||||
}
|
}
|
||||||
|
binding?.quickSearchAutofitResults?.isVisible = isSingleProvider
|
||||||
quick_search_autofit_results?.isVisible = isSingleProvider
|
binding?.quickSearchMasterRecycler?.isGone = isSingleProvider
|
||||||
quick_search_master_recycler?.isGone = isSingleProvider
|
|
||||||
|
|
||||||
val listLock = ReentrantLock()
|
val listLock = ReentrantLock()
|
||||||
observe(searchViewModel.currentSearch) { list ->
|
observe(searchViewModel.currentSearch) { list ->
|
||||||
try {
|
try {
|
||||||
// https://stackoverflow.com/questions/6866238/concurrent-modification-exception-adding-to-an-arraylist
|
// https://stackoverflow.com/questions/6866238/concurrent-modification-exception-adding-to-an-arraylist
|
||||||
listLock.lock()
|
listLock.lock()
|
||||||
(quick_search_master_recycler?.adapter as ParentItemAdapter?)?.apply {
|
(binding?.quickSearchMasterRecycler?.adapter as ParentItemAdapter?)?.apply {
|
||||||
updateList(list.map { ongoing ->
|
updateList(list.map { ongoing ->
|
||||||
val ongoingList = HomePageList(
|
val ongoingList = HomePageList(
|
||||||
ongoing.apiName,
|
ongoing.apiName,
|
||||||
|
@ -192,19 +211,18 @@ class QuickSearchFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val searchExitIcon =
|
val searchExitIcon =
|
||||||
quick_search?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
|
binding?.quickSearch?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
|
||||||
|
|
||||||
//val searchMagIcon =
|
//val searchMagIcon =
|
||||||
// quick_search?.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
|
// binding.quickSearch.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
|
||||||
|
|
||||||
//searchMagIcon?.scaleX = 0.65f
|
//searchMagIcon?.scaleX = 0.65f
|
||||||
//searchMagIcon?.scaleY = 0.65f
|
//searchMagIcon?.scaleY = 0.65f
|
||||||
|
|
||||||
|
binding?.quickSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
quick_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
|
||||||
override fun onQueryTextSubmit(query: String): Boolean {
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
if (search(context, query, false))
|
if (search(context, query, false))
|
||||||
UIHelper.hideKeyboard(quick_search)
|
UIHelper.hideKeyboard(binding?.quickSearch)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,27 +232,28 @@ class QuickSearchFragment : Fragment() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
binding?.quickSearchLoadingBar?.alpha = 0f
|
||||||
quick_search_loading_bar.alpha = 0f
|
|
||||||
observe(searchViewModel.searchResponse) {
|
observe(searchViewModel.searchResponse) {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
it.value.let { data ->
|
it.value.let { data ->
|
||||||
(quick_search_autofit_results?.adapter as? SearchAdapter)?.updateList(
|
(binding?.quickSearchAutofitResults?.adapter as? SearchAdapter)?.updateList(
|
||||||
context?.filterSearchResultByFilmQuality(data) ?: data
|
context?.filterSearchResultByFilmQuality(data) ?: data
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
searchExitIcon?.alpha = 1f
|
searchExitIcon?.alpha = 1f
|
||||||
quick_search_loading_bar?.alpha = 0f
|
binding?.quickSearchLoadingBar?.alpha = 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Failure -> {
|
is Resource.Failure -> {
|
||||||
// Toast.makeText(activity, "Server error", Toast.LENGTH_LONG).show()
|
// Toast.makeText(activity, "Server error", Toast.LENGTH_LONG).show()
|
||||||
searchExitIcon?.alpha = 1f
|
searchExitIcon?.alpha = 1f
|
||||||
quick_search_loading_bar?.alpha = 0f
|
binding?.quickSearchLoadingBar?.alpha = 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Loading -> {
|
is Resource.Loading -> {
|
||||||
searchExitIcon?.alpha = 0f
|
searchExitIcon?.alpha = 0f
|
||||||
quick_search_loading_bar?.alpha = 1f
|
binding?.quickSearchLoadingBar?.alpha = 1f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,13 +265,12 @@ class QuickSearchFragment : Fragment() {
|
||||||
// UIHelper.showInputMethod(view.findFocus())
|
// UIHelper.showInputMethod(view.findFocus())
|
||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
binding?.quickSearchBack?.setOnClickListener {
|
||||||
quick_search_back.setOnClickListener {
|
|
||||||
activity?.popCurrentPage()
|
activity?.popCurrentPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
arguments?.getString(AUTOSEARCH_KEY)?.let {
|
arguments?.getString(AUTOSEARCH_KEY)?.let {
|
||||||
quick_search?.setQuery(it, true)
|
binding?.quickSearch?.setQuery(it, true)
|
||||||
arguments?.remove(AUTOSEARCH_KEY)
|
arguments?.remove(AUTOSEARCH_KEY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,18 +3,16 @@ package com.lagradost.cloudstream3.ui.result
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.ActorData
|
import com.lagradost.cloudstream3.ActorData
|
||||||
import com.lagradost.cloudstream3.ActorRole
|
import com.lagradost.cloudstream3.ActorRole
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.CastItemBinding
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import kotlinx.android.synthetic.main.cast_item.view.*
|
|
||||||
|
|
||||||
class ActorAdaptor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
class ActorAdaptor(private val focusCallback : (View?) -> Unit = {}) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
data class ActorMetaData(
|
data class ActorMetaData(
|
||||||
var isInverted: Boolean,
|
var isInverted: Boolean,
|
||||||
val actor: ActorData,
|
val actor: ActorData,
|
||||||
|
@ -24,7 +22,7 @@ class ActorAdaptor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return CardViewHolder(
|
return CardViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(R.layout.cast_item, parent, false),
|
CastItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), focusCallback
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,15 +66,10 @@ class ActorAdaptor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
private class CardViewHolder
|
private class CardViewHolder
|
||||||
constructor(
|
constructor(
|
||||||
itemView: View,
|
val binding: CastItemBinding,
|
||||||
|
private val focusCallback : (View?) -> Unit = {}
|
||||||
) :
|
) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
private val actorImage: ImageView = itemView.actor_image
|
|
||||||
private val actorName: TextView = itemView.actor_name
|
|
||||||
private val actorExtra: TextView = itemView.actor_extra
|
|
||||||
private val voiceActorImage: ImageView = itemView.voice_actor_image
|
|
||||||
private val voiceActorImageHolder: View = itemView.voice_actor_image_holder
|
|
||||||
private val voiceActorName: TextView = itemView.voice_actor_name
|
|
||||||
|
|
||||||
fun bind(actor: ActorData, isInverted: Boolean, position: Int, callback: (Int) -> Unit) {
|
fun bind(actor: ActorData, isInverted: Boolean, position: Int, callback: (Int) -> Unit) {
|
||||||
val (mainImg, vaImage) = if (!isInverted || actor.voiceActor?.image.isNullOrBlank()) {
|
val (mainImg, vaImage) = if (!isInverted || actor.voiceActor?.image.isNullOrBlank()) {
|
||||||
|
@ -85,43 +78,53 @@ class ActorAdaptor() : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
Pair(actor.voiceActor?.image, actor.actor.image)
|
Pair(actor.voiceActor?.image, actor.actor.image)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
itemView.setOnFocusChangeListener { v, hasFocus ->
|
||||||
|
if(hasFocus) {
|
||||||
|
focusCallback(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
callback(position)
|
callback(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
actorImage.setImage(mainImg)
|
binding.apply {
|
||||||
|
actorImage.setImage(mainImg)
|
||||||
|
|
||||||
actorName.text = actor.actor.name
|
actorName.text = actor.actor.name
|
||||||
actor.role?.let {
|
actor.role?.let {
|
||||||
actorExtra.context?.getString(
|
actorExtra.context?.getString(
|
||||||
when (it) {
|
when (it) {
|
||||||
ActorRole.Main -> {
|
ActorRole.Main -> {
|
||||||
R.string.actor_main
|
R.string.actor_main
|
||||||
}
|
}
|
||||||
ActorRole.Supporting -> {
|
|
||||||
R.string.actor_supporting
|
ActorRole.Supporting -> {
|
||||||
}
|
R.string.actor_supporting
|
||||||
ActorRole.Background -> {
|
}
|
||||||
R.string.actor_background
|
|
||||||
|
ActorRole.Background -> {
|
||||||
|
R.string.actor_background
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
)?.let { text ->
|
||||||
|
actorExtra.isVisible = true
|
||||||
|
actorExtra.text = text
|
||||||
}
|
}
|
||||||
)?.let { text ->
|
} ?: actor.roleString?.let {
|
||||||
actorExtra.isVisible = true
|
actorExtra.isVisible = true
|
||||||
actorExtra.text = text
|
actorExtra.text = it
|
||||||
|
} ?: run {
|
||||||
|
actorExtra.isVisible = false
|
||||||
}
|
}
|
||||||
} ?: actor.roleString?.let {
|
|
||||||
actorExtra.isVisible = true
|
|
||||||
actorExtra.text = it
|
|
||||||
} ?: run {
|
|
||||||
actorExtra.isVisible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actor.voiceActor == null) {
|
if (actor.voiceActor == null) {
|
||||||
voiceActorImageHolder.isVisible = false
|
voiceActorImageHolder.isVisible = false
|
||||||
voiceActorName.isVisible = false
|
voiceActorName.isVisible = false
|
||||||
} else {
|
} else {
|
||||||
voiceActorName.text = actor.voiceActor.name
|
voiceActorName.text = actor.voiceActor.name
|
||||||
voiceActorImageHolder.isVisible = voiceActorImage.setImage(vaImage)
|
voiceActorImageHolder.isVisible = voiceActorImage.setImage(vaImage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,34 +3,24 @@ package com.lagradost.cloudstream3.ui.result
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.widget.ContentLoadingProgressBar
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.button.MaterialButton
|
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.ResultEpisodeBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.ResultEpisodeLargeBinding
|
||||||
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonViewHolder
|
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK
|
||||||
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
|
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
|
||||||
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
|
||||||
import kotlinx.android.synthetic.main.result_episode.view.*
|
|
||||||
import kotlinx.android.synthetic.main.result_episode.view.episode_text
|
|
||||||
import kotlinx.android.synthetic.main.result_episode_large.view.*
|
|
||||||
import kotlinx.android.synthetic.main.result_episode_large.view.episode_filler
|
|
||||||
import kotlinx.android.synthetic.main.result_episode_large.view.episode_progress
|
|
||||||
import kotlinx.android.synthetic.main.result_episode_large.view.result_episode_download
|
|
||||||
import kotlinx.android.synthetic.main.result_episode_large.view.result_episode_progress_downloaded
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
|
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
|
||||||
|
@ -59,7 +49,8 @@ const val ACTION_PLAY_EPISODE_IN_WEB_VIDEO = 16
|
||||||
const val ACTION_PLAY_EPISODE_IN_MPV = 17
|
const val ACTION_PLAY_EPISODE_IN_MPV = 17
|
||||||
|
|
||||||
const val ACTION_MARK_AS_WATCHED = 18
|
const val ACTION_MARK_AS_WATCHED = 18
|
||||||
|
const val TV_EP_SIZE_LARGE = 400
|
||||||
|
const val TV_EP_SIZE_SMALL = 300
|
||||||
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
|
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
|
||||||
|
|
||||||
class EpisodeAdapter(
|
class EpisodeAdapter(
|
||||||
|
@ -88,49 +79,10 @@ class EpisodeAdapter(
|
||||||
|
|
||||||
var cardList: MutableList<ResultEpisode> = mutableListOf()
|
var cardList: MutableList<ResultEpisode> = mutableListOf()
|
||||||
|
|
||||||
private val mBoundViewHolders: HashSet<DownloadButtonViewHolder> = HashSet()
|
|
||||||
private fun getAllBoundViewHolders(): Set<DownloadButtonViewHolder?>? {
|
|
||||||
return Collections.unmodifiableSet(mBoundViewHolders)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun killAdapter() {
|
|
||||||
getAllBoundViewHolders()?.forEach { view ->
|
|
||||||
view?.downloadButton?.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
|
||||||
if (holder.itemView.hasFocus()) {
|
if (holder.itemView.hasFocus()) {
|
||||||
holder.itemView.clearFocus()
|
holder.itemView.clearFocus()
|
||||||
}
|
}
|
||||||
//(holder.itemView as? FrameLayout?)?.descendantFocusability =
|
|
||||||
// ViewGroup.FOCUS_BLOCK_DESCENDANTS
|
|
||||||
|
|
||||||
if (holder is DownloadButtonViewHolder) {
|
|
||||||
holder.downloadButton.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
|
||||||
if (holder is DownloadButtonViewHolder) {
|
|
||||||
holder.downloadButton.dispose()
|
|
||||||
mBoundViewHolders.remove(holder)
|
|
||||||
//(holder.itemView as? FrameLayout?)?.descendantFocusability =
|
|
||||||
// ViewGroup.FOCUS_BLOCK_DESCENDANTS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
|
|
||||||
if (holder is DownloadButtonViewHolder) {
|
|
||||||
//println("onViewAttachedToWindow = ${holder.absoluteAdapterPosition}")
|
|
||||||
//holder.itemView.post {
|
|
||||||
// if (holder.itemView.isAttachedToWindow)
|
|
||||||
// (holder.itemView as? FrameLayout?)?.descendantFocusability =
|
|
||||||
// ViewGroup.FOCUS_AFTER_DESCENDANTS
|
|
||||||
//}
|
|
||||||
|
|
||||||
holder.reattachDownloadButton()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateList(newList: List<ResultEpisode>) {
|
fun updateList(newList: List<ResultEpisode>) {
|
||||||
|
@ -144,27 +96,62 @@ class EpisodeAdapter(
|
||||||
diffResult.dispatchUpdatesTo(this)
|
diffResult.dispatchUpdatesTo(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
var layout = R.layout.result_episode_both
|
private fun getItem(position: Int): ResultEpisode {
|
||||||
|
return cardList[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemViewType(position: Int): Int {
|
||||||
|
val item = getItem(position)
|
||||||
|
return if (item.poster.isNullOrBlank()) 0 else 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// private val layout = R.layout.result_episode_both
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
/*val layout = if (cardList.filter { it.poster != null }.size >= cardList.size / 2)
|
/*val layout = if (cardList.filter { it.poster != null }.size >= cardList.size / 2)
|
||||||
R.layout.result_episode_large
|
R.layout.result_episode_large
|
||||||
else R.layout.result_episode*/
|
else R.layout.result_episode*/
|
||||||
|
|
||||||
return EpisodeCardViewHolder(
|
return when (viewType) {
|
||||||
LayoutInflater.from(parent.context)
|
0 -> {
|
||||||
.inflate(layout, parent, false),
|
EpisodeCardViewHolderSmall(
|
||||||
hasDownloadSupport,
|
ResultEpisodeBinding.inflate(
|
||||||
clickCallback,
|
LayoutInflater.from(parent.context),
|
||||||
downloadClickCallback
|
parent,
|
||||||
)
|
false
|
||||||
|
),
|
||||||
|
hasDownloadSupport,
|
||||||
|
clickCallback,
|
||||||
|
downloadClickCallback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
1 -> {
|
||||||
|
EpisodeCardViewHolderLarge(
|
||||||
|
ResultEpisodeLargeBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
),
|
||||||
|
hasDownloadSupport,
|
||||||
|
clickCallback,
|
||||||
|
downloadClickCallback
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw NotImplementedError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
when (holder) {
|
when (holder) {
|
||||||
is EpisodeCardViewHolder -> {
|
is EpisodeCardViewHolderLarge -> {
|
||||||
holder.bind(cardList[position])
|
holder.bind(getItem(position))
|
||||||
mBoundViewHolders.add(holder)
|
}
|
||||||
|
|
||||||
|
is EpisodeCardViewHolderSmall -> {
|
||||||
|
holder.bind(getItem(position))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,94 +160,108 @@ class EpisodeAdapter(
|
||||||
return cardList.size
|
return cardList.size
|
||||||
}
|
}
|
||||||
|
|
||||||
class EpisodeCardViewHolder
|
class EpisodeCardViewHolderLarge
|
||||||
constructor(
|
constructor(
|
||||||
itemView: View,
|
val binding: ResultEpisodeLargeBinding,
|
||||||
private val hasDownloadSupport: Boolean,
|
private val hasDownloadSupport: Boolean,
|
||||||
private val clickCallback: (EpisodeClickEvent) -> Unit,
|
private val clickCallback: (EpisodeClickEvent) -> Unit,
|
||||||
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
|
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
|
||||||
) : RecyclerView.ViewHolder(itemView), DownloadButtonViewHolder {
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
override var downloadButton = EasyDownloadButton()
|
|
||||||
|
|
||||||
var episodeDownloadBar: ContentLoadingProgressBar? = null
|
|
||||||
var episodeDownloadImage: ImageView? = null
|
|
||||||
var localCard: ResultEpisode? = null
|
var localCard: ResultEpisode? = null
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun bind(card: ResultEpisode) {
|
fun bind(card: ResultEpisode) {
|
||||||
localCard = card
|
localCard = card
|
||||||
|
|
||||||
|
val setWidth =
|
||||||
|
if (isTvSettings()) TV_EP_SIZE_LARGE.toPx else ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
|
||||||
|
binding.episodeLinHolder.layoutParams.width = setWidth
|
||||||
|
binding.episodeHolderLarge.layoutParams.width = setWidth
|
||||||
|
binding.episodeHolder.layoutParams.width = setWidth
|
||||||
|
|
||||||
val isTrueTv = isTrueTvSettings()
|
val isTrueTv = isTrueTvSettings()
|
||||||
|
|
||||||
val (parentView, otherView) = if (card.poster == null) {
|
binding.apply {
|
||||||
itemView.episode_holder to itemView.episode_holder_large
|
downloadButton.isVisible = hasDownloadSupport
|
||||||
} else {
|
downloadButton.setDefaultClickListener(
|
||||||
itemView.episode_holder_large to itemView.episode_holder
|
VideoDownloadHelper.DownloadEpisodeCached(
|
||||||
}
|
card.name,
|
||||||
parentView.isVisible = true
|
card.poster,
|
||||||
otherView.isVisible = false
|
card.episode,
|
||||||
|
card.season,
|
||||||
|
card.id,
|
||||||
|
card.parentId,
|
||||||
|
card.rating,
|
||||||
|
card.description,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
), null
|
||||||
|
) {
|
||||||
|
when (it.action) {
|
||||||
|
DOWNLOAD_ACTION_DOWNLOAD -> {
|
||||||
|
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card))
|
||||||
|
}
|
||||||
|
|
||||||
val episodeText: TextView = parentView.episode_text
|
DOWNLOAD_ACTION_LONG_CLICK -> {
|
||||||
val episodeFiller: MaterialButton? = parentView.episode_filler
|
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_MIRROR, card))
|
||||||
val episodeRating: TextView? = parentView.episode_rating
|
}
|
||||||
val episodeDescript: TextView? = parentView.episode_descript
|
|
||||||
val episodeProgress: ContentLoadingProgressBar? = parentView.episode_progress
|
|
||||||
val episodePoster: ImageView? = parentView.episode_poster
|
|
||||||
|
|
||||||
episodeDownloadBar =
|
else -> {
|
||||||
parentView.result_episode_progress_downloaded
|
downloadClickCallback.invoke(it)
|
||||||
episodeDownloadImage = parentView.result_episode_download
|
}
|
||||||
|
}
|
||||||
val name =
|
|
||||||
if (card.name == null) "${episodeText.context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}"
|
|
||||||
episodeFiller?.isVisible = card.isFiller == true
|
|
||||||
episodeText.text =
|
|
||||||
name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name
|
|
||||||
episodeText.isSelected = true // is needed for text repeating
|
|
||||||
|
|
||||||
if (card.videoWatchState == VideoWatchState.Watched) {
|
|
||||||
// This cannot be done in getDisplayPosition() as when you have not watched something
|
|
||||||
// the duration and position is 0
|
|
||||||
episodeProgress?.max = 1
|
|
||||||
episodeProgress?.progress = 1
|
|
||||||
episodeProgress?.isVisible = true
|
|
||||||
} else {
|
|
||||||
val displayPos = card.getDisplayPosition()
|
|
||||||
episodeProgress?.max = (card.duration / 1000).toInt()
|
|
||||||
episodeProgress?.progress = (displayPos / 1000).toInt()
|
|
||||||
episodeProgress?.isVisible = displayPos > 0L
|
|
||||||
}
|
|
||||||
|
|
||||||
episodePoster?.isVisible = episodePoster?.setImage(card.poster) == true
|
|
||||||
|
|
||||||
if (card.rating != null) {
|
|
||||||
episodeRating?.text = episodeRating?.context?.getString(R.string.rated_format)
|
|
||||||
?.format(card.rating.toFloat() / 10f)
|
|
||||||
} else {
|
|
||||||
episodeRating?.text = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
episodeRating?.isGone = episodeRating?.text.isNullOrBlank()
|
|
||||||
|
|
||||||
episodeDescript?.apply {
|
|
||||||
text = card.description.html()
|
|
||||||
isGone = text.isNullOrBlank()
|
|
||||||
setOnClickListener {
|
|
||||||
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isTrueTv) {
|
|
||||||
episodePoster?.setOnClickListener {
|
|
||||||
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
episodePoster?.setOnLongClickListener {
|
val name =
|
||||||
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_TOAST, card))
|
if (card.name == null) "${episodeText.context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}"
|
||||||
return@setOnLongClickListener true
|
episodeFiller.isVisible = card.isFiller == true
|
||||||
|
episodeText.text =
|
||||||
|
name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name
|
||||||
|
episodeText.isSelected = true // is needed for text repeating
|
||||||
|
|
||||||
|
if (card.videoWatchState == VideoWatchState.Watched) {
|
||||||
|
// This cannot be done in getDisplayPosition() as when you have not watched something
|
||||||
|
// the duration and position is 0
|
||||||
|
episodeProgress.max = 1
|
||||||
|
episodeProgress.progress = 1
|
||||||
|
episodeProgress.isVisible = true
|
||||||
|
} else {
|
||||||
|
val displayPos = card.getDisplayPosition()
|
||||||
|
episodeProgress.max = (card.duration / 1000).toInt()
|
||||||
|
episodeProgress.progress = (displayPos / 1000).toInt()
|
||||||
|
episodeProgress.isVisible = displayPos > 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
episodePoster.isVisible = episodePoster.setImage(card.poster) == true
|
||||||
|
|
||||||
|
if (card.rating != null) {
|
||||||
|
episodeRating.text = episodeRating.context?.getString(R.string.rated_format)
|
||||||
|
?.format(card.rating.toFloat() / 10f)
|
||||||
|
} else {
|
||||||
|
episodeRating.text = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
episodeRating.isGone = episodeRating.text.isNullOrBlank()
|
||||||
|
|
||||||
|
episodeDescript.apply {
|
||||||
|
text = card.description.html()
|
||||||
|
isGone = text.isNullOrBlank()
|
||||||
|
setOnClickListener {
|
||||||
|
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isTrueTv) {
|
||||||
|
episodePoster.setOnClickListener {
|
||||||
|
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
||||||
|
}
|
||||||
|
|
||||||
|
episodePoster.setOnLongClickListener {
|
||||||
|
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_TOAST, card))
|
||||||
|
return@setOnLongClickListener true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
||||||
}
|
}
|
||||||
|
@ -276,29 +277,30 @@ class EpisodeAdapter(
|
||||||
return@setOnLongClickListener true
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
episodeDownloadImage?.isVisible = hasDownloadSupport
|
//binding.resultEpisodeDownload.isVisible = hasDownloadSupport
|
||||||
episodeDownloadBar?.isVisible = hasDownloadSupport
|
//binding.resultEpisodeProgressDownloaded.isVisible = hasDownloadSupport
|
||||||
reattachDownloadButton()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun reattachDownloadButton() {
|
class EpisodeCardViewHolderSmall
|
||||||
downloadButton.dispose()
|
constructor(
|
||||||
val card = localCard
|
val binding: ResultEpisodeBinding,
|
||||||
if (hasDownloadSupport && card != null) {
|
private val hasDownloadSupport: Boolean,
|
||||||
if (episodeDownloadBar == null ||
|
private val clickCallback: (EpisodeClickEvent) -> Unit,
|
||||||
episodeDownloadImage == null
|
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
|
||||||
) return
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
val downloadInfo = VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
|
@SuppressLint("SetTextI18n")
|
||||||
itemView.context,
|
fun bind(card: ResultEpisode) {
|
||||||
card.id
|
val isTrueTv = isTrueTvSettings()
|
||||||
)
|
|
||||||
|
|
||||||
downloadButton.setUpButton(
|
binding.episodeHolder.layoutParams.apply {
|
||||||
downloadInfo?.fileLength,
|
width =
|
||||||
downloadInfo?.totalBytes,
|
if (isTvSettings()) TV_EP_SIZE_SMALL.toPx else ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
episodeDownloadBar ?: return,
|
}
|
||||||
episodeDownloadImage ?: return,
|
|
||||||
null,
|
binding.apply {
|
||||||
|
downloadButton.isVisible = hasDownloadSupport
|
||||||
|
downloadButton.setDefaultClickListener(
|
||||||
VideoDownloadHelper.DownloadEpisodeCached(
|
VideoDownloadHelper.DownloadEpisodeCached(
|
||||||
card.name,
|
card.name,
|
||||||
card.poster,
|
card.poster,
|
||||||
|
@ -309,14 +311,60 @@ class EpisodeAdapter(
|
||||||
card.rating,
|
card.rating,
|
||||||
card.description,
|
card.description,
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
)
|
), null
|
||||||
) {
|
) {
|
||||||
if (it.action == DOWNLOAD_ACTION_DOWNLOAD) {
|
when (it.action) {
|
||||||
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card))
|
DOWNLOAD_ACTION_DOWNLOAD -> {
|
||||||
} else {
|
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card))
|
||||||
downloadClickCallback.invoke(it)
|
}
|
||||||
|
|
||||||
|
DOWNLOAD_ACTION_LONG_CLICK -> {
|
||||||
|
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_MIRROR, card))
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
downloadClickCallback.invoke(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val name =
|
||||||
|
if (card.name == null) "${episodeText.context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}"
|
||||||
|
episodeFiller.isVisible = card.isFiller == true
|
||||||
|
episodeText.text =
|
||||||
|
name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name
|
||||||
|
episodeText.isSelected = true // is needed for text repeating
|
||||||
|
|
||||||
|
if (card.videoWatchState == VideoWatchState.Watched) {
|
||||||
|
// This cannot be done in getDisplayPosition() as when you have not watched something
|
||||||
|
// the duration and position is 0
|
||||||
|
episodeProgress.max = 1
|
||||||
|
episodeProgress.progress = 1
|
||||||
|
episodeProgress.isVisible = true
|
||||||
|
} else {
|
||||||
|
val displayPos = card.getDisplayPosition()
|
||||||
|
episodeProgress.max = (card.duration / 1000).toInt()
|
||||||
|
episodeProgress.progress = (displayPos / 1000).toInt()
|
||||||
|
episodeProgress.isVisible = displayPos > 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
itemView.setOnClickListener {
|
||||||
|
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTrueTv) {
|
||||||
|
itemView.isFocusable = true
|
||||||
|
itemView.isFocusableInTouchMode = true
|
||||||
|
//itemView.touchscreenBlocksFocus = false
|
||||||
|
}
|
||||||
|
|
||||||
|
itemView.setOnLongClickListener {
|
||||||
|
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
|
||||||
|
return@setOnLongClickListener true
|
||||||
|
}
|
||||||
|
|
||||||
|
//binding.resultEpisodeDownload.isVisible = hasDownloadSupport
|
||||||
|
//binding.resultEpisodeProgressDownloaded.isVisible = hasDownloadSupport
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
package com.lagradost.cloudstream3.ui.result
|
package com.lagradost.cloudstream3.ui.result
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.lagradost.cloudstream3.databinding.ResultMiniImageBinding
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -24,7 +23,6 @@ const val IMAGE_CLICK = 0
|
||||||
const val IMAGE_LONG_CLICK = 1
|
const val IMAGE_LONG_CLICK = 1
|
||||||
|
|
||||||
class ImageAdapter(
|
class ImageAdapter(
|
||||||
val layout: Int,
|
|
||||||
val clickCallback: ((Int) -> Unit)? = null,
|
val clickCallback: ((Int) -> Unit)? = null,
|
||||||
val nextFocusUp: Int? = null,
|
val nextFocusUp: Int? = null,
|
||||||
val nextFocusDown: Int? = null,
|
val nextFocusDown: Int? = null,
|
||||||
|
@ -34,7 +32,9 @@ class ImageAdapter(
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return ImageViewHolder(
|
return ImageViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(layout, parent, false)
|
//result_mini_image
|
||||||
|
ResultMiniImageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
// LayoutInflater.from(parent.context).inflate(layout, parent, false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,15 +66,15 @@ class ImageAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
class ImageViewHolder
|
class ImageViewHolder
|
||||||
constructor(itemView: View) :
|
constructor(val binding: ResultMiniImageBinding) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
fun bind(
|
fun bind(
|
||||||
img: Int,
|
img: Int,
|
||||||
clickCallback: ((Int) -> Unit)?,
|
clickCallback: ((Int) -> Unit)?,
|
||||||
nextFocusUp: Int?,
|
nextFocusUp: Int?,
|
||||||
nextFocusDown: Int?,
|
nextFocusDown: Int?,
|
||||||
) {
|
) {
|
||||||
(itemView as? ImageView?)?.apply {
|
binding.root.apply {
|
||||||
setImageResource(img)
|
setImageResource(img)
|
||||||
if (nextFocusDown != null) {
|
if (nextFocusDown != null) {
|
||||||
this.nextFocusDownId = nextFocusDown
|
this.nextFocusDownId = nextFocusDown
|
||||||
|
|
|
@ -8,9 +8,10 @@ import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
|
||||||
fun RecyclerView?.setLinearListLayout(isHorizontal: Boolean = true) {
|
fun RecyclerView?.setLinearListLayout(isHorizontal: Boolean = true) {
|
||||||
if (this == null) return
|
if (this == null) return
|
||||||
|
|
||||||
this.layoutManager =
|
this.layoutManager =
|
||||||
this.context?.let { LinearListLayout(it).apply { if (isHorizontal) setHorizontal() else setVertical() } }
|
this.context?.let { LinearListLayout(it).apply { if (isHorizontal) setHorizontal() else setVertical() } }
|
||||||
?: this.layoutManager
|
// ?: this.layoutManager
|
||||||
}
|
}
|
||||||
|
|
||||||
open class LinearListLayout(context: Context?) :
|
open class LinearListLayout(context: Context?) :
|
||||||
|
@ -66,7 +67,12 @@ open class LinearListLayout(context: Context?) :
|
||||||
(focused.parent as? RecyclerView)?.focusSearch(direction)
|
(focused.parent as? RecyclerView)?.focusSearch(direction)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (direction == View.FOCUS_RIGHT) 1 else -1
|
var ret = if (direction == View.FOCUS_RIGHT) 1 else -1
|
||||||
|
// only flip on horizontal layout
|
||||||
|
if (this.isLayoutRTL) {
|
||||||
|
ret = -ret
|
||||||
|
}
|
||||||
|
ret
|
||||||
} else {
|
} else {
|
||||||
if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) return null
|
if (direction == View.FOCUS_RIGHT || direction == View.FOCUS_LEFT) return null
|
||||||
if (direction == View.FOCUS_DOWN) 1 else -1
|
if (direction == View.FOCUS_DOWN) 1 else -1
|
||||||
|
@ -76,6 +82,13 @@ open class LinearListLayout(context: Context?) :
|
||||||
getPosition(getCorrectParent(focused))?.let { position ->
|
getPosition(getCorrectParent(focused))?.let { position ->
|
||||||
val lookfor = dir + position
|
val lookfor = dir + position
|
||||||
//clamp(dir + position, 0, recyclerView.adapter?.itemCount ?: return null)
|
//clamp(dir + position, 0, recyclerView.adapter?.itemCount ?: return null)
|
||||||
|
|
||||||
|
// refocus on the same view if going out of bounds, note that we only do it
|
||||||
|
// for out of bounds one way as we may override the start where item == -1
|
||||||
|
if (lookfor >= itemCount) {
|
||||||
|
return getViewFromPos(itemCount - 1) ?: focused
|
||||||
|
}
|
||||||
|
|
||||||
getViewFromPos(lookfor) ?: run {
|
getViewFromPos(lookfor) ?: run {
|
||||||
scrollToPosition(lookfor)
|
scrollToPosition(lookfor)
|
||||||
null
|
null
|
||||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,33 +1,81 @@
|
||||||
package com.lagradost.cloudstream3.ui.result
|
package com.lagradost.cloudstream3.ui.result
|
||||||
|
|
||||||
|
import android.animation.Animator
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.animation.DecelerateInterpolator
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.widget.NestedScrollView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
|
||||||
import com.lagradost.cloudstream3.DubStatus
|
import com.lagradost.cloudstream3.DubStatus
|
||||||
import com.lagradost.cloudstream3.LoadResponse
|
import com.lagradost.cloudstream3.LoadResponse
|
||||||
|
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.SearchResponse
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
import com.lagradost.cloudstream3.mvvm.ResourceSome
|
import com.lagradost.cloudstream3.databinding.FragmentResultTvBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.Some
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
|
import com.lagradost.cloudstream3.mvvm.observeNullable
|
||||||
|
import com.lagradost.cloudstream3.ui.WatchType
|
||||||
|
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup
|
||||||
import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator
|
import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator
|
||||||
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
|
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
|
||||||
|
import com.lagradost.cloudstream3.ui.result.ResultFragment.getStoredData
|
||||||
|
import com.lagradost.cloudstream3.ui.result.ResultFragment.updateUIEvent
|
||||||
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchAdapter
|
import com.lagradost.cloudstream3.ui.search.SearchAdapter
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.isRtl
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
||||||
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import kotlinx.android.synthetic.main.fragment_result_tv.*
|
|
||||||
|
|
||||||
class ResultFragmentTv : ResultFragment() {
|
class ResultFragmentTv : Fragment() {
|
||||||
override val resultLayout = R.layout.fragment_result_tv
|
protected lateinit var viewModel: ResultViewModel2
|
||||||
|
private var binding: FragmentResultTvBinding? = null
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
updateUIEvent -= ::updateUI
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
viewModel =
|
||||||
|
ViewModelProvider(this)[ResultViewModel2::class.java]
|
||||||
|
viewModel.EPISODE_RANGE_SIZE = 50
|
||||||
|
updateUIEvent += ::updateUI
|
||||||
|
|
||||||
|
val localBinding = FragmentResultTvBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUI(id: Int?) {
|
||||||
|
viewModel.reloadEpisodes()
|
||||||
|
}
|
||||||
|
|
||||||
private var currentRecommendations: List<SearchResponse> = emptyList()
|
private var currentRecommendations: List<SearchResponse> = emptyList()
|
||||||
|
|
||||||
|
@ -36,12 +84,15 @@ class ResultFragmentTv : ResultFragment() {
|
||||||
is EpisodeRange -> {
|
is EpisodeRange -> {
|
||||||
viewModel.changeRange(data)
|
viewModel.changeRange(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
is Int -> {
|
is Int -> {
|
||||||
viewModel.changeSeason(data)
|
viewModel.changeSeason(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
is DubStatus -> {
|
is DubStatus -> {
|
||||||
viewModel.changeDubStatus(data)
|
viewModel.changeDubStatus(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
is String -> {
|
is String -> {
|
||||||
setRecommendations(currentRecommendations, data)
|
setRecommendations(currentRecommendations, data)
|
||||||
}
|
}
|
||||||
|
@ -66,172 +117,640 @@ class ResultFragmentTv : ResultFragment() {
|
||||||
private fun hasNoFocus(): Boolean {
|
private fun hasNoFocus(): Boolean {
|
||||||
val focus = activity?.currentFocus
|
val focus = activity?.currentFocus
|
||||||
if (focus == null || !focus.isVisible) return true
|
if (focus == null || !focus.isVisible) return true
|
||||||
return focus == this.result_root
|
return focus == binding?.resultRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateEpisodes(episodes: ResourceSome<List<ResultEpisode>>) {
|
private fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
|
||||||
super.updateEpisodes(episodes)
|
|
||||||
if (episodes is ResourceSome.Success && hasNoFocus()) {
|
|
||||||
result_episodes?.requestFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateMovie(data: ResourceSome<Pair<UiText, ResultEpisode>>) {
|
|
||||||
super.updateMovie(data)
|
|
||||||
if (data is ResourceSome.Success && hasNoFocus()) {
|
|
||||||
result_play_movie?.requestFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setTrailers(trailers: List<ExtractorLink>?) {
|
|
||||||
context?.updateHasTrailers()
|
|
||||||
if (!LoadResponse.isTrailersEnabled) return
|
|
||||||
|
|
||||||
result_play_trailer?.isGone = trailers.isNullOrEmpty()
|
|
||||||
result_play_trailer?.setOnClickListener {
|
|
||||||
if (trailers.isNullOrEmpty()) return@setOnClickListener
|
|
||||||
activity.navigate(
|
|
||||||
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
|
|
||||||
ExtractorLinkGenerator(
|
|
||||||
trailers,
|
|
||||||
emptyList()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
|
|
||||||
currentRecommendations = rec ?: emptyList()
|
currentRecommendations = rec ?: emptyList()
|
||||||
val isInvalid = rec.isNullOrEmpty()
|
val isInvalid = rec.isNullOrEmpty()
|
||||||
result_recommendations?.isGone = isInvalid
|
binding?.apply {
|
||||||
result_recommendations_holder?.isGone = isInvalid
|
resultRecommendationsList.isGone = isInvalid
|
||||||
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
|
resultRecommendationsHolder.isGone = isInvalid
|
||||||
(result_recommendations?.adapter as? SearchAdapter)?.updateList(rec?.filter { it.apiName == matchAgainst }
|
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
|
||||||
?: emptyList())
|
(resultRecommendationsList.adapter as? SearchAdapter)?.updateList(rec?.filter { it.apiName == matchAgainst }
|
||||||
|
?: emptyList())
|
||||||
|
|
||||||
rec?.map { it.apiName }?.distinct()?.let { apiNames ->
|
rec?.map { it.apiName }?.distinct()?.let { apiNames ->
|
||||||
// very dirty selection
|
// very dirty selection
|
||||||
result_recommendations_filter_selection?.isVisible = apiNames.size > 1
|
resultRecommendationsFilterSelection.isVisible = apiNames.size > 1
|
||||||
result_recommendations_filter_selection?.update(apiNames.map { txt(it) to it })
|
resultRecommendationsFilterSelection.update(apiNames.map { txt(it) to it })
|
||||||
result_recommendations_filter_selection?.select(apiNames.indexOf(matchAgainst))
|
resultRecommendationsFilterSelection.select(apiNames.indexOf(matchAgainst))
|
||||||
} ?: run {
|
} ?: run {
|
||||||
result_recommendations_filter_selection?.isVisible = false
|
resultRecommendationsFilterSelection.isVisible = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var loadingDialog: Dialog? = null
|
var loadingDialog: Dialog? = null
|
||||||
var popupDialog: Dialog? = null
|
var popupDialog: Dialog? = null
|
||||||
|
|
||||||
|
private fun reloadViewModel(forceReload: Boolean) {
|
||||||
|
if (!viewModel.hasLoaded() || forceReload) {
|
||||||
|
val storedData = getStoredData() ?: return
|
||||||
|
viewModel.load(
|
||||||
|
activity,
|
||||||
|
storedData.url,
|
||||||
|
storedData.apiName,
|
||||||
|
storedData.showFillers,
|
||||||
|
storedData.dubStatus,
|
||||||
|
storedData.start
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
activity?.let {
|
||||||
|
it.window?.navigationBarColor =
|
||||||
|
it.colorFromAttribute(R.attr.primaryBlackBackground)
|
||||||
|
}
|
||||||
|
afterPluginsLoadedEvent += ::reloadViewModel
|
||||||
|
super.onResume()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
afterPluginsLoadedEvent -= ::reloadViewModel
|
||||||
|
super.onStop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun View.fade(turnVisible: Boolean) {
|
||||||
|
if (turnVisible) {
|
||||||
|
isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
this.animate().alpha(if (turnVisible) 1.0f else 0.0f).apply {
|
||||||
|
duration = 200
|
||||||
|
interpolator = DecelerateInterpolator()
|
||||||
|
setListener(object : Animator.AnimatorListener {
|
||||||
|
override fun onAnimationStart(animation: Animator) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationEnd(animation: Animator) {
|
||||||
|
this@fade.isVisible = turnVisible
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationCancel(animation: Animator) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onAnimationRepeat(animation: Animator) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.animate().translationX(if (turnVisible) 0f else if(isRtl()) -100.0f else 100f).apply {
|
||||||
|
duration = 200
|
||||||
|
interpolator = DecelerateInterpolator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleEpisodes(show: Boolean) {
|
||||||
|
binding?.apply {
|
||||||
|
episodesShadow.fade(show)
|
||||||
|
episodeHolderTv.fade(show)
|
||||||
|
if(episodesShadow.isRtl()) {
|
||||||
|
episodesShadow.scaleX = -1.0f
|
||||||
|
episodesShadow.scaleY = -1.0f
|
||||||
|
} else {
|
||||||
|
episodesShadow.scaleX = 1.0f
|
||||||
|
episodesShadow.scaleY = 1.0f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
result_episodes?.layoutManager =
|
// ===== setup =====
|
||||||
//LinearListLayout(result_episodes ?: return, result_episodes?.context).apply {
|
val storedData = getStoredData() ?: return
|
||||||
LinearListLayout(result_episodes?.context).apply {
|
activity?.window?.decorView?.clearFocus()
|
||||||
setHorizontal()
|
activity?.loadCache()
|
||||||
|
hideKeyboard()
|
||||||
|
if (storedData.restart || !viewModel.hasLoaded())
|
||||||
|
viewModel.load(
|
||||||
|
activity,
|
||||||
|
storedData.url,
|
||||||
|
storedData.apiName,
|
||||||
|
storedData.showFillers,
|
||||||
|
storedData.dubStatus,
|
||||||
|
storedData.start
|
||||||
|
)
|
||||||
|
// ===== ===== =====
|
||||||
|
|
||||||
|
binding?.apply {
|
||||||
|
//episodesShadow.rotationX = 180.0f//if(episodesShadow.isRtl()) 180.0f else 0.0f
|
||||||
|
|
||||||
|
val leftListener: View.OnFocusChangeListener =
|
||||||
|
View.OnFocusChangeListener { _, hasFocus ->
|
||||||
|
if (!hasFocus) return@OnFocusChangeListener
|
||||||
|
toggleEpisodes(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val rightListener: View.OnFocusChangeListener =
|
||||||
|
View.OnFocusChangeListener { _, hasFocus ->
|
||||||
|
if (!hasFocus) return@OnFocusChangeListener
|
||||||
|
toggleEpisodes(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultPlayMovie.onFocusChangeListener = leftListener
|
||||||
|
resultPlaySeries.onFocusChangeListener = leftListener
|
||||||
|
resultResumeSeries.onFocusChangeListener = leftListener
|
||||||
|
resultPlayTrailer.onFocusChangeListener = leftListener
|
||||||
|
resultEpisodesShow.onFocusChangeListener = rightListener
|
||||||
|
resultDescription.onFocusChangeListener = leftListener
|
||||||
|
resultBookmarkButton.onFocusChangeListener = leftListener
|
||||||
|
resultEpisodesShow.setOnClickListener {
|
||||||
|
// toggle, to make it more touch accessable just in case someone thinks that a
|
||||||
|
// tv layout is better but is using a touch device
|
||||||
|
toggleEpisodes(!episodeHolderTv.isVisible)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resultEpisodes.onFocusChangeListener = leftListener
|
||||||
|
|
||||||
|
redirectToPlay.setOnFocusChangeListener { _, hasFocus ->
|
||||||
|
if (!hasFocus) return@setOnFocusChangeListener
|
||||||
|
toggleEpisodes(false)
|
||||||
|
|
||||||
|
binding?.apply {
|
||||||
|
val views = listOf(
|
||||||
|
resultPlayMovie,
|
||||||
|
resultPlaySeries,
|
||||||
|
resultResumeSeries,
|
||||||
|
resultPlayTrailer,
|
||||||
|
resultBookmarkButton
|
||||||
|
)
|
||||||
|
for (requestView in views) {
|
||||||
|
if (!requestView.isVisible) continue
|
||||||
|
if (requestView.requestFocus()) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parallax on background
|
||||||
|
resultFinishLoading.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
|
||||||
|
backgroundPosterHolder.translationY = -scrollY.toFloat() * 0.8f
|
||||||
|
})
|
||||||
|
|
||||||
|
redirectToEpisodes.setOnFocusChangeListener { _, hasFocus ->
|
||||||
|
if (!hasFocus) return@setOnFocusChangeListener
|
||||||
|
toggleEpisodes(true)
|
||||||
|
binding?.apply {
|
||||||
|
val views = listOf(
|
||||||
|
resultSeasonSelection,
|
||||||
|
resultRangeSelection,
|
||||||
|
resultDubSelection,
|
||||||
|
resultPlayTrailer,
|
||||||
|
resultEpisodes
|
||||||
|
)
|
||||||
|
for (requestView in views) {
|
||||||
|
if (!requestView.isShown) continue
|
||||||
|
if (requestView.requestFocus()) break // View.FOCUS_RIGHT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resultEpisodes.setLinearListLayout(isHorizontal = false)/*.layoutManager =
|
||||||
|
LinearListLayout(resultEpisodes.context, resultEpisodes.isRtl()).apply {
|
||||||
|
setVertical()
|
||||||
|
}*/
|
||||||
|
|
||||||
|
resultReloadConnectionerror.setOnClickListener {
|
||||||
|
viewModel.load(
|
||||||
|
activity,
|
||||||
|
storedData.url,
|
||||||
|
storedData.apiName,
|
||||||
|
storedData.showFillers,
|
||||||
|
storedData.dubStatus,
|
||||||
|
storedData.start
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
resultMetaSite.isFocusable = false
|
||||||
|
|
||||||
|
//resultReloadConnectionOpenInBrowser.setOnClickListener {view ->
|
||||||
|
// view.context?.openBrowser(storedData?.url ?: return@setOnClickListener, fallbackWebview = true)
|
||||||
|
//}
|
||||||
|
|
||||||
|
resultSeasonSelection.setAdapter()
|
||||||
|
resultRangeSelection.setAdapter()
|
||||||
|
resultDubSelection.setAdapter()
|
||||||
|
resultRecommendationsFilterSelection.setAdapter()
|
||||||
|
|
||||||
|
resultCastItems.setOnFocusChangeListener { _, hasFocus ->
|
||||||
|
// Always escape focus
|
||||||
|
if (hasFocus) binding?.resultBookmarkButton?.requestFocus()
|
||||||
|
}
|
||||||
|
//resultBack.setOnClickListener {
|
||||||
|
// activity?.popCurrentPage()
|
||||||
|
//}
|
||||||
|
|
||||||
|
resultRecommendationsList.spanCount = 8
|
||||||
|
resultRecommendationsList.adapter =
|
||||||
|
SearchAdapter(
|
||||||
|
ArrayList(),
|
||||||
|
resultRecommendationsList,
|
||||||
|
) { callback ->
|
||||||
|
if(callback.action == SEARCH_ACTION_FOCUSED)
|
||||||
|
toggleEpisodes(false)
|
||||||
|
else
|
||||||
|
SearchHelper.handleSearchClickCallback(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultEpisodes.adapter =
|
||||||
|
EpisodeAdapter(
|
||||||
|
false,
|
||||||
|
{ episodeClick ->
|
||||||
|
viewModel.handleAction(episodeClick)
|
||||||
|
},
|
||||||
|
{ downloadClickEvent ->
|
||||||
|
DownloadButtonSetup.handleDownloadClick(downloadClickEvent)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
resultCastItems.layoutManager = object : LinearListLayout(view.context) {
|
||||||
|
override fun onRequestChildFocus(
|
||||||
|
parent: RecyclerView,
|
||||||
|
state: RecyclerView.State,
|
||||||
|
child: View,
|
||||||
|
focused: View?
|
||||||
|
): Boolean {
|
||||||
|
// Make the cast always focus the first visible item when focused
|
||||||
|
// from somewhere else. Otherwise it jumps to the last item.
|
||||||
|
return if (parent.focusedChild == null) {
|
||||||
|
scrollToPosition(this.findFirstCompletelyVisibleItemPosition())
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
super.onRequestChildFocus(parent, state, child, focused)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.apply {
|
||||||
|
this.orientation = RecyclerView.HORIZONTAL
|
||||||
|
}
|
||||||
|
resultCastItems.adapter = ActorAdaptor {
|
||||||
|
toggleEpisodes(false)
|
||||||
}
|
}
|
||||||
(result_episodes?.adapter as EpisodeAdapter?)?.apply {
|
|
||||||
layout = R.layout.result_episode_both_tv
|
|
||||||
}
|
}
|
||||||
//result_episodes?.setMaxViewPoolSize(0, Int.MAX_VALUE)
|
|
||||||
|
|
||||||
result_season_selection.setAdapter()
|
observeNullable(viewModel.resumeWatching) { resume ->
|
||||||
result_range_selection.setAdapter()
|
binding?.apply {
|
||||||
result_dub_selection.setAdapter()
|
// show progress no matter if series or movie
|
||||||
result_recommendations_filter_selection.setAdapter()
|
resume?.progress?.let { progress ->
|
||||||
|
resultResumeSeriesProgressText.setText(progress.progressLeft)
|
||||||
|
resultResumeSeriesProgress.apply {
|
||||||
|
isVisible = true
|
||||||
|
this.max = progress.maxProgress
|
||||||
|
this.progress = progress.progress
|
||||||
|
}
|
||||||
|
resultResumeProgressHolder.isVisible = true
|
||||||
|
} ?: run {
|
||||||
|
resultResumeProgressHolder.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
observe(viewModel.selectPopup) { popup ->
|
// if movie then hide both as movie button is
|
||||||
when (popup) {
|
// always visible on movies, this is done in movie observe
|
||||||
is Some.Success -> {
|
|
||||||
popupDialog?.dismissSafe(activity)
|
|
||||||
|
|
||||||
popupDialog = activity?.let { act ->
|
if (resume?.isMovie == true) {
|
||||||
val pop = popup.value
|
resultPlaySeries.isVisible = false
|
||||||
val options = pop.getOptions(act)
|
resultResumeSeries.isVisible = false
|
||||||
val title = pop.getTitle(act)
|
return@observeNullable
|
||||||
|
}
|
||||||
|
|
||||||
act.showBottomDialogInstant(
|
// if series then
|
||||||
options, title, {
|
// > resultPlaySeries is visible when null
|
||||||
popupDialog = null
|
// > resultResumeSeries is visible when not null
|
||||||
pop.callback(null)
|
if (resume == null) {
|
||||||
}, {
|
resultPlaySeries.isVisible = true
|
||||||
popupDialog = null
|
resultResumeSeries.isVisible = false
|
||||||
pop.callback(it)
|
return@observeNullable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resultPlaySeries.isVisible = false
|
||||||
|
resultResumeSeries.isVisible = true
|
||||||
|
|
||||||
|
if (hasNoFocus()) {
|
||||||
|
resultResumeSeries.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
resultResumeSeries.text =
|
||||||
|
if (resume.isMovie) context?.getString(R.string.play_movie_button) else context?.getNameFull(
|
||||||
|
null, // resume.result.name, we don't want episode title
|
||||||
|
resume.result.episode,
|
||||||
|
resume.result.season
|
||||||
|
)
|
||||||
|
|
||||||
|
resultResumeSeries.setOnClickListener {
|
||||||
|
viewModel.handleAction(
|
||||||
|
EpisodeClickEvent(
|
||||||
|
storedData.playerAction, //?: ACTION_PLAY_EPISODE_IN_PLAYER,
|
||||||
|
resume.result
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultResumeSeries.setOnLongClickListener {
|
||||||
|
viewModel.handleAction(
|
||||||
|
EpisodeClickEvent(ACTION_SHOW_OPTIONS, resume.result)
|
||||||
|
)
|
||||||
|
return@setOnLongClickListener true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
observe(viewModel.trailers) { trailersLinks ->
|
||||||
|
context?.updateHasTrailers()
|
||||||
|
if (!LoadResponse.isTrailersEnabled) return@observe
|
||||||
|
val trailers = trailersLinks.flatMap { it.mirros }
|
||||||
|
binding?.resultPlayTrailer?.apply {
|
||||||
|
isGone = trailers.isEmpty()
|
||||||
|
setOnClickListener {
|
||||||
|
if (trailers.isEmpty()) return@setOnClickListener
|
||||||
|
activity.navigate(
|
||||||
|
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
|
||||||
|
ExtractorLinkGenerator(
|
||||||
|
trailers,
|
||||||
|
emptyList()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
observe(viewModel.watchStatus) { watchType ->
|
||||||
|
binding?.resultBookmarkButton?.apply {
|
||||||
|
setText(watchType.stringRes)
|
||||||
|
setOnClickListener { view ->
|
||||||
|
activity?.showBottomDialog(
|
||||||
|
WatchType.values().map { view.context.getString(it.stringRes) }.toList(),
|
||||||
|
watchType.ordinal,
|
||||||
|
view.context.getString(R.string.action_add_to_bookmarks),
|
||||||
|
showApply = false,
|
||||||
|
{}) {
|
||||||
|
viewModel.updateWatchStatus(WatchType.values()[it])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
observeNullable(viewModel.movie) { data ->
|
||||||
|
binding?.apply {
|
||||||
|
resultPlayMovie.isVisible = data is Resource.Success
|
||||||
|
seriesHolder.isVisible = data == null
|
||||||
|
resultEpisodesShow.isVisible = data == null
|
||||||
|
|
||||||
|
(data as? Resource.Success)?.value?.let { (text, ep) ->
|
||||||
|
resultPlayMovie.setText(text)
|
||||||
|
resultPlayMovie.setOnClickListener {
|
||||||
|
viewModel.handleAction(
|
||||||
|
EpisodeClickEvent(ACTION_CLICK_DEFAULT, ep)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
resultPlayMovie.setOnLongClickListener {
|
||||||
is Some.None -> {
|
viewModel.handleAction(
|
||||||
popupDialog?.dismissSafe(activity)
|
EpisodeClickEvent(ACTION_SHOW_OPTIONS, ep)
|
||||||
popupDialog = null
|
)
|
||||||
|
return@setOnLongClickListener true
|
||||||
|
}
|
||||||
|
if (hasNoFocus()) {
|
||||||
|
resultPlayMovie.requestFocus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(viewModel.loadedLinks) { load ->
|
observeNullable(viewModel.selectPopup) { popup ->
|
||||||
when (load) {
|
if (popup == null) {
|
||||||
is Some.Success -> {
|
popupDialog?.dismissSafe(activity)
|
||||||
if (loadingDialog?.isShowing != true) {
|
popupDialog = null
|
||||||
loadingDialog?.dismissSafe(activity)
|
return@observeNullable
|
||||||
loadingDialog = null
|
}
|
||||||
|
|
||||||
|
popupDialog?.dismissSafe(activity)
|
||||||
|
|
||||||
|
popupDialog = activity?.let { act ->
|
||||||
|
val options = popup.getOptions(act)
|
||||||
|
val title = popup.getTitle(act)
|
||||||
|
|
||||||
|
act.showBottomDialogInstant(
|
||||||
|
options, title, {
|
||||||
|
popupDialog = null
|
||||||
|
popup.callback(null)
|
||||||
|
}, {
|
||||||
|
popupDialog = null
|
||||||
|
popup.callback(it)
|
||||||
}
|
}
|
||||||
loadingDialog = loadingDialog ?: context?.let { ctx ->
|
)
|
||||||
val builder = BottomSheetDialog(ctx)
|
}
|
||||||
builder.setContentView(R.layout.bottom_loading)
|
}
|
||||||
builder.setOnDismissListener {
|
|
||||||
loadingDialog = null
|
observeNullable(viewModel.loadedLinks) { load ->
|
||||||
viewModel.cancelLinks()
|
if (load == null) {
|
||||||
}
|
loadingDialog?.dismissSafe(activity)
|
||||||
//builder.setOnCancelListener {
|
loadingDialog = null
|
||||||
// it?.dismiss()
|
return@observeNullable
|
||||||
//}
|
}
|
||||||
builder.setCanceledOnTouchOutside(true)
|
if (loadingDialog?.isShowing != true) {
|
||||||
builder.show()
|
loadingDialog?.dismissSafe(activity)
|
||||||
builder
|
loadingDialog = null
|
||||||
}
|
}
|
||||||
}
|
loadingDialog = loadingDialog ?: context?.let { ctx ->
|
||||||
is Some.None -> {
|
val builder = BottomSheetDialog(ctx)
|
||||||
loadingDialog?.dismissSafe(activity)
|
builder.setContentView(R.layout.bottom_loading)
|
||||||
|
builder.setOnDismissListener {
|
||||||
loadingDialog = null
|
loadingDialog = null
|
||||||
|
viewModel.cancelLinks()
|
||||||
}
|
}
|
||||||
|
//builder.setOnCancelListener {
|
||||||
|
// it?.dismiss()
|
||||||
|
//}
|
||||||
|
builder.setCanceledOnTouchOutside(true)
|
||||||
|
builder.show()
|
||||||
|
builder
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
observe(viewModel.episodesCountText) { count ->
|
observeNullable(viewModel.episodesCountText) { count ->
|
||||||
result_episodes_text.setText(count)
|
binding?.resultEpisodesText.setText(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(viewModel.selectedRangeIndex) { selected ->
|
observe(viewModel.selectedRangeIndex) { selected ->
|
||||||
result_range_selection.select(selected)
|
binding?.resultRangeSelection.select(selected)
|
||||||
}
|
}
|
||||||
observe(viewModel.selectedSeasonIndex) { selected ->
|
observe(viewModel.selectedSeasonIndex) { selected ->
|
||||||
result_season_selection.select(selected)
|
binding?.resultSeasonSelection.select(selected)
|
||||||
}
|
}
|
||||||
observe(viewModel.selectedDubStatusIndex) { selected ->
|
observe(viewModel.selectedDubStatusIndex) { selected ->
|
||||||
result_dub_selection.select(selected)
|
binding?.resultDubSelection.select(selected)
|
||||||
}
|
}
|
||||||
observe(viewModel.rangeSelections) {
|
observe(viewModel.rangeSelections) {
|
||||||
result_range_selection.update(it)
|
binding?.resultRangeSelection.update(it)
|
||||||
}
|
}
|
||||||
observe(viewModel.dubSubSelections) {
|
observe(viewModel.dubSubSelections) {
|
||||||
result_dub_selection.update(it)
|
binding?.resultDubSelection.update(it)
|
||||||
}
|
}
|
||||||
observe(viewModel.seasonSelections) {
|
observe(viewModel.seasonSelections) {
|
||||||
result_season_selection.update(it)
|
binding?.resultSeasonSelection.update(it)
|
||||||
}
|
}
|
||||||
|
observe(viewModel.recommendations) { recommendations ->
|
||||||
result_back?.setOnClickListener {
|
setRecommendations(recommendations, null)
|
||||||
activity?.popCurrentPage()
|
|
||||||
}
|
}
|
||||||
|
observe(viewModel.episodeSynopsis) { description ->
|
||||||
result_recommendations?.spanCount = 8
|
view.context?.let { ctx ->
|
||||||
result_recommendations?.adapter =
|
val builder: AlertDialog.Builder =
|
||||||
SearchAdapter(
|
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
|
||||||
ArrayList(),
|
builder.setMessage(description.html())
|
||||||
result_recommendations,
|
.setTitle(R.string.synopsis)
|
||||||
) { callback ->
|
.setOnDismissListener {
|
||||||
SearchHelper.handleSearchClickCallback(activity, callback)
|
viewModel.releaseEpisodeSynopsis()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
observeNullable(viewModel.episodes) { episodes ->
|
||||||
|
binding?.apply {
|
||||||
|
resultEpisodes.isVisible = episodes is Resource.Success
|
||||||
|
// resultEpisodeLoading.isVisible = episodes is Resource.Loading
|
||||||
|
if (episodes is Resource.Success) {
|
||||||
|
val first = episodes.value.firstOrNull()
|
||||||
|
if (first != null) {
|
||||||
|
resultPlaySeries.text = context?.getNameFull(
|
||||||
|
null, // resume.result.name, we don't want episode title
|
||||||
|
first.episode,
|
||||||
|
first.season
|
||||||
|
)
|
||||||
|
|
||||||
|
resultPlaySeries.setOnClickListener {
|
||||||
|
viewModel.handleAction(
|
||||||
|
EpisodeClickEvent(
|
||||||
|
ACTION_PLAY_EPISODE_IN_PLAYER,
|
||||||
|
first
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
resultPlaySeries.setOnLongClickListener {
|
||||||
|
viewModel.handleAction(
|
||||||
|
EpisodeClickEvent(ACTION_SHOW_OPTIONS, first)
|
||||||
|
)
|
||||||
|
return@setOnLongClickListener true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Okay so what is this fuckery?
|
||||||
|
* Basically Android TV will crash if you request a new focus while
|
||||||
|
* the adapter gets updated.
|
||||||
|
*
|
||||||
|
* This means that if you load thumbnails and request a next focus at the same time
|
||||||
|
* the app will crash without any way to catch it!
|
||||||
|
*
|
||||||
|
* How to bypass this?
|
||||||
|
* This code basically steals the focus for 500ms and puts it in an inescapable view
|
||||||
|
* then lets out the focus by requesting focus to result_episodes
|
||||||
|
*/
|
||||||
|
|
||||||
|
val hasEpisodes =
|
||||||
|
!(resultEpisodes.adapter as? EpisodeAdapter?)?.cardList.isNullOrEmpty()
|
||||||
|
/*val focus = activity?.currentFocus
|
||||||
|
|
||||||
|
if (hasEpisodes) {
|
||||||
|
// Make it impossible to focus anywhere else!
|
||||||
|
temporaryNoFocus.isFocusable = true
|
||||||
|
temporaryNoFocus.requestFocus()
|
||||||
|
}*/
|
||||||
|
|
||||||
|
(resultEpisodes.adapter as? EpisodeAdapter)?.updateList(episodes.value)
|
||||||
|
|
||||||
|
/* if (hasEpisodes) main {
|
||||||
|
|
||||||
|
delay(500)
|
||||||
|
// This might make some people sad as it changes the focus when leaving an episode :(
|
||||||
|
if(focus?.requestFocus() == true) {
|
||||||
|
temporaryNoFocus.isFocusable = false
|
||||||
|
return@main
|
||||||
|
}
|
||||||
|
temporaryNoFocus.isFocusable = false
|
||||||
|
temporaryNoFocus.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasNoFocus())
|
||||||
|
binding?.resultEpisodes?.requestFocus()*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
observeNullable(viewModel.page) { data ->
|
||||||
|
if (data == null) return@observeNullable
|
||||||
|
binding?.apply {
|
||||||
|
when (data) {
|
||||||
|
is Resource.Success -> {
|
||||||
|
val d = data.value
|
||||||
|
resultVpn.setText(d.vpnText)
|
||||||
|
resultInfo.setText(d.metaText)
|
||||||
|
resultNoEpisodes.setText(d.noEpisodesFoundText)
|
||||||
|
resultTitle.setText(d.titleText)
|
||||||
|
resultMetaSite.setText(d.apiName)
|
||||||
|
resultMetaType.setText(d.typeText)
|
||||||
|
resultMetaYear.setText(d.yearText)
|
||||||
|
resultMetaDuration.setText(d.durationText)
|
||||||
|
resultMetaRating.setText(d.ratingText)
|
||||||
|
resultCastText.setText(d.actorsText)
|
||||||
|
resultNextAiring.setText(d.nextAiringEpisode)
|
||||||
|
resultNextAiringTime.setText(d.nextAiringDate)
|
||||||
|
resultPoster.setImage(d.posterImage)
|
||||||
|
resultDescription.setTextHtml(d.plotText)
|
||||||
|
resultDescription.setOnClickListener { view ->
|
||||||
|
view.context?.let { ctx ->
|
||||||
|
val builder: AlertDialog.Builder =
|
||||||
|
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
|
||||||
|
builder.setMessage(d.plotText.asString(ctx).html())
|
||||||
|
.setTitle(d.plotHeaderText.asString(ctx))
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val error = listOf(
|
||||||
|
R.drawable.profile_bg_dark_blue,
|
||||||
|
R.drawable.profile_bg_blue,
|
||||||
|
R.drawable.profile_bg_orange,
|
||||||
|
R.drawable.profile_bg_pink,
|
||||||
|
R.drawable.profile_bg_purple,
|
||||||
|
R.drawable.profile_bg_red,
|
||||||
|
R.drawable.profile_bg_teal
|
||||||
|
).random()
|
||||||
|
backgroundPoster.setImage(
|
||||||
|
d.posterBackgroundImage ?: UiImage.Drawable(error),
|
||||||
|
radius = 0,
|
||||||
|
errorImageDrawable = error
|
||||||
|
)
|
||||||
|
|
||||||
|
resultComingSoon.isVisible = d.comingSoon
|
||||||
|
resultDataHolder.isGone = d.comingSoon
|
||||||
|
UIHelper.populateChips(resultTag, d.tags)
|
||||||
|
resultCastItems.isGone = d.actors.isNullOrEmpty()
|
||||||
|
(resultCastItems.adapter as? ActorAdaptor)?.updateList(
|
||||||
|
d.actors ?: emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Resource.Loading -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
is Resource.Failure -> {
|
||||||
|
resultErrorText.text =
|
||||||
|
storedData.url.plus("\n") + data.errorString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resultFinishLoading.isVisible = data is Resource.Success
|
||||||
|
|
||||||
|
resultLoading.isVisible = data is Resource.Loading
|
||||||
|
|
||||||
|
resultLoadingError.isVisible = data is Resource.Failure
|
||||||
|
//resultReloadConnectionOpenInBrowser.isVisible = data is Resource.Failure
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,21 +10,13 @@ import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.discord.panels.PanelsChildGestureRegionObserver
|
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
|
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
|
||||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||||
import com.lagradost.cloudstream3.utils.IOnBackPressed
|
import com.lagradost.cloudstream3.utils.IOnBackPressed
|
||||||
import kotlinx.android.synthetic.main.fragment_result.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_result.result_smallscreen_holder
|
|
||||||
import kotlinx.android.synthetic.main.fragment_result_swipe.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_result_tv.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_trailer.*
|
|
||||||
import kotlinx.android.synthetic.main.trailer_custom_layout.*
|
|
||||||
|
|
||||||
|
|
||||||
open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreenPlayer(),
|
open class ResultTrailerPlayer : ResultFragmentPhone(), IOnBackPressed {
|
||||||
PanelsChildGestureRegionObserver.GestureRegionsListener, IOnBackPressed {
|
|
||||||
|
|
||||||
override var lockRotation = false
|
override var lockRotation = false
|
||||||
override var isFullScreenPlayer = false
|
override var isFullScreenPlayer = false
|
||||||
|
@ -60,13 +52,13 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
|
||||||
screenHeight
|
screenHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
result_trailer_loading?.isVisible = false
|
//result_trailer_loading?.isVisible = false
|
||||||
result_smallscreen_holder?.isVisible = !isFullScreenPlayer
|
resultBinding?.resultSmallscreenHolder?.isVisible = !isFullScreenPlayer
|
||||||
result_fullscreen_holder?.isVisible = isFullScreenPlayer
|
binding?.resultFullscreenHolder?.isVisible = isFullScreenPlayer
|
||||||
|
|
||||||
val to = sw * h / w
|
val to = sw * h / w
|
||||||
|
|
||||||
player_background?.apply {
|
resultBinding?.fragmentTrailer?.playerBackground?.apply {
|
||||||
isVisible = true
|
isVisible = true
|
||||||
layoutParams =
|
layoutParams =
|
||||||
FrameLayout.LayoutParams(
|
FrameLayout.LayoutParams(
|
||||||
|
@ -75,16 +67,17 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
player_intro_play?.apply {
|
playerBinding?.playerIntroPlay?.apply {
|
||||||
layoutParams =
|
layoutParams =
|
||||||
FrameLayout.LayoutParams(
|
FrameLayout.LayoutParams(
|
||||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
result_top_holder?.measuredHeight ?: FrameLayout.LayoutParams.MATCH_PARENT
|
resultBinding?.resultTopHolder?.measuredHeight
|
||||||
|
?: FrameLayout.LayoutParams.MATCH_PARENT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player_intro_play?.isGone == true) {
|
if (playerBinding?.playerIntroPlay?.isGone == true) {
|
||||||
result_top_holder?.apply {
|
resultBinding?.resultTopHolder?.apply {
|
||||||
|
|
||||||
val anim = ValueAnimator.ofInt(
|
val anim = ValueAnimator.ofInt(
|
||||||
measuredHeight,
|
measuredHeight,
|
||||||
|
@ -131,23 +124,30 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
|
||||||
private fun updateFullscreen(fullscreen: Boolean) {
|
private fun updateFullscreen(fullscreen: Boolean) {
|
||||||
isFullScreenPlayer = fullscreen
|
isFullScreenPlayer = fullscreen
|
||||||
lockRotation = fullscreen
|
lockRotation = fullscreen
|
||||||
player_fullscreen?.setImageResource(if (fullscreen) R.drawable.baseline_fullscreen_exit_24 else R.drawable.baseline_fullscreen_24)
|
|
||||||
|
playerBinding?.playerFullscreen?.setImageResource(if (fullscreen) R.drawable.baseline_fullscreen_exit_24 else R.drawable.baseline_fullscreen_24)
|
||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
enterFullscreen()
|
enterFullscreen()
|
||||||
result_top_bar?.isVisible = false
|
binding?.apply {
|
||||||
result_fullscreen_holder?.isVisible = true
|
resultTopBar.isVisible = false
|
||||||
result_main_holder?.isVisible = false
|
resultFullscreenHolder.isVisible = true
|
||||||
player_background?.let { view ->
|
resultMainHolder.isVisible = false
|
||||||
(view.parent as ViewGroup?)?.removeView(view)
|
|
||||||
result_fullscreen_holder?.addView(view)
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
result_top_bar?.isVisible = true
|
resultBinding?.fragmentTrailer?.playerBackground?.let { view ->
|
||||||
result_fullscreen_holder?.isVisible = false
|
|
||||||
result_main_holder?.isVisible = true
|
|
||||||
player_background?.let { view ->
|
|
||||||
(view.parent as ViewGroup?)?.removeView(view)
|
(view.parent as ViewGroup?)?.removeView(view)
|
||||||
result_smallscreen_holder?.addView(view)
|
binding?.resultFullscreenHolder?.addView(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
binding?.apply {
|
||||||
|
resultTopBar.isVisible = true
|
||||||
|
resultFullscreenHolder.isVisible = false
|
||||||
|
resultMainHolder.isVisible = true
|
||||||
|
resultBinding?.fragmentTrailer?.playerBackground?.let { view ->
|
||||||
|
(view.parent as ViewGroup?)?.removeView(view)
|
||||||
|
resultBinding?.resultSmallscreenHolder?.addView(view)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
exitFullscreen()
|
exitFullscreen()
|
||||||
}
|
}
|
||||||
|
@ -157,14 +157,14 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
player_fullscreen?.setOnClickListener {
|
playerBinding?.playerFullscreen?.setOnClickListener {
|
||||||
updateFullscreen(!isFullScreenPlayer)
|
updateFullscreen(!isFullScreenPlayer)
|
||||||
}
|
}
|
||||||
updateFullscreen(isFullScreenPlayer)
|
updateFullscreen(isFullScreenPlayer)
|
||||||
uiReset()
|
uiReset()
|
||||||
|
|
||||||
player_intro_play?.setOnClickListener {
|
playerBinding?.playerIntroPlay?.setOnClickListener {
|
||||||
player_intro_play?.isGone = true
|
playerBinding?.playerIntroPlay?.isGone = true
|
||||||
player.handleEvent(CSPlayerEvent.Play)
|
player.handleEvent(CSPlayerEvent.Play)
|
||||||
updateUIVisibility()
|
updateUIVisibility()
|
||||||
fixPlayerSize()
|
fixPlayerSize()
|
||||||
|
|
|
@ -19,6 +19,7 @@ import com.lagradost.cloudstream3.APIHolder.getId
|
||||||
import com.lagradost.cloudstream3.APIHolder.unixTime
|
import com.lagradost.cloudstream3.APIHolder.unixTime
|
||||||
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
|
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
|
import com.lagradost.cloudstream3.CommonActivity.activity
|
||||||
import com.lagradost.cloudstream3.CommonActivity.getCastSession
|
import com.lagradost.cloudstream3.CommonActivity.getCastSession
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||||
|
@ -145,15 +146,18 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
|
||||||
minute
|
minute
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
hours > 0 -> txt(
|
hours > 0 -> txt(
|
||||||
R.string.next_episode_time_hour_format,
|
R.string.next_episode_time_hour_format,
|
||||||
hours,
|
hours,
|
||||||
minute
|
minute
|
||||||
)
|
)
|
||||||
|
|
||||||
minute > 0 -> txt(
|
minute > 0 -> txt(
|
||||||
R.string.next_episode_time_min_format,
|
R.string.next_episode_time_min_format,
|
||||||
minute
|
minute
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}?.also {
|
}?.also {
|
||||||
nextAiringEpisode = txt(R.string.next_episode_format, airing.episode)
|
nextAiringEpisode = txt(R.string.next_episode_format, airing.episode)
|
||||||
|
@ -305,6 +309,7 @@ fun SelectPopup.getOptions(context: Context): List<String> {
|
||||||
is SelectPopup.SelectArray -> {
|
is SelectPopup.SelectArray -> {
|
||||||
this.options.map { it.first.asString(context) }
|
this.options.map { it.first.asString(context) }
|
||||||
}
|
}
|
||||||
|
|
||||||
is SelectPopup.SelectText -> options.map { it.asString(context) }
|
is SelectPopup.SelectText -> options.map { it.asString(context) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,7 +321,7 @@ data class ExtractedTrailerData(
|
||||||
|
|
||||||
class ResultViewModel2 : ViewModel() {
|
class ResultViewModel2 : ViewModel() {
|
||||||
private var currentResponse: LoadResponse? = null
|
private var currentResponse: LoadResponse? = null
|
||||||
|
var EPISODE_RANGE_SIZE: Int = 20
|
||||||
fun clear() {
|
fun clear() {
|
||||||
currentResponse = null
|
currentResponse = null
|
||||||
_page.postValue(null)
|
_page.postValue(null)
|
||||||
|
@ -337,7 +342,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
private var currentIndex: EpisodeIndexer? = null
|
private var currentIndex: EpisodeIndexer? = null
|
||||||
private var currentRange: EpisodeRange? = null
|
private var currentRange: EpisodeRange? = null
|
||||||
private var currentShowFillers: Boolean = false
|
private var currentShowFillers: Boolean = false
|
||||||
private var currentRepo: APIRepository? = null
|
var currentRepo: APIRepository? = null
|
||||||
private var currentId: Int? = null
|
private var currentId: Int? = null
|
||||||
private var fillers: Map<Int, Boolean> = emptyMap()
|
private var fillers: Map<Int, Boolean> = emptyMap()
|
||||||
private var generator: IGenerator? = null
|
private var generator: IGenerator? = null
|
||||||
|
@ -352,17 +357,17 @@ class ResultViewModel2 : ViewModel() {
|
||||||
MutableLiveData(null)
|
MutableLiveData(null)
|
||||||
val page: LiveData<Resource<ResultData>?> = _page
|
val page: LiveData<Resource<ResultData>?> = _page
|
||||||
|
|
||||||
private val _episodes: MutableLiveData<ResourceSome<List<ResultEpisode>>> =
|
private val _episodes: MutableLiveData<Resource<List<ResultEpisode>>?> =
|
||||||
MutableLiveData(ResourceSome.Loading())
|
MutableLiveData(Resource.Loading())
|
||||||
val episodes: LiveData<ResourceSome<List<ResultEpisode>>> = _episodes
|
val episodes: LiveData<Resource<List<ResultEpisode>>?> = _episodes
|
||||||
|
|
||||||
private val _movie: MutableLiveData<ResourceSome<Pair<UiText, ResultEpisode>>> =
|
private val _movie: MutableLiveData<Resource<Pair<UiText, ResultEpisode>>?> =
|
||||||
MutableLiveData(ResourceSome.None)
|
MutableLiveData(null)
|
||||||
val movie: LiveData<ResourceSome<Pair<UiText, ResultEpisode>>> = _movie
|
val movie: LiveData<Resource<Pair<UiText, ResultEpisode>>?> = _movie
|
||||||
|
|
||||||
private val _episodesCountText: MutableLiveData<Some<UiText>> =
|
private val _episodesCountText: MutableLiveData<UiText?> =
|
||||||
MutableLiveData(Some.None)
|
MutableLiveData(null)
|
||||||
val episodesCountText: LiveData<Some<UiText>> = _episodesCountText
|
val episodesCountText: LiveData<UiText?> = _episodesCountText
|
||||||
|
|
||||||
private val _trailers: MutableLiveData<List<ExtractedTrailerData>> =
|
private val _trailers: MutableLiveData<List<ExtractedTrailerData>> =
|
||||||
MutableLiveData(mutableListOf())
|
MutableLiveData(mutableListOf())
|
||||||
|
@ -384,16 +389,16 @@ class ResultViewModel2 : ViewModel() {
|
||||||
MutableLiveData(emptyList())
|
MutableLiveData(emptyList())
|
||||||
val recommendations: LiveData<List<SearchResponse>> = _recommendations
|
val recommendations: LiveData<List<SearchResponse>> = _recommendations
|
||||||
|
|
||||||
private val _selectedRange: MutableLiveData<Some<UiText>> =
|
private val _selectedRange: MutableLiveData<UiText?> =
|
||||||
MutableLiveData(Some.None)
|
MutableLiveData(null)
|
||||||
val selectedRange: LiveData<Some<UiText>> = _selectedRange
|
val selectedRange: LiveData<UiText?> = _selectedRange
|
||||||
|
|
||||||
private val _selectedSeason: MutableLiveData<Some<UiText>> =
|
private val _selectedSeason: MutableLiveData<UiText?> =
|
||||||
MutableLiveData(Some.None)
|
MutableLiveData(null)
|
||||||
val selectedSeason: LiveData<Some<UiText>> = _selectedSeason
|
val selectedSeason: LiveData<UiText?> = _selectedSeason
|
||||||
|
|
||||||
private val _selectedDubStatus: MutableLiveData<Some<UiText>> = MutableLiveData(Some.None)
|
private val _selectedDubStatus: MutableLiveData<UiText?> = MutableLiveData(null)
|
||||||
val selectedDubStatus: LiveData<Some<UiText>> = _selectedDubStatus
|
val selectedDubStatus: LiveData<UiText?> = _selectedDubStatus
|
||||||
|
|
||||||
private val _selectedRangeIndex: MutableLiveData<Int> =
|
private val _selectedRangeIndex: MutableLiveData<Int> =
|
||||||
MutableLiveData(-1)
|
MutableLiveData(-1)
|
||||||
|
@ -406,12 +411,12 @@ class ResultViewModel2 : ViewModel() {
|
||||||
private val _selectedDubStatusIndex: MutableLiveData<Int> = MutableLiveData(-1)
|
private val _selectedDubStatusIndex: MutableLiveData<Int> = MutableLiveData(-1)
|
||||||
val selectedDubStatusIndex: LiveData<Int> = _selectedDubStatusIndex
|
val selectedDubStatusIndex: LiveData<Int> = _selectedDubStatusIndex
|
||||||
|
|
||||||
private val _loadedLinks: MutableLiveData<Some<LinkProgress>> = MutableLiveData(Some.None)
|
private val _loadedLinks: MutableLiveData<LinkProgress?> = MutableLiveData(null)
|
||||||
val loadedLinks: LiveData<Some<LinkProgress>> = _loadedLinks
|
val loadedLinks: LiveData<LinkProgress?> = _loadedLinks
|
||||||
|
|
||||||
private val _resumeWatching: MutableLiveData<Some<ResumeWatchingStatus>> =
|
private val _resumeWatching: MutableLiveData<ResumeWatchingStatus?> =
|
||||||
MutableLiveData(Some.None)
|
MutableLiveData(null)
|
||||||
val resumeWatching: LiveData<Some<ResumeWatchingStatus>> = _resumeWatching
|
val resumeWatching: LiveData<ResumeWatchingStatus?> = _resumeWatching
|
||||||
|
|
||||||
private val _episodeSynopsis: MutableLiveData<String?> = MutableLiveData(null)
|
private val _episodeSynopsis: MutableLiveData<String?> = MutableLiveData(null)
|
||||||
val episodeSynopsis: LiveData<String?> = _episodeSynopsis
|
val episodeSynopsis: LiveData<String?> = _episodeSynopsis
|
||||||
|
@ -421,8 +426,8 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "RVM2"
|
const val TAG = "RVM2"
|
||||||
private const val EPISODE_RANGE_SIZE = 20
|
//private const val EPISODE_RANGE_SIZE = 20
|
||||||
private const val EPISODE_RANGE_OVERLOAD = 30
|
//private const val EPISODE_RANGE_OVERLOAD = 30
|
||||||
|
|
||||||
private fun List<SeasonData>?.getSeason(season: Int?): SeasonData? {
|
private fun List<SeasonData>?.getSeason(season: Int?): SeasonData? {
|
||||||
if (season == null) return null
|
if (season == null) return null
|
||||||
|
@ -432,6 +437,8 @@ class ResultViewModel2 : ViewModel() {
|
||||||
fun updateWatchStatus(currentResponse: LoadResponse, status: WatchType) {
|
fun updateWatchStatus(currentResponse: LoadResponse, status: WatchType) {
|
||||||
val currentId = currentResponse.getId()
|
val currentId = currentResponse.getId()
|
||||||
|
|
||||||
|
val currentWatchType = getResultWatchState(currentId)
|
||||||
|
|
||||||
DataStoreHelper.setResultWatchState(currentId, status.internalId)
|
DataStoreHelper.setResultWatchState(currentId, status.internalId)
|
||||||
val current = DataStoreHelper.getBookmarkedData(currentId)
|
val current = DataStoreHelper.getBookmarkedData(currentId)
|
||||||
val currentTime = System.currentTimeMillis()
|
val currentTime = System.currentTimeMillis()
|
||||||
|
@ -449,6 +456,9 @@ class ResultViewModel2 : ViewModel() {
|
||||||
currentResponse.year
|
currentResponse.year
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if (currentWatchType != status) {
|
||||||
|
MainActivity.bookmarksUpdatedEvent(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun filterName(name: String?): String? {
|
private fun filterName(name: String?): String? {
|
||||||
|
@ -467,12 +477,16 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun getRanges(allEpisodes: Map<EpisodeIndexer, List<ResultEpisode>>): Map<EpisodeIndexer, List<EpisodeRange>> {
|
private fun getRanges(
|
||||||
|
allEpisodes: Map<EpisodeIndexer, List<ResultEpisode>>,
|
||||||
|
EPISODE_RANGE_SIZE: Int
|
||||||
|
): Map<EpisodeIndexer, List<EpisodeRange>> {
|
||||||
return allEpisodes.keys.mapNotNull { index ->
|
return allEpisodes.keys.mapNotNull { index ->
|
||||||
val episodes =
|
val episodes =
|
||||||
allEpisodes[index] ?: return@mapNotNull null // this should never happened
|
allEpisodes[index] ?: return@mapNotNull null // this should never happened
|
||||||
|
|
||||||
// fast case
|
// fast case
|
||||||
|
val EPISODE_RANGE_OVERLOAD = EPISODE_RANGE_SIZE + 10
|
||||||
if (episodes.size <= EPISODE_RANGE_OVERLOAD) {
|
if (episodes.size <= EPISODE_RANGE_OVERLOAD) {
|
||||||
return@mapNotNull index to listOf(
|
return@mapNotNull index to listOf(
|
||||||
EpisodeRange(
|
EpisodeRange(
|
||||||
|
@ -744,7 +758,6 @@ class ResultViewModel2 : ViewModel() {
|
||||||
if (currentLinks.isEmpty()) {
|
if (currentLinks.isEmpty()) {
|
||||||
main {
|
main {
|
||||||
showToast(
|
showToast(
|
||||||
activity,
|
|
||||||
R.string.no_links_found_toast,
|
R.string.no_links_found_toast,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
)
|
)
|
||||||
|
@ -753,7 +766,6 @@ class ResultViewModel2 : ViewModel() {
|
||||||
} else {
|
} else {
|
||||||
main {
|
main {
|
||||||
showToast(
|
showToast(
|
||||||
activity,
|
|
||||||
R.string.download_started,
|
R.string.download_started,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
)
|
)
|
||||||
|
@ -800,8 +812,8 @@ class ResultViewModel2 : ViewModel() {
|
||||||
private val _watchStatus: MutableLiveData<WatchType> = MutableLiveData(WatchType.NONE)
|
private val _watchStatus: MutableLiveData<WatchType> = MutableLiveData(WatchType.NONE)
|
||||||
val watchStatus: LiveData<WatchType> get() = _watchStatus
|
val watchStatus: LiveData<WatchType> get() = _watchStatus
|
||||||
|
|
||||||
private val _selectPopup: MutableLiveData<Some<SelectPopup>> = MutableLiveData(Some.None)
|
private val _selectPopup: MutableLiveData<SelectPopup?> = MutableLiveData(null)
|
||||||
val selectPopup: LiveData<Some<SelectPopup>> get() = _selectPopup
|
val selectPopup: LiveData<SelectPopup?> = _selectPopup
|
||||||
|
|
||||||
|
|
||||||
fun updateWatchStatus(status: WatchType) {
|
fun updateWatchStatus(status: WatchType) {
|
||||||
|
@ -885,23 +897,22 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelLinks() {
|
fun cancelLinks() {
|
||||||
println("called::cancelLinks")
|
|
||||||
currentLoadLinkJob?.cancel()
|
currentLoadLinkJob?.cancel()
|
||||||
currentLoadLinkJob = null
|
currentLoadLinkJob = null
|
||||||
_loadedLinks.postValue(Some.None)
|
_loadedLinks.postValue(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun postPopup(text: UiText, options: List<UiText>, callback: suspend (Int?) -> Unit) {
|
private fun postPopup(text: UiText, options: List<UiText>, callback: suspend (Int?) -> Unit) {
|
||||||
_selectPopup.postValue(
|
_selectPopup.postValue(
|
||||||
some(SelectPopup.SelectText(
|
SelectPopup.SelectText(
|
||||||
text,
|
text,
|
||||||
options
|
options
|
||||||
) { value ->
|
) { value ->
|
||||||
viewModelScope.launchSafe {
|
viewModelScope.launchSafe {
|
||||||
_selectPopup.postValue(Some.None)
|
_selectPopup.postValue(null)
|
||||||
callback.invoke(value)
|
callback.invoke(value)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -912,15 +923,15 @@ class ResultViewModel2 : ViewModel() {
|
||||||
callback: suspend (Int?) -> Unit
|
callback: suspend (Int?) -> Unit
|
||||||
) {
|
) {
|
||||||
_selectPopup.postValue(
|
_selectPopup.postValue(
|
||||||
some(SelectPopup.SelectArray(
|
SelectPopup.SelectArray(
|
||||||
text,
|
text,
|
||||||
options,
|
options,
|
||||||
) { value ->
|
) { value ->
|
||||||
viewModelScope.launchSafe {
|
viewModelScope.launchSafe {
|
||||||
_selectPopup.value = Some.None
|
_selectPopup.postValue(null)
|
||||||
callback.invoke(value)
|
callback.invoke(value)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -988,7 +999,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
val subs: MutableSet<SubtitleData> = mutableSetOf()
|
val subs: MutableSet<SubtitleData> = mutableSetOf()
|
||||||
fun updatePage() {
|
fun updatePage() {
|
||||||
if (isVisible && isActive) {
|
if (isVisible && isActive) {
|
||||||
_loadedLinks.postValue(some(LinkProgress(links.size, subs.size)))
|
_loadedLinks.postValue(LinkProgress(links.size, subs.size))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -1005,7 +1016,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
} finally {
|
} finally {
|
||||||
_loadedLinks.postValue(Some.None)
|
_loadedLinks.postValue(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
return LinkLoadingResult(sortUrls(links), sortSubs(subs))
|
return LinkLoadingResult(sortUrls(links), sortSubs(subs))
|
||||||
|
@ -1027,9 +1038,9 @@ class ResultViewModel2 : ViewModel() {
|
||||||
logError(t)
|
logError(t)
|
||||||
main {
|
main {
|
||||||
if (t is ActivityNotFoundException) {
|
if (t is ActivityNotFoundException) {
|
||||||
showToast(activity, txt(R.string.app_not_found_error), Toast.LENGTH_LONG)
|
showToast(txt(R.string.app_not_found_error), Toast.LENGTH_LONG)
|
||||||
} else {
|
} else {
|
||||||
showToast(activity, t.toString(), Toast.LENGTH_LONG)
|
showToast(t.toString(), Toast.LENGTH_LONG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1138,9 +1149,9 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun handleAction(activity: Activity?, click: EpisodeClickEvent) =
|
fun handleAction(click: EpisodeClickEvent) =
|
||||||
viewModelScope.launchSafe {
|
viewModelScope.launchSafe {
|
||||||
handleEpisodeClickEvent(activity, click)
|
handleEpisodeClickEvent(click)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ExternalApp(
|
data class ExternalApp(
|
||||||
|
@ -1170,7 +1181,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
_episodeSynopsis.postValue(null)
|
_episodeSynopsis.postValue(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleEpisodeClickEvent(activity: Activity?, click: EpisodeClickEvent) {
|
private suspend fun handleEpisodeClickEvent(click: EpisodeClickEvent) {
|
||||||
when (click.action) {
|
when (click.action) {
|
||||||
ACTION_SHOW_OPTIONS -> {
|
ACTION_SHOW_OPTIONS -> {
|
||||||
val options = mutableListOf<Pair<UiText, Int>>()
|
val options = mutableListOf<Pair<UiText, Int>>()
|
||||||
|
@ -1228,27 +1239,26 @@ class ResultViewModel2 : ViewModel() {
|
||||||
options
|
options
|
||||||
) { result ->
|
) { result ->
|
||||||
handleEpisodeClickEvent(
|
handleEpisodeClickEvent(
|
||||||
activity,
|
|
||||||
click.copy(action = result ?: return@postPopup)
|
click.copy(action = result ?: return@postPopup)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_CLICK_DEFAULT -> {
|
ACTION_CLICK_DEFAULT -> {
|
||||||
activity?.let { ctx ->
|
activity?.let { ctx ->
|
||||||
if (ctx.isConnectedToChromecast()) {
|
if (ctx.isConnectedToChromecast()) {
|
||||||
handleEpisodeClickEvent(
|
handleEpisodeClickEvent(
|
||||||
activity,
|
|
||||||
click.copy(action = ACTION_CHROME_CAST_EPISODE)
|
click.copy(action = ACTION_CHROME_CAST_EPISODE)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val action = getPlayerAction(ctx)
|
val action = getPlayerAction(ctx)
|
||||||
handleEpisodeClickEvent(
|
handleEpisodeClickEvent(
|
||||||
activity,
|
|
||||||
click.copy(action = action)
|
click.copy(action = action)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_SHOW_DESCRIPTION -> {
|
ACTION_SHOW_DESCRIPTION -> {
|
||||||
_episodeSynopsis.postValue(click.data.description)
|
_episodeSynopsis.postValue(click.data.description)
|
||||||
}
|
}
|
||||||
|
@ -1280,15 +1290,16 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
showToast(
|
showToast(
|
||||||
activity,
|
|
||||||
R.string.download_started,
|
R.string.download_started,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_SHOW_TOAST -> {
|
ACTION_SHOW_TOAST -> {
|
||||||
showToast(activity, R.string.play_episode_toast, Toast.LENGTH_SHORT)
|
showToast(R.string.play_episode_toast, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_DOWNLOAD_EPISODE -> {
|
ACTION_DOWNLOAD_EPISODE -> {
|
||||||
val response = currentResponse ?: return
|
val response = currentResponse ?: return
|
||||||
downloadEpisode(
|
downloadEpisode(
|
||||||
|
@ -1303,6 +1314,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
response.url
|
response.url
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_DOWNLOAD_MIRROR -> {
|
ACTION_DOWNLOAD_MIRROR -> {
|
||||||
val response = currentResponse ?: return
|
val response = currentResponse ?: return
|
||||||
acquireSingleLink(
|
acquireSingleLink(
|
||||||
|
@ -1326,12 +1338,12 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
showToast(
|
showToast(
|
||||||
activity,
|
|
||||||
R.string.download_started,
|
R.string.download_started,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_RELOAD_EPISODE -> {
|
ACTION_RELOAD_EPISODE -> {
|
||||||
ioSafe {
|
ioSafe {
|
||||||
loadLinks(
|
loadLinks(
|
||||||
|
@ -1342,6 +1354,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_CHROME_CAST_MIRROR -> {
|
ACTION_CHROME_CAST_MIRROR -> {
|
||||||
acquireSingleLink(
|
acquireSingleLink(
|
||||||
click.data,
|
click.data,
|
||||||
|
@ -1351,6 +1364,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
startChromecast(activity, click.data, result.links, result.subs, index)
|
startChromecast(activity, click.data, result.links, result.subs, index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_PLAY_EPISODE_IN_BROWSER -> acquireSingleLink(
|
ACTION_PLAY_EPISODE_IN_BROWSER -> acquireSingleLink(
|
||||||
click.data,
|
click.data,
|
||||||
isCasting = true,
|
isCasting = true,
|
||||||
|
@ -1364,6 +1378,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
logError(e)
|
logError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_COPY_LINK -> {
|
ACTION_COPY_LINK -> {
|
||||||
acquireSingleLink(
|
acquireSingleLink(
|
||||||
click.data,
|
click.data,
|
||||||
|
@ -1377,16 +1392,18 @@ class ResultViewModel2 : ViewModel() {
|
||||||
val link = result.links[index]
|
val link = result.links[index]
|
||||||
val clip = ClipData.newPlainText(link.name, link.url)
|
val clip = ClipData.newPlainText(link.name, link.url)
|
||||||
serviceClipboard.setPrimaryClip(clip)
|
serviceClipboard.setPrimaryClip(clip)
|
||||||
showToast(act, R.string.copy_link_toast, Toast.LENGTH_SHORT)
|
showToast(R.string.copy_link_toast, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_CHROME_CAST_EPISODE -> {
|
ACTION_CHROME_CAST_EPISODE -> {
|
||||||
startChromecast(activity, click.data)
|
startChromecast(activity, click.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> {
|
ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> {
|
||||||
loadLinks(click.data, isVisible = true, isCasting = true) { links ->
|
loadLinks(click.data, isVisible = true, isCasting = true) { links ->
|
||||||
if (links.links.isEmpty()) {
|
if (links.links.isEmpty()) {
|
||||||
showToast(activity, R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT)
|
||||||
return@loadLinks
|
return@loadLinks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1397,6 +1414,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_PLAY_EPISODE_IN_WEB_VIDEO -> acquireSingleLink(
|
ACTION_PLAY_EPISODE_IN_WEB_VIDEO -> acquireSingleLink(
|
||||||
click.data,
|
click.data,
|
||||||
isCasting = true,
|
isCasting = true,
|
||||||
|
@ -1413,6 +1431,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
result.subs
|
result.subs
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_PLAY_EPISODE_IN_MPV -> acquireSingleLink(
|
ACTION_PLAY_EPISODE_IN_MPV -> acquireSingleLink(
|
||||||
click.data,
|
click.data,
|
||||||
isCasting = true,
|
isCasting = true,
|
||||||
|
@ -1428,6 +1447,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
result.subs
|
result.subs
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_PLAY_EPISODE_IN_PLAYER -> {
|
ACTION_PLAY_EPISODE_IN_PLAYER -> {
|
||||||
val data = currentResponse?.syncData?.toList() ?: emptyList()
|
val data = currentResponse?.syncData?.toList() ?: emptyList()
|
||||||
val list =
|
val list =
|
||||||
|
@ -1448,6 +1468,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTION_MARK_AS_WATCHED -> {
|
ACTION_MARK_AS_WATCHED -> {
|
||||||
val isWatched =
|
val isWatched =
|
||||||
DataStoreHelper.getVideoWatchState(click.data.id) == VideoWatchState.Watched
|
DataStoreHelper.getVideoWatchState(click.data.id) == VideoWatchState.Watched
|
||||||
|
@ -1487,13 +1508,14 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val realRecommendations = ArrayList<SearchResponse>()
|
val realRecommendations = ArrayList<SearchResponse>()
|
||||||
val apiNames = apis.filter {
|
val apiNames = synchronized(apis) {
|
||||||
it.name.contains("gogoanime", true) ||
|
apis.filter {
|
||||||
it.name.contains("9anime", true)
|
it.name.contains("gogoanime", true) ||
|
||||||
}.map {
|
it.name.contains("9anime", true)
|
||||||
it.name
|
}.map {
|
||||||
|
it.name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
meta.recommendations?.forEach { rec ->
|
meta.recommendations?.forEach { rec ->
|
||||||
apiNames.forEach { name ->
|
apiNames.forEach { name ->
|
||||||
realRecommendations.add(rec.copy(apiName = name))
|
realRecommendations.add(rec.copy(apiName = name))
|
||||||
|
@ -1672,10 +1694,10 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
private fun postMovie() {
|
private fun postMovie() {
|
||||||
val response = currentResponse
|
val response = currentResponse
|
||||||
_episodes.postValue(ResourceSome.None)
|
_episodes.postValue(null)
|
||||||
|
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
_movie.postValue(ResourceSome.None)
|
_movie.postValue(null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1692,11 +1714,11 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
val data = getMovie()
|
val data = getMovie()
|
||||||
_episodes.postValue(ResourceSome.None)
|
_episodes.postValue(null)
|
||||||
if (text == null || data == null) {
|
if (text == null || data == null) {
|
||||||
_movie.postValue(ResourceSome.None)
|
_movie.postValue(null)
|
||||||
} else {
|
} else {
|
||||||
_movie.postValue(ResourceSome.Success(text to data))
|
_movie.postValue(Resource.Success(text to data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1705,14 +1727,14 @@ class ResultViewModel2 : ViewModel() {
|
||||||
postMovie()
|
postMovie()
|
||||||
} else {
|
} else {
|
||||||
_episodes.postValue(
|
_episodes.postValue(
|
||||||
ResourceSome.Success(
|
Resource.Success(
|
||||||
getEpisodes(
|
getEpisodes(
|
||||||
currentIndex ?: return,
|
currentIndex ?: return,
|
||||||
currentRange ?: return
|
currentRange ?: return
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_movie.postValue(ResourceSome.None)
|
_movie.postValue(null)
|
||||||
}
|
}
|
||||||
postResume()
|
postResume()
|
||||||
}
|
}
|
||||||
|
@ -1755,14 +1777,14 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
val size = currentEpisodes[indexer]?.size
|
val size = currentEpisodes[indexer]?.size
|
||||||
_episodesCountText.postValue(
|
_episodesCountText.postValue(
|
||||||
some(
|
|
||||||
if (isMovie) null else
|
if (isMovie) null else
|
||||||
txt(
|
txt(
|
||||||
R.string.episode_format,
|
R.string.episode_format,
|
||||||
size,
|
size,
|
||||||
txt(if (size == 1) R.string.episode else R.string.episodes),
|
txt(if (size == 1) R.string.episode else R.string.episodes),
|
||||||
)
|
)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_selectedSeasonIndex.postValue(
|
_selectedSeasonIndex.postValue(
|
||||||
|
@ -1770,29 +1792,29 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
|
|
||||||
_selectedSeason.postValue(
|
_selectedSeason.postValue(
|
||||||
some(
|
|
||||||
if (isMovie || currentSeasons.size <= 1) null else
|
|
||||||
when (indexer.season) {
|
|
||||||
0 -> txt(R.string.no_season)
|
|
||||||
else -> {
|
|
||||||
val seasonNames = (currentResponse as? EpisodeResponse)?.seasonNames
|
|
||||||
val seasonData = seasonNames.getSeason(indexer.season)
|
|
||||||
|
|
||||||
// If displaySeason is null then only show the name!
|
if (isMovie || currentSeasons.size <= 1) null else
|
||||||
if (seasonData?.name != null && seasonData.displaySeason == null) {
|
when (indexer.season) {
|
||||||
txt(seasonData.name)
|
0 -> txt(R.string.no_season)
|
||||||
} else {
|
else -> {
|
||||||
val suffix = seasonData?.name?.let { " $it" } ?: ""
|
val seasonNames = (currentResponse as? EpisodeResponse)?.seasonNames
|
||||||
txt(
|
val seasonData = seasonNames.getSeason(indexer.season)
|
||||||
R.string.season_format,
|
|
||||||
txt(R.string.season),
|
// If displaySeason is null then only show the name!
|
||||||
seasonData?.displaySeason ?: indexer.season,
|
if (seasonData?.name != null && seasonData.displaySeason == null) {
|
||||||
suffix
|
txt(seasonData.name)
|
||||||
)
|
} else {
|
||||||
}
|
val suffix = seasonData?.name?.let { " $it" } ?: ""
|
||||||
|
txt(
|
||||||
|
R.string.season_format,
|
||||||
|
txt(R.string.season),
|
||||||
|
seasonData?.displaySeason ?: indexer.season,
|
||||||
|
suffix
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_selectedRangeIndex.postValue(
|
_selectedRangeIndex.postValue(
|
||||||
|
@ -1800,13 +1822,13 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
|
|
||||||
_selectedRange.postValue(
|
_selectedRange.postValue(
|
||||||
some(
|
|
||||||
if (isMovie) null else if ((currentRanges[indexer]?.size ?: 0) > 1) {
|
if (isMovie) null else if ((currentRanges[indexer]?.size ?: 0) > 1) {
|
||||||
txt(R.string.episodes_range, range.startEpisode, range.endEpisode)
|
txt(R.string.episodes_range, range.startEpisode, range.endEpisode)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_selectedDubStatusIndex.postValue(
|
_selectedDubStatusIndex.postValue(
|
||||||
|
@ -1814,10 +1836,10 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
|
|
||||||
_selectedDubStatus.postValue(
|
_selectedDubStatus.postValue(
|
||||||
some(
|
|
||||||
if (isMovie || currentDubStatus.size <= 1) null else
|
if (isMovie || currentDubStatus.size <= 1) null else
|
||||||
txt(indexer.dubStatus)
|
txt(indexer.dubStatus)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
currentId?.let { id ->
|
currentId?.let { id ->
|
||||||
|
@ -1851,7 +1873,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
|
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
_episodes.postValue(ResourceSome.Success(ret))
|
_episodes.postValue(Resource.Success(ret))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1869,7 +1891,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun postEpisodes(loadResponse: LoadResponse, updateFillers: Boolean) {
|
private suspend fun postEpisodes(loadResponse: LoadResponse, updateFillers: Boolean) {
|
||||||
_episodes.postValue(ResourceSome.Loading())
|
_episodes.postValue(Resource.Loading())
|
||||||
|
|
||||||
val mainId = loadResponse.getId()
|
val mainId = loadResponse.getId()
|
||||||
currentId = mainId
|
currentId = mainId
|
||||||
|
@ -1924,6 +1946,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
episodes
|
episodes
|
||||||
}
|
}
|
||||||
|
|
||||||
is TvSeriesLoadResponse -> {
|
is TvSeriesLoadResponse -> {
|
||||||
val episodes: MutableMap<EpisodeIndexer, MutableList<ResultEpisode>> =
|
val episodes: MutableMap<EpisodeIndexer, MutableList<ResultEpisode>> =
|
||||||
mutableMapOf()
|
mutableMapOf()
|
||||||
|
@ -1968,6 +1991,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
episodes
|
episodes
|
||||||
}
|
}
|
||||||
|
|
||||||
is MovieLoadResponse -> {
|
is MovieLoadResponse -> {
|
||||||
singleMap(
|
singleMap(
|
||||||
buildResultEpisode(
|
buildResultEpisode(
|
||||||
|
@ -1989,6 +2013,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is LiveStreamLoadResponse -> {
|
is LiveStreamLoadResponse -> {
|
||||||
singleMap(
|
singleMap(
|
||||||
buildResultEpisode(
|
buildResultEpisode(
|
||||||
|
@ -2010,6 +2035,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is TorrentLoadResponse -> {
|
is TorrentLoadResponse -> {
|
||||||
singleMap(
|
singleMap(
|
||||||
buildResultEpisode(
|
buildResultEpisode(
|
||||||
|
@ -2031,6 +2057,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
mapOf()
|
mapOf()
|
||||||
}
|
}
|
||||||
|
@ -2066,7 +2093,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
currentEpisodes = allEpisodes
|
currentEpisodes = allEpisodes
|
||||||
val ranges = getRanges(allEpisodes)
|
val ranges = getRanges(allEpisodes, EPISODE_RANGE_SIZE)
|
||||||
currentRanges = ranges
|
currentRanges = ranges
|
||||||
|
|
||||||
|
|
||||||
|
@ -2088,7 +2115,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun postResume() {
|
fun postResume() {
|
||||||
_resumeWatching.postValue(some(resume()))
|
_resumeWatching.postValue(resume())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resume(): ResumeWatchingStatus? {
|
private fun resume(): ResumeWatchingStatus? {
|
||||||
|
@ -2186,7 +2213,6 @@ class ResultViewModel2 : ViewModel() {
|
||||||
for (ep in currentRange) {
|
for (ep in currentRange) {
|
||||||
if (ep.getWatchProgress() > 0.9) continue
|
if (ep.getWatchProgress() > 0.9) continue
|
||||||
handleAction(
|
handleAction(
|
||||||
activity,
|
|
||||||
EpisodeClickEvent(
|
EpisodeClickEvent(
|
||||||
getPlayerAction(activity),
|
getPlayerAction(activity),
|
||||||
ep
|
ep
|
||||||
|
@ -2196,6 +2222,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
START_ACTION_LOAD_EP -> {
|
START_ACTION_LOAD_EP -> {
|
||||||
val all = currentEpisodes.values.flatten()
|
val all = currentEpisodes.values.flatten()
|
||||||
val episode =
|
val episode =
|
||||||
|
@ -2206,7 +2233,6 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
?: return@launchSafe
|
?: return@launchSafe
|
||||||
handleAction(
|
handleAction(
|
||||||
activity,
|
|
||||||
EpisodeClickEvent(
|
EpisodeClickEvent(
|
||||||
getPlayerAction(activity),
|
getPlayerAction(activity),
|
||||||
episode
|
episode
|
||||||
|
@ -2227,7 +2253,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
) =
|
) =
|
||||||
ioSafe {
|
ioSafe {
|
||||||
_page.postValue(Resource.Loading(url))
|
_page.postValue(Resource.Loading(url))
|
||||||
_episodes.postValue(ResourceSome.Loading())
|
_episodes.postValue(Resource.Loading())
|
||||||
|
|
||||||
preferDubStatus = dubStatus
|
preferDubStatus = dubStatus
|
||||||
currentShowFillers = showFillers
|
currentShowFillers = showFillers
|
||||||
|
@ -2271,6 +2297,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
is Resource.Failure -> {
|
is Resource.Failure -> {
|
||||||
_page.postValue(data)
|
_page.postValue(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
if (!isActive) return@ioSafe
|
if (!isActive) return@ioSafe
|
||||||
val loadResponse = ioWork {
|
val loadResponse = ioWork {
|
||||||
|
@ -2307,6 +2334,7 @@ class ResultViewModel2 : ViewModel() {
|
||||||
if (!isActive) return@ioSafe
|
if (!isActive) return@ioSafe
|
||||||
handleAutoStart(activity, autostart)
|
handleAutoStart(activity, autostart)
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Loading -> {
|
is Resource.Loading -> {
|
||||||
debugException { "Invalid load result" }
|
debugException { "Invalid load result" }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
package com.lagradost.cloudstream3.ui.result
|
package com.lagradost.cloudstream3.ui.result
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.databinding.ResultSelectionBinding
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
|
|
||||||
typealias SelectData = Pair<UiText?, Any>
|
typealias SelectData = Pair<UiText?, Any>
|
||||||
|
@ -17,7 +16,9 @@ class SelectAdaptor(val callback: (Any) -> Unit) : RecyclerView.Adapter<Recycler
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return SelectViewHolder(
|
return SelectViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(R.layout.result_selection, parent, false),
|
ResultSelectionBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||||
|
|
||||||
|
//LayoutInflater.from(parent.context).inflate(R.layout.result_selection, parent, false),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,10 +74,10 @@ class SelectAdaptor(val callback: (Any) -> Unit) : RecyclerView.Adapter<Recycler
|
||||||
|
|
||||||
private class SelectViewHolder
|
private class SelectViewHolder
|
||||||
constructor(
|
constructor(
|
||||||
itemView: View,
|
binding: ResultSelectionBinding,
|
||||||
) :
|
) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
private val item: MaterialButton = itemView as MaterialButton
|
private val item: MaterialButton = binding.root
|
||||||
|
|
||||||
fun update(isSelected: Boolean) {
|
fun update(isSelected: Boolean) {
|
||||||
item.isSelected = isSelected
|
item.isSelected = isSelected
|
||||||
|
|
|
@ -8,7 +8,6 @@ import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.lagradost.cloudstream3.mvvm.Some
|
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
|
@ -162,11 +161,3 @@ fun TextView?.setTextHtml(text: UiText?) {
|
||||||
this.text = str.html()
|
this.text = str.html()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TextView?.setTextHtml(text: Some<UiText>?) {
|
|
||||||
setTextHtml(if (text is Some.Success) text.value else null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun TextView?.setText(text: Some<UiText>?) {
|
|
||||||
setText(if (text is Some.Success) text.value else null)
|
|
||||||
}
|
|
|
@ -4,16 +4,15 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.ImageView
|
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.R
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.lagradost.cloudstream3.SearchResponse
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
|
import com.lagradost.cloudstream3.databinding.SearchResultGridBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.SearchResultGridExpandedBinding
|
||||||
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.IsBottomLayout
|
import com.lagradost.cloudstream3.utils.UIHelper.IsBottomLayout
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
import kotlinx.android.synthetic.main.search_result_compact.view.*
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
/** Click */
|
/** Click */
|
||||||
|
@ -39,10 +38,23 @@ class SearchAdapter(
|
||||||
var hasNext: Boolean = false
|
var hasNext: Boolean = false
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
|
val inflater = LayoutInflater.from(parent.context)
|
||||||
|
|
||||||
val layout =
|
val layout =
|
||||||
if (parent.context.IsBottomLayout()) R.layout.search_result_grid_expanded else R.layout.search_result_grid
|
if (parent.context.IsBottomLayout()) SearchResultGridExpandedBinding.inflate(
|
||||||
|
inflater,
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
) else SearchResultGridBinding.inflate(
|
||||||
|
inflater,
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
) //R.layout.search_result_grid_expanded else R.layout.search_result_grid
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return CardViewHolder(
|
return CardViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(layout, parent, false),
|
layout,
|
||||||
clickCallback,
|
clickCallback,
|
||||||
resView
|
resView
|
||||||
)
|
)
|
||||||
|
@ -73,20 +85,25 @@ class SearchAdapter(
|
||||||
|
|
||||||
class CardViewHolder
|
class CardViewHolder
|
||||||
constructor(
|
constructor(
|
||||||
itemView: View,
|
val binding: ViewBinding,
|
||||||
private val clickCallback: (SearchClickCallback) -> Unit,
|
private val clickCallback: (SearchClickCallback) -> Unit,
|
||||||
resView: AutofitRecyclerView
|
resView: AutofitRecyclerView
|
||||||
) :
|
) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
val cardView: ImageView = itemView.imageView
|
|
||||||
|
|
||||||
private val compactView = false//itemView.context.getGridIsCompact()
|
private val compactView = false//itemView.context.getGridIsCompact()
|
||||||
private val coverHeight: Int =
|
private val coverHeight: Int =
|
||||||
if (compactView) 80.toPx else (resView.itemWidth / 0.68).roundToInt()
|
if (compactView) 80.toPx else (resView.itemWidth / 0.68).roundToInt()
|
||||||
|
|
||||||
|
private val cardView = when(binding) {
|
||||||
|
is SearchResultGridExpandedBinding -> binding.imageView
|
||||||
|
is SearchResultGridBinding -> binding.imageView
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
fun bind(card: SearchResponse, position: Int) {
|
fun bind(card: SearchResponse, position: Int) {
|
||||||
if (!compactView) {
|
if (!compactView) {
|
||||||
cardView.apply {
|
cardView?.apply {
|
||||||
layoutParams = FrameLayout.LayoutParams(
|
layoutParams = FrameLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
coverHeight
|
coverHeight
|
||||||
|
|
|
@ -33,6 +33,8 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys
|
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentSearchBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.HomeSelectMainpageBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.Resource
|
import com.lagradost.cloudstream3.mvvm.Resource
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
|
@ -56,8 +58,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
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
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
|
||||||
const val SEARCH_PREF_TAGS = "search_pref_tags"
|
const val SEARCH_PREF_TAGS = "search_pref_tags"
|
||||||
|
@ -89,6 +89,7 @@ class SearchFragment : Fragment() {
|
||||||
|
|
||||||
private val searchViewModel: SearchViewModel by activityViewModels()
|
private val searchViewModel: SearchViewModel by activityViewModels()
|
||||||
private var bottomSheetDialog: BottomSheetDialog? = null
|
private var bottomSheetDialog: BottomSheetDialog? = null
|
||||||
|
var binding: FragmentSearchBinding? = null
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
@ -99,18 +100,21 @@ class SearchFragment : Fragment() {
|
||||||
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
|
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
|
||||||
)
|
)
|
||||||
bottomSheetDialog?.ownShow()
|
bottomSheetDialog?.ownShow()
|
||||||
return inflater.inflate(
|
|
||||||
if (isTvSettings()) R.layout.fragment_search_tv else R.layout.fragment_search,
|
val layout = if (isTvSettings()) R.layout.fragment_search_tv else R.layout.fragment_search
|
||||||
container,
|
|
||||||
false
|
val root = inflater.inflate(layout, container, false)
|
||||||
)
|
// TODO TRYCATCH
|
||||||
|
binding = FragmentSearchBinding.bind(root)
|
||||||
|
|
||||||
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fixGrid() {
|
private fun fixGrid() {
|
||||||
activity?.getSpanCount()?.let {
|
activity?.getSpanCount()?.let {
|
||||||
currentSpan = it
|
currentSpan = it
|
||||||
}
|
}
|
||||||
search_autofit_results.spanCount = currentSpan
|
binding?.searchAutofitResults?.spanCount = currentSpan
|
||||||
currentSpan = currentSpan
|
currentSpan = currentSpan
|
||||||
HomeFragment.configEvent.invoke(currentSpan)
|
HomeFragment.configEvent.invoke(currentSpan)
|
||||||
}
|
}
|
||||||
|
@ -123,6 +127,7 @@ class SearchFragment : Fragment() {
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
bottomSheetDialog?.ownHide()
|
bottomSheetDialog?.ownHide()
|
||||||
|
binding = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +186,7 @@ class SearchFragment : Fragment() {
|
||||||
searchViewModel.reloadRepos()
|
searchViewModel.reloadRepos()
|
||||||
context?.filterProviderByPreferredMedia()?.let { validAPIs ->
|
context?.filterProviderByPreferredMedia()?.let { validAPIs ->
|
||||||
bindChips(
|
bindChips(
|
||||||
home_select_group,
|
binding?.tvtypesChipsScroll?.tvtypesChips,
|
||||||
selectedSearchTypes,
|
selectedSearchTypes,
|
||||||
validAPIs.flatMap { api -> api.supportedTypes }.distinct()
|
validAPIs.flatMap { api -> api.supportedTypes }.distinct()
|
||||||
) { list ->
|
) { list ->
|
||||||
|
@ -189,7 +194,7 @@ class SearchFragment : Fragment() {
|
||||||
setKey(SEARCH_PREF_TAGS, selectedSearchTypes)
|
setKey(SEARCH_PREF_TAGS, selectedSearchTypes)
|
||||||
selectedSearchTypes.clear()
|
selectedSearchTypes.clear()
|
||||||
selectedSearchTypes.addAll(list)
|
selectedSearchTypes.addAll(list)
|
||||||
search(main_search?.query?.toString())
|
search(binding?.mainSearch?.query?.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,24 +204,27 @@ class SearchFragment : Fragment() {
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
context?.fixPaddingStatusbar(searchRoot)
|
fixPaddingStatusbar(binding?.searchRoot)
|
||||||
fixGrid()
|
fixGrid()
|
||||||
reloadRepos()
|
reloadRepos()
|
||||||
|
|
||||||
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder>? = activity?.let {
|
binding?.apply {
|
||||||
SearchAdapter(
|
val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder>? =
|
||||||
ArrayList(),
|
SearchAdapter(
|
||||||
search_autofit_results,
|
ArrayList(),
|
||||||
) { callback ->
|
searchAutofitResults,
|
||||||
SearchHelper.handleSearchClickCallback(activity, callback)
|
) { callback ->
|
||||||
}
|
SearchHelper.handleSearchClickCallback(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
searchAutofitResults.adapter = adapter
|
||||||
|
searchLoadingBar.alpha = 0f
|
||||||
}
|
}
|
||||||
|
|
||||||
search_autofit_results.adapter = adapter
|
|
||||||
search_loading_bar.alpha = 0f
|
|
||||||
|
|
||||||
val searchExitIcon =
|
val searchExitIcon =
|
||||||
main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
|
binding?.mainSearch?.findViewById<ImageView>(androidx.appcompat.R.id.search_close_btn)
|
||||||
// val searchMagIcon =
|
// val searchMagIcon =
|
||||||
// main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
|
// main_search.findViewById<ImageView>(androidx.appcompat.R.id.search_mag_icon)
|
||||||
//searchMagIcon.scaleX = 0.65f
|
//searchMagIcon.scaleX = 0.65f
|
||||||
|
@ -230,7 +238,7 @@ class SearchFragment : Fragment() {
|
||||||
)!!.toMutableSet()
|
)!!.toMutableSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
search_filter.setOnClickListener { searchView ->
|
binding?.searchFilter?.setOnClickListener { searchView ->
|
||||||
searchView?.context?.let { ctx ->
|
searchView?.context?.let { ctx ->
|
||||||
val validAPIs = ctx.filterProviderByPreferredMedia(hasHomePageIsRequired = false)
|
val validAPIs = ctx.filterProviderByPreferredMedia(hasHomePageIsRequired = false)
|
||||||
var currentValidApis = listOf<MainAPI>()
|
var currentValidApis = listOf<MainAPI>()
|
||||||
|
@ -241,7 +249,13 @@ class SearchFragment : Fragment() {
|
||||||
BottomSheetDialog(ctx)
|
BottomSheetDialog(ctx)
|
||||||
|
|
||||||
builder.behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
builder.behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
builder.setContentView(R.layout.home_select_mainpage)
|
|
||||||
|
val binding: HomeSelectMainpageBinding = HomeSelectMainpageBinding.inflate(
|
||||||
|
builder.layoutInflater,
|
||||||
|
null,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
builder.setContentView(binding.root)
|
||||||
builder.show()
|
builder.show()
|
||||||
builder.let { dialog ->
|
builder.let { dialog ->
|
||||||
val isMultiLang = ctx.getApiProviderLangSettings().let { set ->
|
val isMultiLang = ctx.getApiProviderLangSettings().let { set ->
|
||||||
|
@ -303,7 +317,7 @@ class SearchFragment : Fragment() {
|
||||||
?: mutableListOf(TvType.Movie, TvType.TvSeries)
|
?: mutableListOf(TvType.Movie, TvType.TvSeries)
|
||||||
|
|
||||||
bindChips(
|
bindChips(
|
||||||
dialog.home_select_group,
|
binding.tvtypesChipsScroll.tvtypesChips,
|
||||||
selectedSearchTypes,
|
selectedSearchTypes,
|
||||||
TvType.values().toList()
|
TvType.values().toList()
|
||||||
) { list ->
|
) { list ->
|
||||||
|
@ -343,15 +357,15 @@ class SearchFragment : Fragment() {
|
||||||
?: mutableListOf(TvType.Movie, TvType.TvSeries)
|
?: mutableListOf(TvType.Movie, TvType.TvSeries)
|
||||||
|
|
||||||
if (isTrueTvSettings()) {
|
if (isTrueTvSettings()) {
|
||||||
search_filter.isFocusable = true
|
binding?.searchFilter?.isFocusable = true
|
||||||
search_filter.isFocusableInTouchMode = true
|
binding?.searchFilter?.isFocusableInTouchMode = true
|
||||||
}
|
}
|
||||||
|
|
||||||
main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
binding?.mainSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
override fun onQueryTextSubmit(query: String): Boolean {
|
override fun onQueryTextSubmit(query: String): Boolean {
|
||||||
search(query)
|
search(query)
|
||||||
|
|
||||||
main_search?.let {
|
binding?.mainSearch?.let {
|
||||||
hideKeyboard(it)
|
hideKeyboard(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,17 +379,17 @@ class SearchFragment : Fragment() {
|
||||||
searchViewModel.clearSearch()
|
searchViewModel.clearSearch()
|
||||||
searchViewModel.updateHistory()
|
searchViewModel.updateHistory()
|
||||||
}
|
}
|
||||||
|
binding?.apply {
|
||||||
search_history_holder?.isVisible = showHistory
|
searchHistoryHolder.isVisible = showHistory
|
||||||
|
searchMasterRecycler.isVisible = !showHistory && isAdvancedSearch
|
||||||
search_master_recycler?.isVisible = !showHistory && isAdvancedSearch
|
searchAutofitResults.isVisible = !showHistory && !isAdvancedSearch
|
||||||
search_autofit_results?.isVisible = !showHistory && !isAdvancedSearch
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
search_clear_call_history?.setOnClickListener {
|
binding?.searchClearCallHistory?.setOnClickListener {
|
||||||
activity?.let { ctx ->
|
activity?.let { ctx ->
|
||||||
val builder: AlertDialog.Builder = AlertDialog.Builder(ctx)
|
val builder: AlertDialog.Builder = AlertDialog.Builder(ctx)
|
||||||
val dialogClickListener =
|
val dialogClickListener =
|
||||||
|
@ -409,8 +423,8 @@ class SearchFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(searchViewModel.currentHistory) { list ->
|
observe(searchViewModel.currentHistory) { list ->
|
||||||
search_clear_call_history?.isVisible = list.isNotEmpty()
|
binding?.searchClearCallHistory?.isVisible = list.isNotEmpty()
|
||||||
(search_history_recycler.adapter as? SearchHistoryAdaptor?)?.updateList(list)
|
(binding?.searchHistoryRecycler?.adapter as? SearchHistoryAdaptor?)?.updateList(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
searchViewModel.updateHistory()
|
searchViewModel.updateHistory()
|
||||||
|
@ -420,20 +434,20 @@ class SearchFragment : Fragment() {
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
it.value.let { data ->
|
it.value.let { data ->
|
||||||
if (data.isNotEmpty()) {
|
if (data.isNotEmpty()) {
|
||||||
(search_autofit_results?.adapter as? SearchAdapter)?.updateList(data)
|
(binding?.searchAutofitResults?.adapter as? SearchAdapter)?.updateList(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
searchExitIcon.alpha = 1f
|
searchExitIcon?.alpha = 1f
|
||||||
search_loading_bar.alpha = 0f
|
binding?.searchLoadingBar?.alpha = 0f
|
||||||
}
|
}
|
||||||
is Resource.Failure -> {
|
is Resource.Failure -> {
|
||||||
// Toast.makeText(activity, "Server error", Toast.LENGTH_LONG).show()
|
// Toast.makeText(activity, "Server error", Toast.LENGTH_LONG).show()
|
||||||
searchExitIcon.alpha = 1f
|
searchExitIcon?.alpha = 1f
|
||||||
search_loading_bar.alpha = 0f
|
binding?.searchLoadingBar?.alpha = 0f
|
||||||
}
|
}
|
||||||
is Resource.Loading -> {
|
is Resource.Loading -> {
|
||||||
searchExitIcon.alpha = 0f
|
searchExitIcon?.alpha = 0f
|
||||||
search_loading_bar.alpha = 1f
|
binding?.searchLoadingBar?.alpha = 1f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -443,7 +457,7 @@ class SearchFragment : Fragment() {
|
||||||
try {
|
try {
|
||||||
// https://stackoverflow.com/questions/6866238/concurrent-modification-exception-adding-to-an-arraylist
|
// https://stackoverflow.com/questions/6866238/concurrent-modification-exception-adding-to-an-arraylist
|
||||||
listLock.lock()
|
listLock.lock()
|
||||||
(search_master_recycler?.adapter as ParentItemAdapter?)?.apply {
|
(binding?.searchMasterRecycler?.adapter as ParentItemAdapter?)?.apply {
|
||||||
val newItems = list.map { ongoing ->
|
val newItems = list.map { ongoing ->
|
||||||
val dataList =
|
val dataList =
|
||||||
if (ongoing.data is Resource.Success) ongoing.data.value else ArrayList()
|
if (ongoing.data is Resource.Success) ongoing.data.value else ArrayList()
|
||||||
|
@ -477,7 +491,7 @@ class SearchFragment : Fragment() {
|
||||||
|
|
||||||
val masterAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
|
val masterAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
|
||||||
ParentItemAdapter(mutableListOf(), { callback ->
|
ParentItemAdapter(mutableListOf(), { callback ->
|
||||||
SearchHelper.handleSearchClickCallback(activity, callback)
|
SearchHelper.handleSearchClickCallback(callback)
|
||||||
}, { item ->
|
}, { item ->
|
||||||
bottomSheetDialog = activity?.loadHomepageList(item, dismissCallback = {
|
bottomSheetDialog = activity?.loadHomepageList(item, dismissCallback = {
|
||||||
bottomSheetDialog = null
|
bottomSheetDialog = null
|
||||||
|
@ -490,8 +504,8 @@ class SearchFragment : Fragment() {
|
||||||
SEARCH_HISTORY_OPEN -> {
|
SEARCH_HISTORY_OPEN -> {
|
||||||
searchViewModel.clearSearch()
|
searchViewModel.clearSearch()
|
||||||
if (searchItem.type.isNotEmpty())
|
if (searchItem.type.isNotEmpty())
|
||||||
updateChips(home_select_group, searchItem.type.toMutableList())
|
updateChips(binding?.tvtypesChipsScroll?.tvtypesChips, searchItem.type.toMutableList())
|
||||||
main_search?.setQuery(searchItem.searchText, true)
|
binding?.mainSearch?.setQuery(searchItem.searchText, true)
|
||||||
}
|
}
|
||||||
SEARCH_HISTORY_REMOVE -> {
|
SEARCH_HISTORY_REMOVE -> {
|
||||||
removeKey(SEARCH_HISTORY_KEY, searchItem.key)
|
removeKey(SEARCH_HISTORY_KEY, searchItem.key)
|
||||||
|
@ -503,20 +517,23 @@ class SearchFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
search_history_recycler?.adapter = historyAdapter
|
binding?.apply {
|
||||||
search_history_recycler?.layoutManager = GridLayoutManager(context, 1)
|
searchHistoryRecycler.adapter = historyAdapter
|
||||||
|
searchHistoryRecycler.layoutManager = GridLayoutManager(context, 1)
|
||||||
|
|
||||||
search_master_recycler?.adapter = masterAdapter
|
searchMasterRecycler.adapter = masterAdapter
|
||||||
search_master_recycler?.layoutManager = GridLayoutManager(context, 1)
|
searchMasterRecycler.layoutManager = GridLayoutManager(context, 1)
|
||||||
|
|
||||||
// Automatically search the specified query, this allows the app search to launch from intent
|
// Automatically search the specified query, this allows the app search to launch from intent
|
||||||
arguments?.getString(SEARCH_QUERY)?.let { query ->
|
arguments?.getString(SEARCH_QUERY)?.let { query ->
|
||||||
if (query.isBlank()) return@let
|
if (query.isBlank()) return@let
|
||||||
main_search?.setQuery(query, true)
|
mainSearch.setQuery(query, true)
|
||||||
// Clear the query as to not make it request the same query every time the page is opened
|
// Clear the query as to not make it request the same query every time the page is opened
|
||||||
arguments?.putString(SEARCH_QUERY, null)
|
arguments?.putString(SEARCH_QUERY, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// SubtitlesFragment.push(activity)
|
// SubtitlesFragment.push(activity)
|
||||||
//searchViewModel.search("iron man")
|
//searchViewModel.search("iron man")
|
||||||
//(activity as AppCompatActivity).loadResult("https://shiro.is/overlord-dubbed", "overlord-dubbed", "Shiro")
|
//(activity as AppCompatActivity).loadResult("https://shiro.is/overlord-dubbed", "overlord-dubbed", "Shiro")
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.search
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import com.lagradost.cloudstream3.CommonActivity.activity
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.MainActivity
|
import com.lagradost.cloudstream3.MainActivity
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
@ -15,21 +16,21 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
|
|
||||||
object SearchHelper {
|
object SearchHelper {
|
||||||
fun handleSearchClickCallback(activity: Activity?, callback: SearchClickCallback) {
|
fun handleSearchClickCallback(callback: SearchClickCallback) {
|
||||||
val card = callback.card
|
val card = callback.card
|
||||||
when (callback.action) {
|
when (callback.action) {
|
||||||
SEARCH_ACTION_LOAD -> {
|
SEARCH_ACTION_LOAD -> {
|
||||||
activity.loadSearchResult(card)
|
loadSearchResult(card)
|
||||||
}
|
}
|
||||||
SEARCH_ACTION_PLAY_FILE -> {
|
SEARCH_ACTION_PLAY_FILE -> {
|
||||||
if (card is DataStoreHelper.ResumeWatchingResult) {
|
if (card is DataStoreHelper.ResumeWatchingResult) {
|
||||||
val id = card.id
|
val id = card.id
|
||||||
if(id == null) {
|
if(id == null) {
|
||||||
showToast(activity, R.string.error_invalid_id, Toast.LENGTH_SHORT)
|
showToast(R.string.error_invalid_id, Toast.LENGTH_SHORT)
|
||||||
} else {
|
} else {
|
||||||
if (card.isFromDownload) {
|
if (card.isFromDownload) {
|
||||||
handleDownloadClick(
|
handleDownloadClick(
|
||||||
activity, DownloadClickEvent(
|
DownloadClickEvent(
|
||||||
DOWNLOAD_ACTION_PLAY_FILE,
|
DOWNLOAD_ACTION_PLAY_FILE,
|
||||||
VideoDownloadHelper.DownloadEpisodeCached(
|
VideoDownloadHelper.DownloadEpisodeCached(
|
||||||
card.name,
|
card.name,
|
||||||
|
@ -45,12 +46,11 @@ object SearchHelper {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
activity.loadSearchResult(card, START_ACTION_LOAD_EP, id)
|
loadSearchResult(card, START_ACTION_LOAD_EP, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
handleSearchClickCallback(
|
handleSearchClickCallback(
|
||||||
activity,
|
|
||||||
SearchClickCallback(SEARCH_ACTION_LOAD, callback.view, -1, callback.card)
|
SearchClickCallback(SEARCH_ACTION_LOAD, callback.view, -1, callback.card)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -60,10 +60,10 @@ object SearchHelper {
|
||||||
(activity as? MainActivity?)?.apply {
|
(activity as? MainActivity?)?.apply {
|
||||||
loadPopup(callback.card)
|
loadPopup(callback.card)
|
||||||
} ?: kotlin.run {
|
} ?: kotlin.run {
|
||||||
showToast(activity, callback.card.name, Toast.LENGTH_SHORT)
|
showToast(callback.card.name, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showToast(activity, callback.card.name, Toast.LENGTH_SHORT)
|
showToast(callback.card.name, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
import kotlinx.android.synthetic.main.search_history_item.view.*
|
import com.lagradost.cloudstream3.databinding.AccountSingleBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.SearchHistoryItemBinding
|
||||||
|
|
||||||
data class SearchHistoryItem(
|
data class SearchHistoryItem(
|
||||||
@JsonProperty("searchedAt") val searchedAt: Long,
|
@JsonProperty("searchedAt") val searchedAt: Long,
|
||||||
|
@ -34,8 +35,7 @@ class SearchHistoryAdaptor(
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return CardViewHolder(
|
return CardViewHolder(
|
||||||
LayoutInflater.from(parent.context)
|
SearchHistoryItemBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||||
.inflate(R.layout.search_history_item, parent, false),
|
|
||||||
clickCallback,
|
clickCallback,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -65,22 +65,24 @@ class SearchHistoryAdaptor(
|
||||||
|
|
||||||
class CardViewHolder
|
class CardViewHolder
|
||||||
constructor(
|
constructor(
|
||||||
itemView: View,
|
val binding: SearchHistoryItemBinding,
|
||||||
private val clickCallback: (SearchHistoryCallback) -> Unit,
|
private val clickCallback: (SearchHistoryCallback) -> Unit,
|
||||||
) :
|
) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
private val removeButton: ImageView = itemView.home_history_remove
|
// private val removeButton: ImageView = itemView.home_history_remove
|
||||||
private val openButton: View = itemView.home_history_tab
|
// private val openButton: View = itemView.home_history_tab
|
||||||
private val title: TextView = itemView.home_history_title
|
// private val title: TextView = itemView.home_history_title
|
||||||
|
|
||||||
fun bind(card: SearchHistoryItem) {
|
fun bind(card: SearchHistoryItem) {
|
||||||
title.text = card.searchText
|
binding.apply {
|
||||||
|
homeHistoryTitle.text = card.searchText
|
||||||
|
|
||||||
removeButton.setOnClickListener {
|
homeHistoryRemove.setOnClickListener {
|
||||||
clickCallback.invoke(SearchHistoryCallback(card, SEARCH_HISTORY_REMOVE))
|
clickCallback.invoke(SearchHistoryCallback(card, SEARCH_HISTORY_REMOVE))
|
||||||
}
|
}
|
||||||
openButton.setOnClickListener {
|
homeHistoryTab.setOnClickListener {
|
||||||
clickCallback.invoke(SearchHistoryCallback(card, SEARCH_HISTORY_OPEN))
|
clickCallback.invoke(SearchHistoryCallback(card, SEARCH_HISTORY_OPEN))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.lagradost.cloudstream3.ui.search
|
package com.lagradost.cloudstream3.ui.search
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
|
@ -10,14 +9,20 @@ import androidx.cardview.widget.CardView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.palette.graphics.Palette
|
import androidx.palette.graphics.Palette
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.AnimeSearchResponse
|
||||||
|
import com.lagradost.cloudstream3.DubStatus
|
||||||
|
import com.lagradost.cloudstream3.LiveSearchResponse
|
||||||
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.SearchQuality
|
||||||
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
|
import com.lagradost.cloudstream3.isMovieType
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import kotlinx.android.synthetic.main.home_result_grid.view.*
|
|
||||||
|
|
||||||
object SearchResultBuilder {
|
object SearchResultBuilder {
|
||||||
private val showCache: MutableMap<String, Boolean> = mutableMapOf()
|
private val showCache: MutableMap<String, Boolean> = mutableMapOf()
|
||||||
|
@ -45,19 +50,21 @@ object SearchResultBuilder {
|
||||||
nextFocusDown: Int? = null,
|
nextFocusDown: Int? = null,
|
||||||
colorCallback : ((Palette) -> Unit)? = null
|
colorCallback : ((Palette) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val cardView: ImageView = itemView.imageView
|
val cardView: ImageView = itemView.findViewById(R.id.imageView)
|
||||||
val cardText: TextView? = itemView.imageText
|
val cardText: TextView? = itemView.findViewById(R.id.imageText)
|
||||||
|
|
||||||
val textIsDub: TextView? = itemView.text_is_dub
|
val textIsDub: TextView? = itemView.findViewById(R.id.text_is_dub)
|
||||||
val textIsSub: TextView? = itemView.text_is_sub
|
val textIsSub: TextView? = itemView.findViewById(R.id.text_is_sub)
|
||||||
val textFlag: TextView? = itemView.text_flag
|
val textFlag: TextView? = itemView.findViewById(R.id.text_flag)
|
||||||
val textQuality: TextView? = itemView.text_quality
|
val rating: TextView? = itemView.findViewById(R.id.text_rating)
|
||||||
val shadow: View? = itemView.title_shadow
|
|
||||||
|
|
||||||
val bg: CardView = itemView.background_card
|
val textQuality: TextView? = itemView.findViewById(R.id.text_quality)
|
||||||
|
val shadow: View? = itemView.findViewById(R.id.title_shadow)
|
||||||
|
|
||||||
val bar: ProgressBar? = itemView.watchProgress
|
val bg: CardView = itemView.findViewById(R.id.background_card)
|
||||||
val playImg: ImageView? = itemView.search_item_download_play
|
|
||||||
|
val bar: ProgressBar? = itemView.findViewById(R.id.watchProgress)
|
||||||
|
val playImg: ImageView? = itemView.findViewById(R.id.search_item_download_play)
|
||||||
|
|
||||||
// Do logic
|
// Do logic
|
||||||
|
|
||||||
|
@ -66,12 +73,25 @@ object SearchResultBuilder {
|
||||||
textIsDub?.isVisible = false
|
textIsDub?.isVisible = false
|
||||||
textIsSub?.isVisible = false
|
textIsSub?.isVisible = false
|
||||||
textFlag?.isVisible = false
|
textFlag?.isVisible = false
|
||||||
|
rating?.isVisible = false
|
||||||
|
|
||||||
val showSub = showCache[textIsDub?.context?.getString(R.string.show_sub_key)] ?: false
|
val showSub = showCache[textIsDub?.context?.getString(R.string.show_sub_key)] ?: false
|
||||||
val showDub = showCache[textIsDub?.context?.getString(R.string.show_dub_key)] ?: false
|
val showDub = showCache[textIsDub?.context?.getString(R.string.show_dub_key)] ?: false
|
||||||
val showTitle = showCache[cardText?.context?.getString(R.string.show_title_key)] ?: false
|
val showTitle = showCache[cardText?.context?.getString(R.string.show_title_key)] ?: false
|
||||||
val showHd = showCache[textQuality?.context?.getString(R.string.show_hd_key)] ?: false
|
val showHd = showCache[textQuality?.context?.getString(R.string.show_hd_key)] ?: false
|
||||||
|
|
||||||
|
if(card is SyncAPI.LibraryItem) {
|
||||||
|
val showRating = (card.personalRating ?: 0) != 0
|
||||||
|
rating?.isVisible = showRating
|
||||||
|
if (showRating) {
|
||||||
|
// We want to show 8.5 but not 8.0 hence the replace
|
||||||
|
val ratingText = ((card.personalRating ?: 0).toDouble() / 10).toString()
|
||||||
|
.replace(".0", "")
|
||||||
|
|
||||||
|
rating?.text = ratingText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
shadow?.isVisible = showTitle
|
shadow?.isVisible = showTitle
|
||||||
|
|
||||||
when (card.quality) {
|
when (card.quality) {
|
||||||
|
@ -142,15 +162,42 @@ object SearchResultBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bg.setOnClickListener {
|
bg.isFocusable = false
|
||||||
click(it)
|
bg.isFocusableInTouchMode = false
|
||||||
|
if(!isTrueTvSettings()) {
|
||||||
|
bg.setOnClickListener {
|
||||||
|
click(it)
|
||||||
|
}
|
||||||
|
bg.setOnLongClickListener {
|
||||||
|
longClick(it)
|
||||||
|
return@setOnLongClickListener true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
click(it)
|
click(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextFocusUp != null) {
|
if (nextFocusUp != null) {
|
||||||
|
itemView.nextFocusUpId = nextFocusUp
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextFocusDown != null) {
|
||||||
|
itemView.nextFocusDownId = nextFocusDown
|
||||||
|
}
|
||||||
|
|
||||||
|
/*when (nextFocusBehavior) {
|
||||||
|
true -> itemView.nextFocusLeftId = bg.id
|
||||||
|
false -> itemView.nextFocusRightId = bg.id
|
||||||
|
null -> {
|
||||||
|
bg.nextFocusRightId = -1
|
||||||
|
bg.nextFocusLeftId = -1
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*if (nextFocusUp != null) {
|
||||||
bg.nextFocusUpId = nextFocusUp
|
bg.nextFocusUpId = nextFocusUp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,36 +205,26 @@ object SearchResultBuilder {
|
||||||
bg.nextFocusDownId = nextFocusDown
|
bg.nextFocusDownId = nextFocusDown
|
||||||
}
|
}
|
||||||
|
|
||||||
when (nextFocusBehavior) {
|
*/
|
||||||
true -> bg.nextFocusLeftId = bg.id
|
|
||||||
false -> bg.nextFocusRightId = bg.id
|
|
||||||
null -> {
|
|
||||||
bg.nextFocusRightId = -1
|
|
||||||
bg.nextFocusLeftId = -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTrueTvSettings()) {
|
if (isTrueTvSettings()) {
|
||||||
bg.isFocusable = true
|
// bg.isFocusable = true
|
||||||
bg.isFocusableInTouchMode = true
|
// bg.isFocusableInTouchMode = true
|
||||||
bg.touchscreenBlocksFocus = false
|
// bg.touchscreenBlocksFocus = false
|
||||||
itemView.isFocusableInTouchMode = true
|
itemView.isFocusableInTouchMode = true
|
||||||
itemView.isFocusable = true
|
itemView.isFocusable = true
|
||||||
}
|
}
|
||||||
|
|
||||||
bg.setOnLongClickListener {
|
/**/
|
||||||
longClick(it)
|
|
||||||
return@setOnLongClickListener true
|
|
||||||
}
|
|
||||||
|
|
||||||
itemView.setOnLongClickListener {
|
itemView.setOnLongClickListener {
|
||||||
longClick(it)
|
longClick(it)
|
||||||
return@setOnLongClickListener true
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
bg.setOnFocusChangeListener { view, b ->
|
/*bg.setOnFocusChangeListener { view, b ->
|
||||||
focus(view, b)
|
focus(view, b)
|
||||||
}
|
}*/
|
||||||
|
|
||||||
itemView.setOnFocusChangeListener { view, b ->
|
itemView.setOnFocusChangeListener { view, b ->
|
||||||
focus(view, b)
|
focus(view, b)
|
||||||
|
|
|
@ -37,7 +37,7 @@ class SearchViewModel : ViewModel() {
|
||||||
private val _currentHistory: MutableLiveData<List<SearchHistoryItem>> = MutableLiveData()
|
private val _currentHistory: MutableLiveData<List<SearchHistoryItem>> = MutableLiveData()
|
||||||
val currentHistory: LiveData<List<SearchHistoryItem>> get() = _currentHistory
|
val currentHistory: LiveData<List<SearchHistoryItem>> get() = _currentHistory
|
||||||
|
|
||||||
private var repos = apis.map { APIRepository(it) }
|
private var repos = synchronized(apis) { apis.map { APIRepository(it) } }
|
||||||
|
|
||||||
fun clearSearch() {
|
fun clearSearch() {
|
||||||
_searchResponse.postValue(Resource.Success(ArrayList()))
|
_searchResponse.postValue(Resource.Success(ArrayList()))
|
||||||
|
@ -48,7 +48,7 @@ class SearchViewModel : ViewModel() {
|
||||||
private var onGoingSearch: Job? = null
|
private var onGoingSearch: Job? = null
|
||||||
|
|
||||||
fun reloadRepos() {
|
fun reloadRepos() {
|
||||||
repos = apis.map { APIRepository(it) }
|
repos = synchronized(apis) { apis.map { APIRepository(it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchAndCancel(
|
fun searchAndCancel(
|
||||||
|
|
|
@ -3,11 +3,10 @@ package com.lagradost.cloudstream3.ui.settings
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.AccountSingleBinding
|
||||||
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
|
|
||||||
|
@ -15,14 +14,15 @@ class AccountClickCallback(val action: Int, val view: View, val card: AuthAPI.Lo
|
||||||
|
|
||||||
class AccountAdapter(
|
class AccountAdapter(
|
||||||
val cardList: List<AuthAPI.LoginInfo>,
|
val cardList: List<AuthAPI.LoginInfo>,
|
||||||
val layout: Int = R.layout.account_single,
|
|
||||||
private val clickCallback: (AccountClickCallback) -> Unit
|
private val clickCallback: (AccountClickCallback) -> Unit
|
||||||
) :
|
) :
|
||||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return CardViewHolder(
|
return CardViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(layout, parent, false), clickCallback
|
AccountSingleBinding.inflate(LayoutInflater.from(parent.context), parent, false), //LayoutInflater.from(parent.context).inflate(layout, parent, false),
|
||||||
|
|
||||||
|
clickCallback
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,18 +43,18 @@ class AccountAdapter(
|
||||||
}
|
}
|
||||||
|
|
||||||
class CardViewHolder
|
class CardViewHolder
|
||||||
constructor(itemView: View, private val clickCallback: (AccountClickCallback) -> Unit) :
|
constructor(val binding: AccountSingleBinding, private val clickCallback: (AccountClickCallback) -> Unit) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
private val pfp: ImageView = itemView.findViewById(R.id.account_profile_picture)!!
|
// private val pfp: ImageView = itemView.findViewById(R.id.account_profile_picture)!!
|
||||||
private val accountName: TextView = itemView.findViewById(R.id.account_name)!!
|
// private val accountName: TextView = itemView.findViewById(R.id.account_name)!!
|
||||||
|
|
||||||
fun bind(card: AuthAPI.LoginInfo) {
|
fun bind(card: AuthAPI.LoginInfo) {
|
||||||
// just in case name is null account index will show, should never happened
|
// just in case name is null account index will show, should never happened
|
||||||
accountName.text = card.name ?: "%s %d".format(
|
binding.accountName.text = card.name ?: "%s %d".format(
|
||||||
accountName.context.getString(R.string.account),
|
binding.accountName.context.getString(R.string.account),
|
||||||
card.accountIndex
|
card.accountIndex
|
||||||
)
|
)
|
||||||
pfp.isVisible = pfp.setImage(card.profilePicture)
|
binding.accountProfilePicture.isVisible = binding.accountProfilePicture.setImage(card.profilePicture)
|
||||||
|
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
clickCallback.invoke(AccountClickCallback(0, itemView, card))
|
clickCallback.invoke(AccountClickCallback(0, itemView, card))
|
||||||
|
|
|
@ -15,6 +15,9 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.AccountManagmentBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.AccountSwitchBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.AddAccountInputBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
import com.lagradost.cloudstream3.syncproviders.AccountManager
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
|
||||||
|
@ -31,9 +34,6 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import kotlinx.android.synthetic.main.account_managment.*
|
|
||||||
import kotlinx.android.synthetic.main.account_switch.*
|
|
||||||
import kotlinx.android.synthetic.main.add_account_input.*
|
|
||||||
|
|
||||||
class SettingsAccount : PreferenceFragmentCompat() {
|
class SettingsAccount : PreferenceFragmentCompat() {
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -43,15 +43,18 @@ class SettingsAccount : PreferenceFragmentCompat() {
|
||||||
api: AccountManager,
|
api: AccountManager,
|
||||||
info: AuthAPI.LoginInfo
|
info: AuthAPI.LoginInfo
|
||||||
) {
|
) {
|
||||||
|
if (activity == null) return
|
||||||
|
val binding: AccountManagmentBinding =
|
||||||
|
AccountManagmentBinding.inflate(activity.layoutInflater, null, false)
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(activity ?: return, R.style.AlertDialogCustom)
|
AlertDialog.Builder(activity, R.style.AlertDialogCustom)
|
||||||
.setView(R.layout.account_managment)
|
.setView(binding.root)
|
||||||
val dialog = builder.show()
|
val dialog = builder.show()
|
||||||
|
|
||||||
dialog.account_main_profile_picture_holder?.isVisible =
|
binding.accountMainProfilePictureHolder.isVisible =
|
||||||
dialog.account_main_profile_picture?.setImage(info.profilePicture) == true
|
binding.accountMainProfilePicture.setImage(info.profilePicture)
|
||||||
|
|
||||||
dialog.account_logout?.setOnClickListener {
|
binding.accountLogout.setOnClickListener {
|
||||||
api.logOut()
|
api.logOut()
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
|
@ -60,26 +63,28 @@ class SettingsAccount : PreferenceFragmentCompat() {
|
||||||
dialog.findViewById<TextView>(R.id.account_name)?.text = it
|
dialog.findViewById<TextView>(R.id.account_name)?.text = it
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.account_site?.text = api.name
|
binding.accountSite.text = api.name
|
||||||
dialog.account_switch_account?.setOnClickListener {
|
binding.accountSwitchAccount.setOnClickListener {
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
showAccountSwitch(activity, api)
|
showAccountSwitch(activity, api)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTvSettings()) {
|
if (isTvSettings()) {
|
||||||
dialog.account_switch_account?.requestFocus()
|
binding.accountSwitchAccount.requestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showAccountSwitch(activity: FragmentActivity, api: AccountManager) {
|
private fun showAccountSwitch(activity: FragmentActivity, api: AccountManager) {
|
||||||
val accounts = api.getAccounts() ?: return
|
val accounts = api.getAccounts() ?: return
|
||||||
|
val binding: AccountSwitchBinding =
|
||||||
|
AccountSwitchBinding.inflate(activity.layoutInflater, null, false)
|
||||||
|
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(activity, R.style.AlertDialogCustom)
|
AlertDialog.Builder(activity, R.style.AlertDialogCustom)
|
||||||
.setView(R.layout.account_switch)
|
.setView(binding.root)
|
||||||
val dialog = builder.show()
|
val dialog = builder.show()
|
||||||
|
|
||||||
dialog.account_add?.setOnClickListener {
|
binding.accountAdd.setOnClickListener {
|
||||||
addAccount(activity, api)
|
addAccount(activity, api)
|
||||||
dialog?.dismissSafe(activity)
|
dialog?.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
|
@ -96,7 +101,7 @@ class SettingsAccount : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
api.accountIndex = ogIndex
|
api.accountIndex = ogIndex
|
||||||
val adapter = AccountAdapter(items, R.layout.account_single) {
|
val adapter = AccountAdapter(items) {
|
||||||
dialog?.dismissSafe(activity)
|
dialog?.dismissSafe(activity)
|
||||||
api.changeAccount(it.card.accountIndex)
|
api.changeAccount(it.card.accountIndex)
|
||||||
}
|
}
|
||||||
|
@ -111,17 +116,21 @@ class SettingsAccount : PreferenceFragmentCompat() {
|
||||||
is OAuth2API -> {
|
is OAuth2API -> {
|
||||||
api.authenticate(activity)
|
api.authenticate(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
is InAppAuthAPI -> {
|
is InAppAuthAPI -> {
|
||||||
|
if (activity == null) return
|
||||||
|
val binding: AddAccountInputBinding =
|
||||||
|
AddAccountInputBinding.inflate(activity.layoutInflater, null, false)
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(activity ?: return, R.style.AlertDialogCustom)
|
AlertDialog.Builder(activity, R.style.AlertDialogCustom)
|
||||||
.setView(R.layout.add_account_input)
|
.setView(binding.root)
|
||||||
val dialog = builder.show()
|
val dialog = builder.show()
|
||||||
|
|
||||||
val visibilityMap = mapOf(
|
val visibilityMap = listOf(
|
||||||
dialog.login_email_input to api.requiresEmail,
|
binding.loginEmailInput to api.requiresEmail,
|
||||||
dialog.login_password_input to api.requiresPassword,
|
binding.loginPasswordInput to api.requiresPassword,
|
||||||
dialog.login_server_input to api.requiresServer,
|
binding.loginServerInput to api.requiresServer,
|
||||||
dialog.login_username_input to api.requiresUsername
|
binding.loginUsernameInput to api.requiresUsername
|
||||||
)
|
)
|
||||||
|
|
||||||
if (isTvSettings()) {
|
if (isTvSettings()) {
|
||||||
|
@ -145,12 +154,12 @@ class SettingsAccount : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.login_email_input?.isVisible = api.requiresEmail
|
binding.loginEmailInput.isVisible = api.requiresEmail
|
||||||
dialog.login_password_input?.isVisible = api.requiresPassword
|
binding.loginPasswordInput.isVisible = api.requiresPassword
|
||||||
dialog.login_server_input?.isVisible = api.requiresServer
|
binding.loginServerInput.isVisible = api.requiresServer
|
||||||
dialog.login_username_input?.isVisible = api.requiresUsername
|
binding.loginUsernameInput.isVisible = api.requiresUsername
|
||||||
dialog.create_account?.isGone = api.createAccountUrl.isNullOrBlank()
|
binding.createAccount.isGone = api.createAccountUrl.isNullOrBlank()
|
||||||
dialog.create_account?.setOnClickListener {
|
binding.createAccount.setOnClickListener {
|
||||||
openBrowser(
|
openBrowser(
|
||||||
api.createAccountUrl ?: return@setOnClickListener,
|
api.createAccountUrl ?: return@setOnClickListener,
|
||||||
activity
|
activity
|
||||||
|
@ -159,43 +168,43 @@ class SettingsAccount : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val displayedItems = listOf(
|
val displayedItems = listOf(
|
||||||
dialog.login_username_input,
|
binding.loginUsernameInput,
|
||||||
dialog.login_email_input,
|
binding.loginEmailInput,
|
||||||
dialog.login_server_input,
|
binding.loginServerInput,
|
||||||
dialog.login_password_input
|
binding.loginPasswordInput
|
||||||
).filter { it.isVisible }
|
).filter { it.isVisible }
|
||||||
|
|
||||||
displayedItems.foldRight(displayedItems.firstOrNull()) { item, previous ->
|
displayedItems.foldRight(displayedItems.firstOrNull()) { item, previous ->
|
||||||
item?.id?.let { previous?.nextFocusDownId = it }
|
item.id.let { previous?.nextFocusDownId = it }
|
||||||
previous?.id?.let { item?.nextFocusUpId = it }
|
previous?.id?.let { item.nextFocusUpId = it }
|
||||||
item
|
item
|
||||||
}
|
}
|
||||||
|
|
||||||
displayedItems.firstOrNull()?.let {
|
displayedItems.firstOrNull()?.let {
|
||||||
dialog.create_account?.nextFocusDownId = it.id
|
binding.createAccount.nextFocusDownId = it.id
|
||||||
it.nextFocusUpId = dialog.create_account.id
|
it.nextFocusUpId = binding.createAccount.id
|
||||||
}
|
}
|
||||||
dialog.apply_btt?.id?.let {
|
binding.applyBtt.id.let {
|
||||||
displayedItems.lastOrNull()?.nextFocusDownId = it
|
displayedItems.lastOrNull()?.nextFocusDownId = it
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.text1?.text = api.name
|
binding.text1.text = api.name
|
||||||
|
|
||||||
if (api.storesPasswordInPlainText) {
|
if (api.storesPasswordInPlainText) {
|
||||||
api.getLatestLoginData()?.let { data ->
|
api.getLatestLoginData()?.let { data ->
|
||||||
dialog.login_email_input?.setText(data.email ?: "")
|
binding.loginEmailInput.setText(data.email ?: "")
|
||||||
dialog.login_server_input?.setText(data.server ?: "")
|
binding.loginServerInput.setText(data.server ?: "")
|
||||||
dialog.login_username_input?.setText(data.username ?: "")
|
binding.loginUsernameInput.setText(data.username ?: "")
|
||||||
dialog.login_password_input?.setText(data.password ?: "")
|
binding.loginPasswordInput.setText(data.password ?: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.apply_btt?.setOnClickListener {
|
binding.applyBtt.setOnClickListener {
|
||||||
val loginData = InAppAuthAPI.LoginData(
|
val loginData = InAppAuthAPI.LoginData(
|
||||||
username = if (api.requiresUsername) dialog.login_username_input?.text?.toString() else null,
|
username = if (api.requiresUsername) binding.loginUsernameInput.text?.toString() else null,
|
||||||
password = if (api.requiresPassword) dialog.login_password_input?.text?.toString() else null,
|
password = if (api.requiresPassword) binding.loginPasswordInput.text?.toString() else null,
|
||||||
email = if (api.requiresEmail) dialog.login_email_input?.text?.toString() else null,
|
email = if (api.requiresEmail) binding.loginEmailInput.text?.toString() else null,
|
||||||
server = if (api.requiresServer) dialog.login_server_input?.text?.toString() else null,
|
server = if (api.requiresServer) binding.loginServerInput.text?.toString() else null,
|
||||||
)
|
)
|
||||||
ioSafe {
|
ioSafe {
|
||||||
val isSuccessful = try {
|
val isSuccessful = try {
|
||||||
|
@ -207,7 +216,6 @@ class SettingsAccount : PreferenceFragmentCompat() {
|
||||||
activity.runOnUiThread {
|
activity.runOnUiThread {
|
||||||
try {
|
try {
|
||||||
showToast(
|
showToast(
|
||||||
activity,
|
|
||||||
activity.getString(if (isSuccessful) R.string.authenticated_user else R.string.authenticated_user_fail)
|
activity.getString(if (isSuccessful) R.string.authenticated_user else R.string.authenticated_user_fail)
|
||||||
.format(
|
.format(
|
||||||
api.name
|
api.name
|
||||||
|
@ -220,10 +228,11 @@ class SettingsAccount : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
dialog.cancel_btt?.setOnClickListener {
|
binding.cancelBtt.setOnClickListener {
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
throw NotImplementedError("You are trying to add an account that has an unknown login method")
|
throw NotImplementedError("You are trying to add an account that has an unknown login method")
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,17 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.view.children
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.MainSettingsBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
|
||||||
import com.lagradost.cloudstream3.ui.home.HomeFragment
|
import com.lagradost.cloudstream3.ui.home.HomeFragment
|
||||||
|
@ -22,16 +26,14 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
import kotlinx.android.synthetic.main.main_settings.*
|
|
||||||
import kotlinx.android.synthetic.main.standard_toolbar.*
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class SettingsFragment : Fragment() {
|
class SettingsFragment : Fragment() {
|
||||||
companion object {
|
companion object {
|
||||||
var beneneCount = 0
|
var beneneCount = 0
|
||||||
|
|
||||||
private var isTv : Boolean = false
|
private var isTv: Boolean = false
|
||||||
private var isTrueTv : Boolean = false
|
private var isTrueTv: Boolean = false
|
||||||
|
|
||||||
fun PreferenceFragmentCompat?.getPref(id: Int): Preference? {
|
fun PreferenceFragmentCompat?.getPref(id: Int): Preference? {
|
||||||
if (this == null) return null
|
if (this == null) return null
|
||||||
|
@ -55,26 +57,31 @@ class SettingsFragment : Fragment() {
|
||||||
|
|
||||||
fun Fragment?.setUpToolbar(title: String) {
|
fun Fragment?.setUpToolbar(title: String) {
|
||||||
if (this == null) return
|
if (this == null) return
|
||||||
settings_toolbar?.apply {
|
val settingsToolbar = view?.findViewById<MaterialToolbar>(R.id.settings_toolbar) ?: return
|
||||||
|
|
||||||
|
settingsToolbar.apply {
|
||||||
setTitle(title)
|
setTitle(title)
|
||||||
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
|
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
|
||||||
setNavigationOnClickListener {
|
setNavigationOnClickListener {
|
||||||
activity?.onBackPressed()
|
activity?.onBackPressed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.fixPaddingStatusbar(settings_toolbar)
|
fixPaddingStatusbar(settingsToolbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Fragment?.setUpToolbar(@StringRes title: Int) {
|
fun Fragment?.setUpToolbar(@StringRes title: Int) {
|
||||||
if (this == null) return
|
if (this == null) return
|
||||||
settings_toolbar?.apply {
|
val settingsToolbar = view?.findViewById<MaterialToolbar>(R.id.settings_toolbar) ?: return
|
||||||
|
|
||||||
|
settingsToolbar.apply {
|
||||||
setTitle(title)
|
setTitle(title)
|
||||||
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
|
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
|
||||||
|
children.firstOrNull { it is ImageView }?.tag = getString(R.string.tv_no_focus_tag)
|
||||||
setNavigationOnClickListener {
|
setNavigationOnClickListener {
|
||||||
activity?.onBackPressed()
|
activity?.onBackPressed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.fixPaddingStatusbar(settings_toolbar)
|
fixPaddingStatusbar(settingsToolbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFolderSize(dir: File): Long {
|
fun getFolderSize(dir: File): Long {
|
||||||
|
@ -139,12 +146,21 @@ class SettingsFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
var binding: MainSettingsBinding? = null
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?,
|
savedInstanceState: Bundle?,
|
||||||
): View? {
|
): View {
|
||||||
return inflater.inflate(R.layout.main_settings, container, false)
|
val localBinding = MainSettingsBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root
|
||||||
|
//return inflater.inflate(R.layout.main_settings, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
@ -152,41 +168,41 @@ class SettingsFragment : Fragment() {
|
||||||
activity?.navigate(id, Bundle())
|
activity?.navigate(id, Bundle())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// used to debug leaks showToast(activity,"${VideoDownloadManager.downloadStatusEvent.size} : ${VideoDownloadManager.downloadProgressEvent.size}")
|
||||||
|
|
||||||
val isTrueTv = isTrueTvSettings()
|
val isTrueTv = isTrueTvSettings()
|
||||||
|
|
||||||
for (syncApi in accountManagers) {
|
for (syncApi in accountManagers) {
|
||||||
val login = syncApi.loginInfo()
|
val login = syncApi.loginInfo()
|
||||||
val pic = login?.profilePicture ?: continue
|
val pic = login?.profilePicture ?: continue
|
||||||
if (settings_profile_pic?.setImage(
|
if (binding?.settingsProfilePic?.setImage(
|
||||||
pic,
|
pic,
|
||||||
errorImageDrawable = HomeFragment.errorProfilePic
|
errorImageDrawable = HomeFragment.errorProfilePic
|
||||||
) == true
|
) == true
|
||||||
) {
|
) {
|
||||||
settings_profile_text?.text = login.name
|
binding?.settingsProfileText?.text = login.name
|
||||||
settings_profile?.isVisible = true
|
binding?.settingsProfile?.isVisible = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding?.apply {
|
||||||
listOf(
|
listOf(
|
||||||
Pair(settings_general, R.id.action_navigation_settings_to_navigation_settings_general),
|
settingsGeneral to R.id.action_navigation_settings_to_navigation_settings_general,
|
||||||
Pair(settings_player, R.id.action_navigation_settings_to_navigation_settings_player),
|
settingsPlayer to R.id.action_navigation_settings_to_navigation_settings_player,
|
||||||
Pair(settings_credits, R.id.action_navigation_settings_to_navigation_settings_account),
|
settingsCredits to R.id.action_navigation_settings_to_navigation_settings_account,
|
||||||
Pair(settings_ui, R.id.action_navigation_settings_to_navigation_settings_ui),
|
settingsUi to R.id.action_navigation_settings_to_navigation_settings_ui,
|
||||||
Pair(settings_providers, R.id.action_navigation_settings_to_navigation_settings_providers),
|
settingsProviders to R.id.action_navigation_settings_to_navigation_settings_providers,
|
||||||
Pair(settings_updates, R.id.action_navigation_settings_to_navigation_settings_updates),
|
settingsUpdates to R.id.action_navigation_settings_to_navigation_settings_updates,
|
||||||
Pair(
|
settingsExtensions to R.id.action_navigation_settings_to_navigation_settings_extensions,
|
||||||
settings_extensions,
|
).forEach { (view, navigationId) ->
|
||||||
R.id.action_navigation_settings_to_navigation_settings_extensions
|
view.apply {
|
||||||
),
|
setOnClickListener {
|
||||||
).forEach { (view, navigationId) ->
|
navigate(navigationId)
|
||||||
view?.apply {
|
}
|
||||||
setOnClickListener {
|
if (isTrueTv) {
|
||||||
navigate(navigationId)
|
isFocusable = true
|
||||||
}
|
isFocusableInTouchMode = true
|
||||||
if (isTrueTv) {
|
}
|
||||||
isFocusable = true
|
|
||||||
isFocusableInTouchMode = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,11 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
import com.lagradost.cloudstream3.CommonActivity
|
import com.lagradost.cloudstream3.CommonActivity
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
|
import com.lagradost.cloudstream3.MainActivity
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
|
import com.lagradost.cloudstream3.databinding.AddRemoveSitesBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.AddSiteInputBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.network.initClient
|
import com.lagradost.cloudstream3.network.initClient
|
||||||
|
@ -38,8 +41,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
|
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getBasePath
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getBasePath
|
||||||
import kotlinx.android.synthetic.main.add_remove_sites.*
|
|
||||||
import kotlinx.android.synthetic.main.add_site_input.*
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
fun getCurrentLocale(context: Context): String {
|
fun getCurrentLocale(context: Context): String {
|
||||||
|
@ -69,6 +70,7 @@ val appLanguages = arrayListOf(
|
||||||
Triple("", "español", "es"),
|
Triple("", "español", "es"),
|
||||||
Triple("", "فارسی", "fa"),
|
Triple("", "فارسی", "fa"),
|
||||||
Triple("", "français", "fr"),
|
Triple("", "français", "fr"),
|
||||||
|
Triple("", "galego", "gl"),
|
||||||
Triple("", "हिन्दी", "hi"),
|
Triple("", "हिन्दी", "hi"),
|
||||||
Triple("", "hrvatski", "hr"),
|
Triple("", "hrvatski", "hr"),
|
||||||
Triple("", "magyar", "hu"),
|
Triple("", "magyar", "hu"),
|
||||||
|
@ -188,7 +190,7 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
|
|
||||||
fun showAdd() {
|
fun showAdd() {
|
||||||
val providers = allProviders.distinctBy { it.javaClass }.sortedBy { it.name }
|
val providers = synchronized(allProviders) { allProviders.distinctBy { it.javaClass }.sortedBy { it.name } }
|
||||||
activity?.showDialog(
|
activity?.showDialog(
|
||||||
providers.map { "${it.name} (${it.mainUrl})" },
|
providers.map { "${it.name} (${it.mainUrl})" },
|
||||||
-1,
|
-1,
|
||||||
|
@ -197,21 +199,23 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
||||||
{}) { selection ->
|
{}) { selection ->
|
||||||
val provider = providers.getOrNull(selection) ?: return@showDialog
|
val provider = providers.getOrNull(selection) ?: return@showDialog
|
||||||
|
|
||||||
|
val binding : AddSiteInputBinding = AddSiteInputBinding.inflate(layoutInflater,null,false)
|
||||||
|
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(context ?: return@showDialog, R.style.AlertDialogCustom)
|
AlertDialog.Builder(context ?: return@showDialog, R.style.AlertDialogCustom)
|
||||||
.setView(R.layout.add_site_input)
|
.setView(binding.root)
|
||||||
|
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
dialog.text2?.text = provider.name
|
binding.text2.text = provider.name
|
||||||
dialog.apply_btt?.setOnClickListener {
|
binding.applyBtt.setOnClickListener {
|
||||||
val name = dialog.site_name_input?.text?.toString()
|
val name = binding.siteNameInput.text?.toString()
|
||||||
val url = dialog.site_url_input?.text?.toString()
|
val url = binding.siteUrlInput.text?.toString()
|
||||||
val lang = dialog.site_lang_input?.text?.toString()
|
val lang = binding.siteLangInput.text?.toString()
|
||||||
val realLang = if (lang.isNullOrBlank()) provider.lang else lang
|
val realLang = if (lang.isNullOrBlank()) provider.lang else lang
|
||||||
if (url.isNullOrBlank() || name.isNullOrBlank() || realLang.length != 2) {
|
if (url.isNullOrBlank() || name.isNullOrBlank() || realLang.length != 2) {
|
||||||
showToast(activity, R.string.error_invalid_data, Toast.LENGTH_SHORT)
|
showToast(R.string.error_invalid_data, Toast.LENGTH_SHORT)
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,10 +223,12 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
||||||
val newSite = CustomSite(provider.javaClass.simpleName, name, url, realLang)
|
val newSite = CustomSite(provider.javaClass.simpleName, name, url, realLang)
|
||||||
current.add(newSite)
|
current.add(newSite)
|
||||||
setKey(USER_PROVIDER_API, current.toTypedArray())
|
setKey(USER_PROVIDER_API, current.toTypedArray())
|
||||||
|
// reload apis
|
||||||
|
MainActivity.afterPluginsLoadedEvent.invoke(false)
|
||||||
|
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
dialog.cancel_btt?.setOnClickListener {
|
binding.cancelBtt.setOnClickListener {
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,18 +248,19 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showAddOrDelete() {
|
fun showAddOrDelete() {
|
||||||
|
val binding : AddRemoveSitesBinding = AddRemoveSitesBinding.inflate(layoutInflater,null,false)
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(context ?: return, R.style.AlertDialogCustom)
|
AlertDialog.Builder(context ?: return, R.style.AlertDialogCustom)
|
||||||
.setView(R.layout.add_remove_sites)
|
.setView(binding.root)
|
||||||
|
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
dialog.add_site?.setOnClickListener {
|
binding.addSite.setOnClickListener {
|
||||||
showAdd()
|
showAdd()
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
dialog.remove_site?.setOnClickListener {
|
binding.removeSite.setOnClickListener {
|
||||||
showDelete()
|
showDelete()
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,8 +105,10 @@ class SettingsProviders : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
getPref(R.string.provider_lang_key)?.setOnPreferenceClickListener {
|
getPref(R.string.provider_lang_key)?.setOnPreferenceClickListener {
|
||||||
activity?.getApiProviderLangSettings()?.let { current ->
|
activity?.getApiProviderLangSettings()?.let { current ->
|
||||||
val languages = APIHolder.apis.map { it.lang }.toSet()
|
val languages = synchronized(APIHolder.apis) {
|
||||||
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName
|
APIHolder.apis.map { it.lang }.toSet()
|
||||||
|
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName
|
||||||
|
}
|
||||||
|
|
||||||
val currentList = current.map {
|
val currentList = current.map {
|
||||||
languages.indexOf(it)
|
languages.indexOf(it)
|
||||||
|
|
|
@ -13,6 +13,7 @@ import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.LogcatBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
|
||||||
|
@ -25,7 +26,6 @@ import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
||||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager
|
||||||
import kotlinx.android.synthetic.main.logcat.*
|
|
||||||
import okhttp3.internal.closeQuietly
|
import okhttp3.internal.closeQuietly
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
|
@ -60,7 +60,9 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
||||||
getPref(R.string.show_logcat_key)?.setOnPreferenceClickListener { pref ->
|
getPref(R.string.show_logcat_key)?.setOnPreferenceClickListener { pref ->
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(pref.context, R.style.AlertDialogCustom)
|
AlertDialog.Builder(pref.context, R.style.AlertDialogCustom)
|
||||||
.setView(R.layout.logcat)
|
|
||||||
|
val binding = LogcatBinding.inflate(layoutInflater,null,false )
|
||||||
|
builder.setView(binding.root)
|
||||||
|
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
@ -81,9 +83,9 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val text = log.toString()
|
val text = log.toString()
|
||||||
dialog.text1?.text = text
|
binding.text1.text = text
|
||||||
|
|
||||||
dialog.copy_btt?.setOnClickListener {
|
binding.copyBtt.setOnClickListener {
|
||||||
// Can crash on too much text
|
// Can crash on too much text
|
||||||
try {
|
try {
|
||||||
val serviceClipboard =
|
val serviceClipboard =
|
||||||
|
@ -93,14 +95,14 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
||||||
serviceClipboard.setPrimaryClip(clip)
|
serviceClipboard.setPrimaryClip(clip)
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
} catch (e: TransactionTooLargeException) {
|
} catch (e: TransactionTooLargeException) {
|
||||||
showToast(activity, R.string.clipboard_too_large)
|
showToast(R.string.clipboard_too_large)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialog.clear_btt?.setOnClickListener {
|
binding.clearBtt.setOnClickListener {
|
||||||
Runtime.getRuntime().exec("logcat -c")
|
Runtime.getRuntime().exec("logcat -c")
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
dialog.save_btt?.setOnClickListener {
|
binding.saveBtt.setOnClickListener {
|
||||||
var fileStream: OutputStream? = null
|
var fileStream: OutputStream? = null
|
||||||
try {
|
try {
|
||||||
fileStream =
|
fileStream =
|
||||||
|
@ -119,7 +121,7 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialog.close_btt?.setOnClickListener {
|
binding.closeBtt.setOnClickListener {
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
|
@ -156,7 +158,6 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
||||||
if (activity?.runAutoUpdate(false) == false) {
|
if (activity?.runAutoUpdate(false) == false) {
|
||||||
activity?.runOnUiThread {
|
activity?.runOnUiThread {
|
||||||
showToast(
|
showToast(
|
||||||
activity,
|
|
||||||
R.string.no_update_found,
|
R.string.no_update_found,
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,8 +18,10 @@ import androidx.navigation.fragment.findNavController
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent
|
import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.mvvm.Some
|
import com.lagradost.cloudstream3.databinding.AddRepoInputBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentExtensionsBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
|
import com.lagradost.cloudstream3.mvvm.observeNullable
|
||||||
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
||||||
import com.lagradost.cloudstream3.ui.result.setText
|
import com.lagradost.cloudstream3.ui.result.setText
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
|
@ -30,16 +32,22 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
import com.lagradost.cloudstream3.widget.LinearRecycleViewLayoutManager
|
import com.lagradost.cloudstream3.widget.LinearRecycleViewLayoutManager
|
||||||
import kotlinx.android.synthetic.main.add_repo_input.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_extensions.*
|
|
||||||
|
|
||||||
class ExtensionsFragment : Fragment() {
|
class ExtensionsFragment : Fragment() {
|
||||||
|
var binding: FragmentExtensionsBinding? = null
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?,
|
savedInstanceState: Bundle?,
|
||||||
): View? {
|
): View {
|
||||||
return inflater.inflate(R.layout.fragment_extensions, container, false)
|
val localBinding = FragmentExtensionsBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root//inflater.inflate(R.layout.fragment_extensions, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun View.setLayoutWidth(weight: Int) {
|
private fun View.setLayoutWidth(weight: Int) {
|
||||||
|
@ -74,7 +82,7 @@ class ExtensionsFragment : Fragment() {
|
||||||
|
|
||||||
setUpToolbar(R.string.extensions)
|
setUpToolbar(R.string.extensions)
|
||||||
|
|
||||||
repo_recycler_view?.adapter = RepoAdapter(false, {
|
binding?.repoRecyclerView?.adapter = RepoAdapter(false, {
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
R.id.navigation_settings_extensions_to_navigation_settings_plugins,
|
R.id.navigation_settings_extensions_to_navigation_settings_plugins,
|
||||||
PluginsFragment.newInstance(
|
PluginsFragment.newInstance(
|
||||||
|
@ -97,6 +105,7 @@ class ExtensionsFragment : Fragment() {
|
||||||
extensionViewModel.loadRepositories()
|
extensionViewModel.loadRepositories()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DialogInterface.BUTTON_NEGATIVE -> {}
|
DialogInterface.BUTTON_NEGATIVE -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,12 +121,12 @@ class ExtensionsFragment : Fragment() {
|
||||||
})
|
})
|
||||||
|
|
||||||
observe(extensionViewModel.repositories) {
|
observe(extensionViewModel.repositories) {
|
||||||
repo_recycler_view?.isVisible = it.isNotEmpty()
|
binding?.repoRecyclerView?.isVisible = it.isNotEmpty()
|
||||||
blank_repo_screen?.isVisible = it.isEmpty()
|
binding?.blankRepoScreen?.isVisible = it.isEmpty()
|
||||||
(repo_recycler_view?.adapter as? RepoAdapter)?.updateList(it)
|
(binding?.repoRecyclerView?.adapter as? RepoAdapter)?.updateList(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
repo_recycler_view?.apply {
|
binding?.repoRecyclerView?.apply {
|
||||||
context?.let { ctx ->
|
context?.let { ctx ->
|
||||||
layoutManager = LinearRecycleViewLayoutManager(ctx, nextFocusUpId, nextFocusDownId)
|
layoutManager = LinearRecycleViewLayoutManager(ctx, nextFocusUpId, nextFocusDownId)
|
||||||
}
|
}
|
||||||
|
@ -138,32 +147,31 @@ class ExtensionsFragment : Fragment() {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
observe(extensionViewModel.pluginStats) {
|
observeNullable(extensionViewModel.pluginStats) { value ->
|
||||||
when (it) {
|
binding?.apply {
|
||||||
is Some.Success -> {
|
if (value == null) {
|
||||||
val value = it.value
|
pluginStorageAppbar.isVisible = false
|
||||||
|
|
||||||
plugin_storage_appbar?.isVisible = true
|
return@observeNullable
|
||||||
if (value.total == 0) {
|
|
||||||
plugin_download?.setLayoutWidth(1)
|
|
||||||
plugin_disabled?.setLayoutWidth(0)
|
|
||||||
plugin_not_downloaded?.setLayoutWidth(0)
|
|
||||||
} else {
|
|
||||||
plugin_download?.setLayoutWidth(value.downloaded)
|
|
||||||
plugin_disabled?.setLayoutWidth(value.disabled)
|
|
||||||
plugin_not_downloaded?.setLayoutWidth(value.notDownloaded)
|
|
||||||
}
|
|
||||||
plugin_not_downloaded_txt.setText(value.notDownloadedText)
|
|
||||||
plugin_disabled_txt.setText(value.disabledText)
|
|
||||||
plugin_download_txt.setText(value.downloadedText)
|
|
||||||
}
|
}
|
||||||
is Some.None -> {
|
|
||||||
plugin_storage_appbar?.isVisible = false
|
pluginStorageAppbar.isVisible = true
|
||||||
|
if (value.total == 0) {
|
||||||
|
pluginDownload.setLayoutWidth(1)
|
||||||
|
pluginDisabled.setLayoutWidth(0)
|
||||||
|
pluginNotDownloaded.setLayoutWidth(0)
|
||||||
|
} else {
|
||||||
|
pluginDownload.setLayoutWidth(value.downloaded)
|
||||||
|
pluginDisabled.setLayoutWidth(value.disabled)
|
||||||
|
pluginNotDownloaded.setLayoutWidth(value.notDownloaded)
|
||||||
}
|
}
|
||||||
|
pluginNotDownloadedTxt.setText(value.notDownloadedText)
|
||||||
|
pluginDisabledTxt.setText(value.disabledText)
|
||||||
|
pluginDownloadTxt.setText(value.downloadedText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin_storage_appbar?.setOnClickListener {
|
binding?.pluginStorageAppbar?.setOnClickListener {
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
R.id.navigation_settings_extensions_to_navigation_settings_plugins,
|
R.id.navigation_settings_extensions_to_navigation_settings_plugins,
|
||||||
PluginsFragment.newInstance(
|
PluginsFragment.newInstance(
|
||||||
|
@ -175,16 +183,18 @@ class ExtensionsFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val addRepositoryClick = View.OnClickListener {
|
val addRepositoryClick = View.OnClickListener {
|
||||||
|
val ctx = context ?: return@OnClickListener
|
||||||
|
val binding = AddRepoInputBinding.inflate(LayoutInflater.from(ctx), null, false)
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(context ?: return@OnClickListener, R.style.AlertDialogCustom)
|
AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
|
||||||
.setView(R.layout.add_repo_input)
|
.setView(binding.root)
|
||||||
|
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
dialog.show()
|
dialog.show()
|
||||||
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)?.primaryClip?.getItemAt(
|
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)?.primaryClip?.getItemAt(
|
||||||
0
|
0
|
||||||
)?.text?.toString()?.let { copy ->
|
)?.text?.toString()?.let { copy ->
|
||||||
dialog.repo_url_input?.setText(copy)
|
binding.repoUrlInput.setText(copy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// dialog.list_repositories?.setOnClickListener {
|
// dialog.list_repositories?.setOnClickListener {
|
||||||
|
@ -194,14 +204,14 @@ class ExtensionsFragment : Fragment() {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// dialog.text2?.text = provider.name
|
// dialog.text2?.text = provider.name
|
||||||
dialog.apply_btt?.setOnClickListener secondListener@{
|
binding.applyBtt.setOnClickListener secondListener@{
|
||||||
val name = dialog.repo_name_input?.text?.toString()
|
val name = binding.repoNameInput.text?.toString()
|
||||||
ioSafe {
|
ioSafe {
|
||||||
val url = dialog.repo_url_input?.text?.toString()
|
val url = binding.repoUrlInput.text?.toString()
|
||||||
?.let { it1 -> RepositoryManager.parseRepoUrl(it1) }
|
?.let { it1 -> RepositoryManager.parseRepoUrl(it1) }
|
||||||
if (url.isNullOrBlank()) {
|
if (url.isNullOrBlank()) {
|
||||||
main {
|
main {
|
||||||
showToast(activity, R.string.error_invalid_data, Toast.LENGTH_SHORT)
|
showToast(R.string.error_invalid_data, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val fixedName = if (!name.isNullOrBlank()) name
|
val fixedName = if (!name.isNullOrBlank()) name
|
||||||
|
@ -216,22 +226,23 @@ class ExtensionsFragment : Fragment() {
|
||||||
}
|
}
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
dialog.cancel_btt?.setOnClickListener {
|
binding.cancelBtt.setOnClickListener {
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isTv = isTrueTvSettings()
|
val isTv = isTrueTvSettings()
|
||||||
add_repo_button?.isGone = isTv
|
binding?.apply {
|
||||||
add_repo_button_imageview_holder?.isVisible = isTv
|
addRepoButton.isGone = isTv
|
||||||
|
addRepoButtonImageviewHolder.isVisible = isTv
|
||||||
|
|
||||||
// Band-aid for Fire TV
|
// Band-aid for Fire TV
|
||||||
plugin_storage_appbar?.isFocusableInTouchMode = isTv
|
pluginStorageAppbar.isFocusableInTouchMode = isTv
|
||||||
add_repo_button_imageview?.isFocusableInTouchMode = isTv
|
addRepoButtonImageview.isFocusableInTouchMode = isTv
|
||||||
|
|
||||||
add_repo_button?.setOnClickListener(addRepositoryClick)
|
|
||||||
add_repo_button_imageview?.setOnClickListener(addRepositoryClick)
|
|
||||||
|
|
||||||
|
addRepoButton.setOnClickListener(addRepositoryClick)
|
||||||
|
addRepoButtonImageview.setOnClickListener(addRepositoryClick)
|
||||||
|
}
|
||||||
reloadRepositories()
|
reloadRepositories()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,7 +7,6 @@ import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.amap
|
import com.lagradost.cloudstream3.amap
|
||||||
import com.lagradost.cloudstream3.mvvm.Some
|
|
||||||
import com.lagradost.cloudstream3.mvvm.debugAssert
|
import com.lagradost.cloudstream3.mvvm.debugAssert
|
||||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||||
import com.lagradost.cloudstream3.plugins.PluginManager.getPluginsOnline
|
import com.lagradost.cloudstream3.plugins.PluginManager.getPluginsOnline
|
||||||
|
@ -40,8 +39,8 @@ class ExtensionsViewModel : ViewModel() {
|
||||||
private val _repositories = MutableLiveData<Array<RepositoryData>>()
|
private val _repositories = MutableLiveData<Array<RepositoryData>>()
|
||||||
val repositories: LiveData<Array<RepositoryData>> = _repositories
|
val repositories: LiveData<Array<RepositoryData>> = _repositories
|
||||||
|
|
||||||
private val _pluginStats: MutableLiveData<Some<PluginStats>> = MutableLiveData(Some.None)
|
private val _pluginStats: MutableLiveData<PluginStats?> = MutableLiveData(null)
|
||||||
val pluginStats: LiveData<Some<PluginStats>> = _pluginStats
|
val pluginStats: LiveData<PluginStats?> = _pluginStats
|
||||||
|
|
||||||
//TODO CACHE GET REQUESTS
|
//TODO CACHE GET REQUESTS
|
||||||
// DO not use viewModelScope.launchSafe, it will ANR on slow internet
|
// DO not use viewModelScope.launchSafe, it will ANR on slow internet
|
||||||
|
@ -78,7 +77,7 @@ class ExtensionsViewModel : ViewModel() {
|
||||||
debugAssert({ stats.downloaded + stats.notDownloaded + stats.disabled != stats.total }) {
|
debugAssert({ stats.downloaded + stats.notDownloaded + stats.disabled != stats.total }) {
|
||||||
"downloaded(${stats.downloaded}) + notDownloaded(${stats.notDownloaded}) + disabled(${stats.disabled}) != total(${stats.total})"
|
"downloaded(${stats.downloaded}) + notDownloaded(${stats.notDownloaded}) + disabled(${stats.disabled}) != total(${stats.total})"
|
||||||
}
|
}
|
||||||
_pluginStats.postValue(Some.Success(stats))
|
_pluginStats.postValue(stats)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun repos() = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
|
private fun repos() = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.ui.settings.extensions
|
||||||
import android.text.format.Formatter.formatShortFileSize
|
import android.text.format.Formatter.formatShortFileSize
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
|
@ -13,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity
|
import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity
|
||||||
import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN
|
import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.RepositoryItemBinding
|
||||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||||
import com.lagradost.cloudstream3.plugins.VotingApi.getVotes
|
import com.lagradost.cloudstream3.plugins.VotingApi.getVotes
|
||||||
import com.lagradost.cloudstream3.ui.result.setText
|
import com.lagradost.cloudstream3.ui.result.setText
|
||||||
|
@ -26,10 +26,11 @@ import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
|
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
import kotlinx.android.synthetic.main.repository_item.view.*
|
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
import kotlin.math.floor
|
||||||
|
import kotlin.math.log10
|
||||||
|
|
||||||
|
|
||||||
data class PluginViewData(
|
data class PluginViewData(
|
||||||
|
@ -45,8 +46,10 @@ class PluginAdapter(
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
val layout = if(isTrueTvSettings()) R.layout.repository_item_tv else R.layout.repository_item
|
val layout = if(isTrueTvSettings()) R.layout.repository_item_tv else R.layout.repository_item
|
||||||
|
val inflated = LayoutInflater.from(parent.context).inflate(layout, parent, false)
|
||||||
|
|
||||||
return PluginViewHolder(
|
return PluginViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(layout, parent, false)
|
RepositoryItemBinding.bind(inflated) // may crash
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,8 +85,10 @@ class PluginAdapter(
|
||||||
|
|
||||||
// Clear glide image because setImageResource doesn't override
|
// Clear glide image because setImageResource doesn't override
|
||||||
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
||||||
holder.itemView.entry_icon?.let { pluginIcon ->
|
if (holder is PluginViewHolder) {
|
||||||
GlideApp.with(pluginIcon).clear(pluginIcon)
|
holder.binding.entryIcon.let { pluginIcon ->
|
||||||
|
GlideApp.with(pluginIcon).clear(pluginIcon)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
super.onViewRecycled(holder)
|
super.onViewRecycled(holder)
|
||||||
}
|
}
|
||||||
|
@ -112,7 +117,7 @@ class PluginAdapter(
|
||||||
fun prettyCount(number: Number): String? {
|
fun prettyCount(number: Number): String? {
|
||||||
val suffix = charArrayOf(' ', 'k', 'M', 'B', 'T', 'P', 'E')
|
val suffix = charArrayOf(' ', 'k', 'M', 'B', 'T', 'P', 'E')
|
||||||
val numValue = number.toLong()
|
val numValue = number.toLong()
|
||||||
val value = Math.floor(Math.log10(numValue.toDouble())).toInt()
|
val value = floor(log10(numValue.toDouble())).toInt()
|
||||||
val base = value / 3
|
val base = value / 3
|
||||||
return if (value >= 3 && base < suffix.size) {
|
return if (value >= 3 && base < suffix.size) {
|
||||||
DecimalFormat("#0.00").format(
|
DecimalFormat("#0.00").format(
|
||||||
|
@ -127,8 +132,8 @@ class PluginAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class PluginViewHolder(itemView: View) :
|
inner class PluginViewHolder(val binding: RepositoryItemBinding) :
|
||||||
RecyclerView.ViewHolder(itemView) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
fun bind(
|
fun bind(
|
||||||
data: PluginViewData,
|
data: PluginViewData,
|
||||||
|
@ -138,17 +143,17 @@ class PluginAdapter(
|
||||||
val name = metadata.name.removeSuffix("Provider")
|
val name = metadata.name.removeSuffix("Provider")
|
||||||
val alpha = if (disabled) 0.6f else 1f
|
val alpha = if (disabled) 0.6f else 1f
|
||||||
val isLocal = !data.plugin.second.url.startsWith("http")
|
val isLocal = !data.plugin.second.url.startsWith("http")
|
||||||
itemView.main_text?.alpha = alpha
|
binding.mainText.alpha = alpha
|
||||||
itemView.sub_text?.alpha = alpha
|
binding.subText.alpha = alpha
|
||||||
|
|
||||||
val drawableInt = if (data.isDownloaded)
|
val drawableInt = if (data.isDownloaded)
|
||||||
R.drawable.ic_baseline_delete_outline_24
|
R.drawable.ic_baseline_delete_outline_24
|
||||||
else R.drawable.netflix_download
|
else R.drawable.netflix_download
|
||||||
|
|
||||||
itemView.nsfw_marker?.isVisible = metadata.tvTypes?.contains("NSFW") ?: false
|
binding.nsfwMarker.isVisible = metadata.tvTypes?.contains("NSFW") ?: false
|
||||||
itemView.action_button?.setImageResource(drawableInt)
|
binding.actionButton.setImageResource(drawableInt)
|
||||||
|
|
||||||
itemView.action_button?.setOnClickListener {
|
binding.actionButton.setOnClickListener {
|
||||||
iconClickCallback.invoke(data.plugin)
|
iconClickCallback.invoke(data.plugin)
|
||||||
}
|
}
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
|
@ -169,10 +174,11 @@ class PluginAdapter(
|
||||||
|
|
||||||
if (data.isDownloaded) {
|
if (data.isDownloaded) {
|
||||||
// On local plugins page the filepath is provided instead of url.
|
// On local plugins page the filepath is provided instead of url.
|
||||||
val plugin = PluginManager.urlPlugins[metadata.url] ?: PluginManager.plugins[metadata.url]
|
val plugin =
|
||||||
|
PluginManager.urlPlugins[metadata.url] ?: PluginManager.plugins[metadata.url]
|
||||||
if (plugin?.openSettings != null) {
|
if (plugin?.openSettings != null) {
|
||||||
itemView.action_settings?.isVisible = true
|
binding.actionSettings.isVisible = true
|
||||||
itemView.action_settings.setOnClickListener {
|
binding.actionSettings.setOnClickListener {
|
||||||
try {
|
try {
|
||||||
plugin.openSettings!!.invoke(itemView.context)
|
plugin.openSettings!!.invoke(itemView.context)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
@ -185,13 +191,13 @@ class PluginAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
itemView.action_settings?.isVisible = false
|
binding.actionSettings.isVisible = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
itemView.action_settings?.isVisible = false
|
binding.actionSettings.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemView.entry_icon?.setImage(//itemView.entry_icon?.height ?:
|
if (!binding.entryIcon.setImage(//itemView.entry_icon?.height ?:
|
||||||
metadata.iconUrl?.replace(
|
metadata.iconUrl?.replace(
|
||||||
"%size%",
|
"%size%",
|
||||||
"$iconSize"
|
"$iconSize"
|
||||||
|
@ -201,41 +207,47 @@ class PluginAdapter(
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
errorImageDrawable = R.drawable.ic_baseline_extension_24
|
errorImageDrawable = R.drawable.ic_baseline_extension_24
|
||||||
) != true
|
)
|
||||||
) {
|
) {
|
||||||
itemView.entry_icon?.setImageResource(R.drawable.ic_baseline_extension_24)
|
binding.entryIcon.setImageResource(R.drawable.ic_baseline_extension_24)
|
||||||
}
|
}
|
||||||
|
|
||||||
itemView.ext_version?.isVisible = true
|
binding.extVersion.isVisible = true
|
||||||
itemView.ext_version?.text = "v${metadata.version}"
|
binding.extVersion.text = "v${metadata.version}"
|
||||||
|
|
||||||
if (metadata.language.isNullOrBlank()) {
|
if (metadata.language.isNullOrBlank()) {
|
||||||
itemView.lang_icon?.isVisible = false
|
binding.langIcon.isVisible = false
|
||||||
} else {
|
} else {
|
||||||
itemView.lang_icon?.isVisible = true
|
binding.langIcon.isVisible = true
|
||||||
itemView.lang_icon.text = "${getFlagFromIso(metadata.language)} ${fromTwoLettersToLanguage(metadata.language)}"
|
binding.langIcon.text =
|
||||||
|
"${getFlagFromIso(metadata.language)} ${fromTwoLettersToLanguage(metadata.language)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
itemView.ext_votes?.isVisible = false
|
binding.extVotes.isVisible = false
|
||||||
if (!isLocal) {
|
if (!isLocal) {
|
||||||
ioSafe {
|
ioSafe {
|
||||||
metadata.getVotes().main {
|
metadata.getVotes().main {
|
||||||
itemView.ext_votes?.setText(txt(R.string.extension_rating, prettyCount(it)))
|
binding.extVotes.setText(txt(R.string.extension_rating, prettyCount(it)))
|
||||||
itemView.ext_votes?.isVisible = true
|
binding.extVotes.isVisible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (metadata.fileSize != null) {
|
if (metadata.fileSize != null) {
|
||||||
itemView.ext_filesize?.isVisible = true
|
binding.extFilesize.isVisible = true
|
||||||
itemView.ext_filesize?.text = formatShortFileSize(itemView.context, metadata.fileSize)
|
binding.extFilesize.text = formatShortFileSize(itemView.context, metadata.fileSize)
|
||||||
} else {
|
} else {
|
||||||
itemView.ext_filesize?.isVisible = false
|
binding.extFilesize.isVisible = false
|
||||||
}
|
}
|
||||||
itemView.main_text.setText(if(disabled) txt(R.string.single_plugin_disabled, name) else txt(name))
|
binding.mainText.setText(
|
||||||
itemView.sub_text?.isGone = metadata.description.isNullOrBlank()
|
if (disabled) txt(
|
||||||
itemView.sub_text?.text = metadata.description.html()
|
R.string.single_plugin_disabled,
|
||||||
|
name
|
||||||
|
) else txt(name)
|
||||||
|
)
|
||||||
|
binding.subText.isGone = metadata.description.isNullOrBlank()
|
||||||
|
binding.subText.text = metadata.description.html()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,30 +2,29 @@ package com.lagradost.cloudstream3.ui.settings.extensions
|
||||||
|
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import android.text.format.Formatter.formatFileSize
|
||||||
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import com.lagradost.cloudstream3.R
|
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
|
||||||
import kotlinx.android.synthetic.main.fragment_plugin_details.*
|
|
||||||
import android.text.format.Formatter.formatFileSize
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
||||||
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentPluginDetailsBinding
|
||||||
|
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||||
import com.lagradost.cloudstream3.plugins.VotingApi
|
import com.lagradost.cloudstream3.plugins.VotingApi
|
||||||
|
import com.lagradost.cloudstream3.plugins.VotingApi.canVote
|
||||||
import com.lagradost.cloudstream3.plugins.VotingApi.getVoteType
|
import com.lagradost.cloudstream3.plugins.VotingApi.getVoteType
|
||||||
import com.lagradost.cloudstream3.plugins.VotingApi.getVotes
|
import com.lagradost.cloudstream3.plugins.VotingApi.getVotes
|
||||||
import com.lagradost.cloudstream3.plugins.VotingApi.vote
|
import com.lagradost.cloudstream3.plugins.VotingApi.vote
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
|
||||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
|
||||||
import com.lagradost.cloudstream3.plugins.VotingApi.canVote
|
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
|
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
|
||||||
import kotlinx.android.synthetic.main.repository_item.view.*
|
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
|
|
||||||
|
|
||||||
class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragment() {
|
class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragment() {
|
||||||
|
@ -43,18 +42,27 @@ class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragmen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
var binding: FragmentPluginDetailsBinding? = null
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
return inflater.inflate(R.layout.fragment_plugin_details, container, false)
|
val localBinding = FragmentPluginDetailsBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root
|
||||||
|
//return inflater.inflate(R.layout.fragment_plugin_details, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
val metadata = data.plugin.second
|
val metadata = data.plugin.second
|
||||||
if (plugin_icon?.setImage(//plugin_icon?.height ?:
|
binding?.apply {
|
||||||
|
if (!pluginIcon.setImage(//plugin_icon?.height ?:
|
||||||
metadata.iconUrl?.replace(
|
metadata.iconUrl?.replace(
|
||||||
"%size%",
|
"%size%",
|
||||||
"$iconSize"
|
"$iconSize"
|
||||||
|
@ -64,23 +72,33 @@ class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragmen
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
errorImageDrawable = R.drawable.ic_baseline_extension_24
|
errorImageDrawable = R.drawable.ic_baseline_extension_24
|
||||||
) != true
|
)
|
||||||
) {
|
) {
|
||||||
plugin_icon?.setImageResource(R.drawable.ic_baseline_extension_24)
|
pluginIcon.setImageResource(R.drawable.ic_baseline_extension_24)
|
||||||
}
|
}
|
||||||
plugin_name?.text = metadata.name.removeSuffix("Provider")
|
pluginName.text = metadata.name.removeSuffix("Provider")
|
||||||
plugin_version?.text = metadata.version.toString()
|
pluginVersion.text = metadata.version.toString()
|
||||||
plugin_description?.text = metadata.description ?: getString(R.string.no_data)
|
pluginDescription.text = metadata.description ?: getString(R.string.no_data)
|
||||||
plugin_size?.text = if (metadata.fileSize == null) getString(R.string.no_data) else formatFileSize(context, metadata.fileSize)
|
pluginSize.text =
|
||||||
plugin_author?.text = if (metadata.authors.isEmpty()) getString(R.string.no_data) else metadata.authors.joinToString(", ")
|
if (metadata.fileSize == null) getString(R.string.no_data) else formatFileSize(
|
||||||
plugin_status?.text = resources.getStringArray(R.array.extension_statuses)[metadata.status]
|
context,
|
||||||
plugin_types?.text = if ((metadata.tvTypes == null) || metadata.tvTypes.isEmpty()) getString(R.string.no_data) else metadata.tvTypes.joinToString(", ")
|
metadata.fileSize
|
||||||
plugin_lang?.text = if (metadata.language == null)
|
)
|
||||||
getString(R.string.no_data)
|
pluginAuthor.text =
|
||||||
|
if (metadata.authors.isEmpty()) getString(R.string.no_data) else metadata.authors.joinToString(
|
||||||
|
", "
|
||||||
|
)
|
||||||
|
pluginStatus.text = resources.getStringArray(R.array.extension_statuses)[metadata.status]
|
||||||
|
pluginTypes.text =
|
||||||
|
if (metadata.tvTypes.isNullOrEmpty()) getString(R.string.no_data) else metadata.tvTypes.joinToString(
|
||||||
|
", "
|
||||||
|
)
|
||||||
|
pluginLang.text = if (metadata.language == null)
|
||||||
|
getString(R.string.no_data)
|
||||||
else
|
else
|
||||||
"${getFlagFromIso(metadata.language)} ${fromTwoLettersToLanguage(metadata.language)}"
|
"${getFlagFromIso(metadata.language)} ${fromTwoLettersToLanguage(metadata.language)}"
|
||||||
|
|
||||||
github_btn.setOnClickListener {
|
githubBtn.setOnClickListener {
|
||||||
if (metadata.repositoryUrl != null) {
|
if (metadata.repositoryUrl != null) {
|
||||||
openBrowser(metadata.repositoryUrl)
|
openBrowser(metadata.repositoryUrl)
|
||||||
}
|
}
|
||||||
|
@ -93,10 +111,11 @@ class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragmen
|
||||||
|
|
||||||
if (data.isDownloaded) {
|
if (data.isDownloaded) {
|
||||||
// On local plugins page the filepath is provided instead of url.
|
// On local plugins page the filepath is provided instead of url.
|
||||||
val plugin = PluginManager.urlPlugins[metadata.url] ?: PluginManager.plugins[metadata.url]
|
val plugin =
|
||||||
|
PluginManager.urlPlugins[metadata.url] ?: PluginManager.plugins[metadata.url]
|
||||||
if (plugin?.openSettings != null && context != null) {
|
if (plugin?.openSettings != null && context != null) {
|
||||||
action_settings?.isVisible = true
|
actionSettings.isVisible = true
|
||||||
action_settings.setOnClickListener {
|
actionSettings.setOnClickListener {
|
||||||
try {
|
try {
|
||||||
plugin.openSettings!!.invoke(requireContext())
|
plugin.openSettings!!.invoke(requireContext())
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
@ -109,10 +128,10 @@ class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragmen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
action_settings?.isVisible = false
|
actionSettings.isVisible = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
action_settings?.isVisible = false
|
actionSettings.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
upvote.setOnClickListener {
|
upvote.setOnClickListener {
|
||||||
|
@ -136,23 +155,40 @@ class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragmen
|
||||||
updateVoting(it)
|
updateVoting(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateVoting(value: Int) {
|
private fun updateVoting(value: Int) {
|
||||||
val metadata = data.plugin.second
|
val metadata = data.plugin.second
|
||||||
plugin_votes.text = value.toString()
|
binding?.apply {
|
||||||
when (metadata.getVoteType()) {
|
pluginVotes.text = value.toString()
|
||||||
VotingApi.VoteType.UPVOTE -> {
|
when (metadata.getVoteType()) {
|
||||||
upvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.colorPrimary) ?: R.color.colorPrimary)
|
VotingApi.VoteType.UPVOTE -> {
|
||||||
downvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.white) ?: R.color.white)
|
upvote.imageTintList = ColorStateList.valueOf(
|
||||||
}
|
context?.colorFromAttribute(R.attr.colorPrimary) ?: R.color.colorPrimary
|
||||||
VotingApi.VoteType.DOWNVOTE -> {
|
)
|
||||||
downvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.colorPrimary) ?: R.color.colorPrimary)
|
downvote.imageTintList = ColorStateList.valueOf(
|
||||||
upvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.white) ?: R.color.white)
|
context?.colorFromAttribute(R.attr.white) ?: R.color.white
|
||||||
}
|
)
|
||||||
VotingApi.VoteType.NONE -> {
|
}
|
||||||
upvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.white) ?: R.color.white)
|
|
||||||
downvote.imageTintList = ColorStateList.valueOf(context?.colorFromAttribute(R.attr.white) ?: R.color.white)
|
VotingApi.VoteType.DOWNVOTE -> {
|
||||||
|
downvote.imageTintList = ColorStateList.valueOf(
|
||||||
|
context?.colorFromAttribute(R.attr.colorPrimary) ?: R.color.colorPrimary
|
||||||
|
)
|
||||||
|
upvote.imageTintList = ColorStateList.valueOf(
|
||||||
|
context?.colorFromAttribute(R.attr.white) ?: R.color.white
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
VotingApi.VoteType.NONE -> {
|
||||||
|
upvote.imageTintList = ColorStateList.valueOf(
|
||||||
|
context?.colorFromAttribute(R.attr.white) ?: R.color.white
|
||||||
|
)
|
||||||
|
downvote.imageTintList = ColorStateList.valueOf(
|
||||||
|
context?.colorFromAttribute(R.attr.white) ?: R.color.white
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
|
||||||
import com.lagradost.cloudstream3.AllLanguagesName
|
import com.lagradost.cloudstream3.AllLanguagesName
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentPluginsBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips
|
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.isTvSettings
|
||||||
|
@ -20,9 +21,6 @@ import com.lagradost.cloudstream3.ui.settings.appLanguages
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
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_NAME = "name"
|
||||||
const val PLUGINS_BUNDLE_URL = "url"
|
const val PLUGINS_BUNDLE_URL = "url"
|
||||||
|
@ -33,11 +31,19 @@ class PluginsFragment : Fragment() {
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?,
|
savedInstanceState: Bundle?,
|
||||||
): View? {
|
): View {
|
||||||
return inflater.inflate(R.layout.fragment_plugins, container, false)
|
val localBinding = FragmentPluginsBinding.inflate(inflater,container,false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root//inflater.inflate(R.layout.fragment_plugins, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val pluginViewModel: PluginsViewModel by activityViewModels()
|
private val pluginViewModel: PluginsViewModel by activityViewModels()
|
||||||
|
var binding: FragmentPluginsBinding? = null
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
@ -66,8 +72,8 @@ class PluginsFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
setUpToolbar(name)
|
setUpToolbar(name)
|
||||||
|
binding?.settingsToolbar?.apply {
|
||||||
settings_toolbar?.setOnMenuItemClickListener { menuItem ->
|
setOnMenuItemClickListener { menuItem ->
|
||||||
when (menuItem?.itemId) {
|
when (menuItem?.itemId) {
|
||||||
R.id.download_all -> {
|
R.id.download_all -> {
|
||||||
PluginsViewModel.downloadAll(activity, url, pluginViewModel)
|
PluginsViewModel.downloadAll(activity, url, pluginViewModel)
|
||||||
|
@ -99,67 +105,69 @@ class PluginsFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val searchView =
|
val searchView =
|
||||||
settings_toolbar?.menu?.findItem(R.id.search_button)?.actionView as? SearchView
|
menu?.findItem(R.id.search_button)?.actionView as? SearchView
|
||||||
|
|
||||||
// Don't go back if active query
|
// Don't go back if active query
|
||||||
settings_toolbar?.setNavigationOnClickListener {
|
setNavigationOnClickListener {
|
||||||
if (searchView?.isIconified == false) {
|
if (searchView?.isIconified == false) {
|
||||||
searchView.isIconified = true
|
searchView.isIconified = true
|
||||||
} else {
|
} else {
|
||||||
activity?.onBackPressed()
|
activity?.onBackPressed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
searchView?.setOnQueryTextFocusChangeListener { _, hasFocus ->
|
||||||
|
if (!hasFocus) pluginViewModel.search(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||||
|
pluginViewModel.search(query)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextChange(newText: String?): Boolean {
|
||||||
|
pluginViewModel.search(newText)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
// searchView?.onActionViewCollapsed = {
|
// searchView?.onActionViewCollapsed = {
|
||||||
// pluginViewModel.search(null)
|
// pluginViewModel.search(null)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Because onActionViewCollapsed doesn't wanna work we need this workaround :(
|
// Because onActionViewCollapsed doesn't wanna work we need this workaround :(
|
||||||
searchView?.setOnQueryTextFocusChangeListener { _, hasFocus ->
|
|
||||||
if (!hasFocus) pluginViewModel.search(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
|
||||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
|
||||||
pluginViewModel.search(query)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onQueryTextChange(newText: String?): Boolean {
|
|
||||||
pluginViewModel.search(newText)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
plugin_recycler_view?.adapter =
|
|
||||||
|
binding?.pluginRecyclerView?.adapter =
|
||||||
PluginAdapter {
|
PluginAdapter {
|
||||||
pluginViewModel.handlePluginAction(activity, url, it, isLocal)
|
pluginViewModel.handlePluginAction(activity, url, it, isLocal)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTvSettings()) {
|
if (isTvSettings()) {
|
||||||
// Scrolling down does not reveal the whole RecyclerView on TV, add to bypass that.
|
// Scrolling down does not reveal the whole RecyclerView on TV, add to bypass that.
|
||||||
plugin_recycler_view?.setPadding(0, 0, 0, 200.toPx)
|
binding?.pluginRecyclerView?.setPadding(0, 0, 0, 200.toPx)
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(pluginViewModel.filteredPlugins) { (scrollToTop, list) ->
|
observe(pluginViewModel.filteredPlugins) { (scrollToTop, list) ->
|
||||||
(plugin_recycler_view?.adapter as? PluginAdapter)?.updateList(list)
|
(binding?.pluginRecyclerView?.adapter as? PluginAdapter)?.updateList(list)
|
||||||
|
|
||||||
if (scrollToTop)
|
if (scrollToTop)
|
||||||
plugin_recycler_view?.scrollToPosition(0)
|
binding?.pluginRecyclerView?.scrollToPosition(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
// No download button and no categories on local
|
// No download button and no categories on local
|
||||||
settings_toolbar?.menu?.findItem(R.id.download_all)?.isVisible = false
|
binding?.settingsToolbar?.menu?.findItem(R.id.download_all)?.isVisible = false
|
||||||
settings_toolbar?.menu?.findItem(R.id.lang_filter)?.isVisible = false
|
binding?.settingsToolbar?.menu?.findItem(R.id.lang_filter)?.isVisible = false
|
||||||
pluginViewModel.updatePluginListLocal()
|
pluginViewModel.updatePluginListLocal()
|
||||||
tv_types_scroll_view?.isVisible = false
|
|
||||||
|
binding?.tvtypesChipsScroll?.root?.isVisible = false
|
||||||
} else {
|
} else {
|
||||||
pluginViewModel.updatePluginList(context, url)
|
pluginViewModel.updatePluginList(context, url)
|
||||||
tv_types_scroll_view?.isVisible = true
|
binding?.tvtypesChipsScroll?.root?.isVisible = true
|
||||||
|
|
||||||
bindChips(home_select_group, emptyList(), TvType.values().toList()) { list ->
|
bindChips(binding?.tvtypesChipsScroll?.tvtypesChips, emptyList(), TvType.values().toList()) { list ->
|
||||||
pluginViewModel.tvTypes.clear()
|
pluginViewModel.tvTypes.clear()
|
||||||
pluginViewModel.tvTypes.addAll(list.map { it.name })
|
pluginViewModel.tvTypes.addAll(list.map { it.name })
|
||||||
pluginViewModel.updateFilteredPlugins()
|
pluginViewModel.updateFilteredPlugins()
|
||||||
|
|
|
@ -86,7 +86,6 @@ class PluginsViewModel : ViewModel() {
|
||||||
}.also { list ->
|
}.also { list ->
|
||||||
main {
|
main {
|
||||||
showToast(
|
showToast(
|
||||||
activity,
|
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
txt(
|
txt(
|
||||||
R.string.batch_download_nothing_to_download_format,
|
R.string.batch_download_nothing_to_download_format,
|
||||||
|
@ -113,7 +112,6 @@ class PluginsViewModel : ViewModel() {
|
||||||
}.main { list ->
|
}.main { list ->
|
||||||
if (list.any { it }) {
|
if (list.any { it }) {
|
||||||
showToast(
|
showToast(
|
||||||
activity,
|
|
||||||
txt(
|
txt(
|
||||||
R.string.batch_download_finish_format,
|
R.string.batch_download_finish_format,
|
||||||
list.count { it },
|
list.count { it },
|
||||||
|
@ -123,7 +121,7 @@ class PluginsViewModel : ViewModel() {
|
||||||
)
|
)
|
||||||
viewModel?.updatePluginListPrivate(activity, repositoryUrl)
|
viewModel?.updatePluginListPrivate(activity, repositoryUrl)
|
||||||
} else if (list.isNotEmpty()) {
|
} else if (list.isNotEmpty()) {
|
||||||
showToast(activity, R.string.download_failed, Toast.LENGTH_SHORT)
|
showToast(R.string.download_failed, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,9 +164,9 @@ class PluginsViewModel : ViewModel() {
|
||||||
|
|
||||||
runOnMainThread {
|
runOnMainThread {
|
||||||
if (success)
|
if (success)
|
||||||
showToast(activity, message, Toast.LENGTH_SHORT)
|
showToast(message, Toast.LENGTH_SHORT)
|
||||||
else
|
else
|
||||||
showToast(activity, R.string.error, Toast.LENGTH_SHORT)
|
showToast(R.string.error, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
package com.lagradost.cloudstream3.ui.settings.extensions
|
package com.lagradost.cloudstream3.ui.settings.extensions
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.RepositoryItemBinding
|
||||||
|
import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding
|
||||||
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
|
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
import kotlinx.android.synthetic.main.repository_item.view.*
|
|
||||||
|
|
||||||
class RepoAdapter(
|
class RepoAdapter(
|
||||||
val isSetup: Boolean,
|
val isSetup: Boolean,
|
||||||
|
@ -20,9 +21,17 @@ class RepoAdapter(
|
||||||
private val repositories: MutableList<RepositoryData> = mutableListOf()
|
private val repositories: MutableList<RepositoryData> = mutableListOf()
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
val layout = if(isTrueTvSettings()) R.layout.repository_item_tv else R.layout.repository_item
|
val layout = if (isTrueTvSettings()) RepositoryItemTvBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
) else RepositoryItemBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
) //R.layout.repository_item_tv else R.layout.repository_item
|
||||||
return RepoViewHolder(
|
return RepoViewHolder(
|
||||||
LayoutInflater.from(parent.context).inflate(layout, parent, false)
|
layout
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,30 +66,57 @@ class RepoAdapter(
|
||||||
diffResult.dispatchUpdatesTo(this)
|
diffResult.dispatchUpdatesTo(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class RepoViewHolder(itemView: View) :
|
inner class RepoViewHolder(
|
||||||
RecyclerView.ViewHolder(itemView) {
|
val binding: ViewBinding
|
||||||
|
) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
fun bind(
|
fun bind(
|
||||||
repositoryData: RepositoryData
|
repositoryData: RepositoryData
|
||||||
) {
|
) {
|
||||||
val isPrebuilt = PREBUILT_REPOSITORIES.contains(repositoryData)
|
val isPrebuilt = PREBUILT_REPOSITORIES.contains(repositoryData)
|
||||||
val drawable =
|
val drawable =
|
||||||
if (isSetup) R.drawable.netflix_download else R.drawable.ic_baseline_delete_outline_24
|
if (isSetup) R.drawable.netflix_download else R.drawable.ic_baseline_delete_outline_24
|
||||||
|
when (binding) {
|
||||||
|
is RepositoryItemTvBinding -> {
|
||||||
|
binding.apply {
|
||||||
|
// Only shows icon if on setup or if it isn't a prebuilt repo.
|
||||||
|
// No delete buttons on prebuilt repos.
|
||||||
|
if (!isPrebuilt || isSetup) {
|
||||||
|
actionButton.setImageResource(drawable)
|
||||||
|
}
|
||||||
|
|
||||||
// Only shows icon if on setup or if it isn't a prebuilt repo.
|
actionButton.setOnClickListener {
|
||||||
// No delete buttons on prebuilt repos.
|
imageClickCallback(repositoryData)
|
||||||
if (!isPrebuilt || isSetup) {
|
}
|
||||||
itemView.action_button?.setImageResource(drawable)
|
|
||||||
}
|
|
||||||
|
|
||||||
itemView.action_button?.setOnClickListener {
|
repositoryItemRoot.setOnClickListener {
|
||||||
imageClickCallback(repositoryData)
|
clickCallback(repositoryData)
|
||||||
}
|
}
|
||||||
|
mainText.text = repositoryData.name
|
||||||
|
subText.text = repositoryData.url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
itemView.repository_item_root?.setOnClickListener {
|
is RepositoryItemBinding -> {
|
||||||
clickCallback(repositoryData)
|
binding.apply {
|
||||||
|
// Only shows icon if on setup or if it isn't a prebuilt repo.
|
||||||
|
// No delete buttons on prebuilt repos.
|
||||||
|
if (!isPrebuilt || isSetup) {
|
||||||
|
actionButton.setImageResource(drawable)
|
||||||
|
}
|
||||||
|
|
||||||
|
actionButton.setOnClickListener {
|
||||||
|
imageClickCallback(repositoryData)
|
||||||
|
}
|
||||||
|
|
||||||
|
repositoryItemRoot.setOnClickListener {
|
||||||
|
clickCallback(repositoryData)
|
||||||
|
}
|
||||||
|
mainText.text = repositoryData.name
|
||||||
|
subText.text = repositoryData.url
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
itemView.main_text?.text = repositoryData.name
|
|
||||||
itemView.sub_text?.text = repositoryData.url
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,97 +1,105 @@
|
||||||
package com.lagradost.cloudstream3.ui.settings.testing
|
package com.lagradost.cloudstream3.ui.settings.testing
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentTestingBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.mvvm.observe
|
import com.lagradost.cloudstream3.mvvm.observe
|
||||||
import com.lagradost.cloudstream3.mvvm.observeNullable
|
import com.lagradost.cloudstream3.mvvm.observeNullable
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||||
import kotlinx.android.synthetic.main.fragment_testing.*
|
|
||||||
import kotlinx.android.synthetic.main.view_test.*
|
|
||||||
|
|
||||||
|
|
||||||
class TestFragment : Fragment() {
|
class TestFragment : Fragment() {
|
||||||
|
|
||||||
private val testViewModel: TestViewModel by activityViewModels()
|
private val testViewModel: TestViewModel by activityViewModels()
|
||||||
|
var binding: FragmentTestingBinding? = null
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
setUpToolbar(R.string.category_provider_test)
|
setUpToolbar(R.string.category_provider_test)
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
provider_test_recycler_view?.adapter = TestResultAdapter(
|
binding?.apply {
|
||||||
mutableListOf()
|
providerTestRecyclerView.adapter = TestResultAdapter(
|
||||||
)
|
mutableListOf()
|
||||||
|
)
|
||||||
|
|
||||||
testViewModel.init()
|
testViewModel.init()
|
||||||
if (testViewModel.isRunningTest) {
|
if (testViewModel.isRunningTest) {
|
||||||
provider_test?.setState(TestView.TestState.Running)
|
providerTest.setState(TestView.TestState.Running)
|
||||||
}
|
|
||||||
|
|
||||||
observe(testViewModel.providerProgress) { (passed, failed, total) ->
|
|
||||||
provider_test?.setProgress(passed, failed, total)
|
|
||||||
}
|
|
||||||
|
|
||||||
observeNullable(testViewModel.providerResults) {
|
|
||||||
normalSafeApiCall {
|
|
||||||
val newItems = it.sortedBy { api -> api.first.name }
|
|
||||||
(provider_test_recycler_view?.adapter as? TestResultAdapter)?.updateList(
|
|
||||||
newItems
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
provider_test?.setOnPlayButtonListener { state ->
|
observe(testViewModel.providerProgress) { (passed, failed, total) ->
|
||||||
when (state) {
|
providerTest.setProgress(passed, failed, total)
|
||||||
TestView.TestState.Stopped -> testViewModel.stopTest()
|
|
||||||
TestView.TestState.Running -> testViewModel.startTest()
|
|
||||||
TestView.TestState.None -> testViewModel.startTest()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (isTrueTvSettings()) {
|
observeNullable(testViewModel.providerResults) {
|
||||||
tests_play_pause?.isFocusableInTouchMode = true
|
normalSafeApiCall {
|
||||||
tests_play_pause?.requestFocus()
|
val newItems = it.sortedBy { api -> api.first.name }
|
||||||
}
|
(providerTestRecyclerView.adapter as? TestResultAdapter)?.updateList(
|
||||||
|
newItems
|
||||||
provider_test?.playPauseButton?.setOnFocusChangeListener { _, hasFocus ->
|
)
|
||||||
if (hasFocus) {
|
}
|
||||||
provider_test_appbar?.setExpanded(true, true)
|
}
|
||||||
|
|
||||||
|
providerTest.setOnPlayButtonListener { state ->
|
||||||
|
when (state) {
|
||||||
|
TestView.TestState.Stopped -> testViewModel.stopTest()
|
||||||
|
TestView.TestState.Running -> testViewModel.startTest()
|
||||||
|
TestView.TestState.None -> testViewModel.startTest()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun focusRecyclerView() {
|
|
||||||
// Hack to make it possible to focus the recyclerview.
|
|
||||||
if (isTrueTvSettings()) {
|
if (isTrueTvSettings()) {
|
||||||
provider_test_recycler_view?.requestFocus()
|
providerTest.playPauseButton?.isFocusableInTouchMode = true
|
||||||
provider_test_appbar?.setExpanded(false, true)
|
providerTest.playPauseButton?.requestFocus()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
provider_test?.setOnMainClick {
|
providerTest.playPauseButton?.setOnFocusChangeListener { _, hasFocus ->
|
||||||
testViewModel.setFilterMethod(TestViewModel.ProviderFilter.All)
|
if (hasFocus) {
|
||||||
focusRecyclerView()
|
providerTestAppbar.setExpanded(true, true)
|
||||||
}
|
}
|
||||||
provider_test?.setOnFailedClick {
|
}
|
||||||
testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Failed)
|
|
||||||
focusRecyclerView()
|
fun focusRecyclerView() {
|
||||||
}
|
// Hack to make it possible to focus the recyclerview.
|
||||||
provider_test?.setOnPassedClick {
|
if (isTrueTvSettings()) {
|
||||||
testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Passed)
|
providerTestRecyclerView.requestFocus()
|
||||||
focusRecyclerView()
|
providerTestAppbar.setExpanded(false, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
providerTest.setOnMainClick {
|
||||||
|
testViewModel.setFilterMethod(TestViewModel.ProviderFilter.All)
|
||||||
|
focusRecyclerView()
|
||||||
|
}
|
||||||
|
providerTest.setOnFailedClick {
|
||||||
|
testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Failed)
|
||||||
|
focusRecyclerView()
|
||||||
|
}
|
||||||
|
providerTest.setOnPassedClick {
|
||||||
|
testViewModel.setFilterMethod(TestViewModel.ProviderFilter.Passed)
|
||||||
|
focusRecyclerView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
return inflater.inflate(R.layout.fragment_testing, container, false)
|
val localBinding = FragmentTestingBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root//inflater.inflate(R.layout.fragment_testing, container, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,19 +10,20 @@ import androidx.core.content.ContextCompat
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.lagradost.cloudstream3.MainAPI
|
import com.lagradost.cloudstream3.MainAPI
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.ProviderTestItemBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.getAllMessages
|
import com.lagradost.cloudstream3.mvvm.getAllMessages
|
||||||
import com.lagradost.cloudstream3.mvvm.getStackTracePretty
|
import com.lagradost.cloudstream3.mvvm.getStackTracePretty
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils
|
import com.lagradost.cloudstream3.utils.AppUtils
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
|
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
|
||||||
import com.lagradost.cloudstream3.utils.TestingUtils
|
import com.lagradost.cloudstream3.utils.TestingUtils
|
||||||
import kotlinx.android.synthetic.main.provider_test_item.view.*
|
|
||||||
|
|
||||||
class TestResultAdapter(override val items: MutableList<Pair<MainAPI, TestingUtils.TestResultProvider>>) :
|
class TestResultAdapter(override val items: MutableList<Pair<MainAPI, TestingUtils.TestResultProvider>>) :
|
||||||
AppUtils.DiffAdapter<Pair<MainAPI, TestingUtils.TestResultProvider>>(items) {
|
AppUtils.DiffAdapter<Pair<MainAPI, TestingUtils.TestResultProvider>>(items) {
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||||
return ProviderTestViewHolder(
|
return ProviderTestViewHolder(
|
||||||
LayoutInflater.from(parent.context)
|
ProviderTestItemBinding.inflate(LayoutInflater.from(parent.context), parent,false)
|
||||||
.inflate(R.layout.provider_test_item, parent, false),
|
//LayoutInflater.from(parent.context)
|
||||||
|
// .inflate(R.layout.provider_test_item, parent, false),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,12 +36,12 @@ class TestResultAdapter(override val items: MutableList<Pair<MainAPI, TestingUti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class ProviderTestViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
inner class ProviderTestViewHolder(binding: ProviderTestItemBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||||
private val languageText: TextView = itemView.lang_icon
|
private val languageText: TextView = binding.langIcon
|
||||||
private val providerTitle: TextView = itemView.main_text
|
private val providerTitle: TextView = binding.mainText
|
||||||
private val statusText: TextView = itemView.passed_failed_marker
|
private val statusText: TextView = binding.passedFailedMarker
|
||||||
private val failDescription: TextView = itemView.fail_description
|
private val failDescription: TextView = binding.failDescription
|
||||||
private val logButton: ImageView = itemView.action_button
|
private val logButton: ImageView = binding.actionButton
|
||||||
|
|
||||||
private fun String.lastLine(): String? {
|
private fun String.lastLine(): String? {
|
||||||
return this.lines().lastOrNull { it.isNotBlank() }
|
return this.lines().lastOrNull { it.isNotBlank() }
|
||||||
|
|
|
@ -10,6 +10,7 @@ import com.lagradost.cloudstream3.utils.TestingUtils
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
import okhttp3.internal.toImmutableList
|
||||||
|
|
||||||
class TestViewModel : ViewModel() {
|
class TestViewModel : ViewModel() {
|
||||||
data class TestProgress(
|
data class TestProgress(
|
||||||
|
@ -81,15 +82,14 @@ class TestViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
val apis = APIHolder.allProviders
|
total = synchronized(APIHolder.allProviders) { APIHolder.allProviders.size }
|
||||||
total = apis.size
|
|
||||||
updateProgress()
|
updateProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startTest() {
|
fun startTest() {
|
||||||
scope = CoroutineScope(Dispatchers.Default)
|
scope = CoroutineScope(Dispatchers.Default)
|
||||||
|
|
||||||
val apis = APIHolder.allProviders
|
val apis = synchronized(APIHolder.allProviders) { APIHolder.allProviders.toTypedArray() }
|
||||||
total = apis.size
|
total = apis.size
|
||||||
failed = 0
|
failed = 0
|
||||||
passed = 0
|
passed = 0
|
||||||
|
|
|
@ -8,21 +8,16 @@ import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import com.lagradost.cloudstream3.APIHolder.apis
|
import com.lagradost.cloudstream3.APIHolder.apis
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
|
|
||||||
import com.lagradost.cloudstream3.AllLanguagesName
|
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent
|
import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentSetupExtensionsBinding
|
||||||
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
||||||
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
|
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
|
||||||
import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel
|
import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel
|
||||||
import com.lagradost.cloudstream3.ui.settings.extensions.RepoAdapter
|
import com.lagradost.cloudstream3.ui.settings.extensions.RepoAdapter
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import kotlinx.android.synthetic.main.fragment_extensions.blank_repo_screen
|
|
||||||
import kotlinx.android.synthetic.main.fragment_extensions.repo_recycler_view
|
|
||||||
import kotlinx.android.synthetic.main.fragment_setup_media.next_btt
|
|
||||||
import kotlinx.android.synthetic.main.fragment_setup_media.prev_btt
|
|
||||||
import kotlinx.android.synthetic.main.fragment_setup_media.setup_root
|
|
||||||
|
|
||||||
|
|
||||||
class SetupFragmentExtensions : Fragment() {
|
class SetupFragmentExtensions : Fragment() {
|
||||||
|
@ -39,13 +34,24 @@ class SetupFragmentExtensions : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var binding: FragmentSetupExtensionsBinding? = null
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
return inflater.inflate(R.layout.fragment_setup_extensions, container, false)
|
val localBinding = FragmentSetupExtensionsBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root
|
||||||
|
//return inflater.inflate(R.layout.fragment_setup_extensions, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
afterRepositoryLoadedEvent += ::setRepositories
|
afterRepositoryLoadedEvent += ::setRepositories
|
||||||
|
@ -60,12 +66,12 @@ class SetupFragmentExtensions : Fragment() {
|
||||||
main {
|
main {
|
||||||
val repositories = RepositoryManager.getRepositories() + PREBUILT_REPOSITORIES
|
val repositories = RepositoryManager.getRepositories() + PREBUILT_REPOSITORIES
|
||||||
val hasRepos = repositories.isNotEmpty()
|
val hasRepos = repositories.isNotEmpty()
|
||||||
repo_recycler_view?.isVisible = hasRepos
|
binding?.repoRecyclerView?.isVisible = hasRepos
|
||||||
blank_repo_screen?.isVisible = !hasRepos
|
binding?.blankRepoScreen?.isVisible = !hasRepos
|
||||||
// view_public_repositories_button?.isVisible = hasRepos
|
// view_public_repositories_button?.isVisible = hasRepos
|
||||||
|
|
||||||
if (hasRepos) {
|
if (hasRepos) {
|
||||||
repo_recycler_view?.adapter = RepoAdapter(true, {}, {
|
binding?.repoRecyclerView?.adapter = RepoAdapter(true, {}, {
|
||||||
PluginsViewModel.downloadAll(activity, it.url, null)
|
PluginsViewModel.downloadAll(activity, it.url, null)
|
||||||
}).apply { updateList(repositories) }
|
}).apply { updateList(repositories) }
|
||||||
}
|
}
|
||||||
|
@ -80,39 +86,40 @@ class SetupFragmentExtensions : Fragment() {
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
context?.fixPaddingStatusbar(setup_root)
|
fixPaddingStatusbar(binding?.setupRoot)
|
||||||
val isSetup = arguments?.getBoolean(SETUP_EXTENSION_BUNDLE_IS_SETUP) ?: false
|
val isSetup = arguments?.getBoolean(SETUP_EXTENSION_BUNDLE_IS_SETUP) ?: false
|
||||||
|
|
||||||
// view_public_repositories_button?.setOnClickListener {
|
// view_public_repositories_button?.setOnClickListener {
|
||||||
// openBrowser(PUBLIC_REPOSITORIES_LIST, isTvSettings(), this)
|
// openBrowser(PUBLIC_REPOSITORIES_LIST, isTvSettings(), this)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
with(context) {
|
normalSafeApiCall {
|
||||||
if (this == null) return
|
// val ctx = context ?: return@normalSafeApiCall
|
||||||
setRepositories()
|
setRepositories()
|
||||||
|
binding?.apply {
|
||||||
|
if (!isSetup) {
|
||||||
|
nextBtt.setText(R.string.setup_done)
|
||||||
|
}
|
||||||
|
prevBtt.isVisible = isSetup
|
||||||
|
|
||||||
if (!isSetup) {
|
nextBtt.setOnClickListener {
|
||||||
next_btt.setText(R.string.setup_done)
|
// Continue setup
|
||||||
}
|
if (isSetup)
|
||||||
prev_btt?.isVisible = isSetup
|
if (
|
||||||
|
// If any available languages
|
||||||
|
synchronized(apis) { apis.distinctBy { it.lang }.size > 1 }
|
||||||
|
) {
|
||||||
|
findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_provider_languages)
|
||||||
|
} else {
|
||||||
|
findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_media)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
findNavController().navigate(R.id.navigation_home)
|
||||||
|
}
|
||||||
|
|
||||||
next_btt?.setOnClickListener {
|
prevBtt.setOnClickListener {
|
||||||
// Continue setup
|
findNavController().navigate(R.id.navigation_setup_language)
|
||||||
if (isSetup)
|
}
|
||||||
if (
|
|
||||||
// If any available languages
|
|
||||||
apis.distinctBy { it.lang }.size > 1
|
|
||||||
) {
|
|
||||||
findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_provider_languages)
|
|
||||||
} else {
|
|
||||||
findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_media)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
findNavController().navigate(R.id.navigation_home)
|
|
||||||
}
|
|
||||||
|
|
||||||
prev_btt?.setOnClickListener {
|
|
||||||
findNavController().navigate(R.id.navigation_setup_language)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,40 +13,49 @@ import androidx.preference.PreferenceManager
|
||||||
import com.lagradost.cloudstream3.BuildConfig
|
import com.lagradost.cloudstream3.BuildConfig
|
||||||
import com.lagradost.cloudstream3.CommonActivity
|
import com.lagradost.cloudstream3.CommonActivity
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentSetupLanguageBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||||
import com.lagradost.cloudstream3.ui.settings.appLanguages
|
import com.lagradost.cloudstream3.ui.settings.appLanguages
|
||||||
import com.lagradost.cloudstream3.ui.settings.getCurrentLocale
|
import com.lagradost.cloudstream3.ui.settings.getCurrentLocale
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import kotlinx.android.synthetic.main.fragment_setup_language.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_setup_media.listview1
|
|
||||||
import kotlinx.android.synthetic.main.fragment_setup_media.next_btt
|
|
||||||
|
|
||||||
const val HAS_DONE_SETUP_KEY = "HAS_DONE_SETUP"
|
const val HAS_DONE_SETUP_KEY = "HAS_DONE_SETUP"
|
||||||
|
|
||||||
class SetupFragmentLanguage : Fragment() {
|
class SetupFragmentLanguage : Fragment() {
|
||||||
|
var binding: FragmentSetupLanguageBinding? = null
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
// Inflate the layout for this fragment
|
val localBinding = FragmentSetupLanguageBinding.inflate(inflater, container, false)
|
||||||
return inflater.inflate(R.layout.fragment_setup_language, container, false)
|
binding = localBinding
|
||||||
|
return localBinding.root
|
||||||
|
//return inflater.inflate(R.layout.fragment_setup_language, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
context?.fixPaddingStatusbar(setup_root)
|
|
||||||
|
|
||||||
// We don't want a crash for all users
|
// We don't want a crash for all users
|
||||||
normalSafeApiCall {
|
normalSafeApiCall {
|
||||||
with(context) {
|
fixPaddingStatusbar(binding?.setupRoot)
|
||||||
if (this == null) return@normalSafeApiCall
|
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
|
||||||
|
|
||||||
val arrayAdapter =
|
val ctx = context ?: return@normalSafeApiCall
|
||||||
ArrayAdapter<String>(this, R.layout.sort_bottom_single_choice)
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
|
||||||
|
val arrayAdapter =
|
||||||
|
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
||||||
|
|
||||||
|
binding?.apply {
|
||||||
// Icons may crash on some weird android versions?
|
// Icons may crash on some weird android versions?
|
||||||
normalSafeApiCall {
|
normalSafeApiCall {
|
||||||
val drawable = when {
|
val drawable = when {
|
||||||
|
@ -54,10 +63,10 @@ class SetupFragmentLanguage : Fragment() {
|
||||||
BuildConfig.BUILD_TYPE == "prerelease" -> R.drawable.cloud_2_gradient_beta
|
BuildConfig.BUILD_TYPE == "prerelease" -> R.drawable.cloud_2_gradient_beta
|
||||||
else -> R.drawable.cloud_2_gradient
|
else -> R.drawable.cloud_2_gradient
|
||||||
}
|
}
|
||||||
app_icon_image?.setImageDrawable(ContextCompat.getDrawable(this, drawable))
|
appIconImage.setImageDrawable(ContextCompat.getDrawable(ctx, drawable))
|
||||||
}
|
}
|
||||||
|
|
||||||
val current = getCurrentLocale(this)
|
val current = getCurrentLocale(ctx)
|
||||||
val languageCodes = appLanguages.map { it.third }
|
val languageCodes = appLanguages.map { it.third }
|
||||||
val languageNames = appLanguages.map { (emoji, name, iso) ->
|
val languageNames = appLanguages.map { (emoji, name, iso) ->
|
||||||
val flag = emoji.ifBlank { SubtitleHelper.getFlagFromIso(iso) ?: "ERROR" }
|
val flag = emoji.ifBlank { SubtitleHelper.getFlagFromIso(iso) ?: "ERROR" }
|
||||||
|
@ -66,18 +75,19 @@ class SetupFragmentLanguage : Fragment() {
|
||||||
val index = languageCodes.indexOf(current)
|
val index = languageCodes.indexOf(current)
|
||||||
|
|
||||||
arrayAdapter.addAll(languageNames)
|
arrayAdapter.addAll(languageNames)
|
||||||
listview1?.adapter = arrayAdapter
|
listview1.adapter = arrayAdapter
|
||||||
listview1?.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
listview1.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
||||||
listview1?.setItemChecked(index, true)
|
listview1.setItemChecked(index, true)
|
||||||
|
|
||||||
listview1?.setOnItemClickListener { _, _, position, _ ->
|
listview1.setOnItemClickListener { _, _, position, _ ->
|
||||||
val code = languageCodes[position]
|
val code = languageCodes[position]
|
||||||
CommonActivity.setLocale(activity, code)
|
CommonActivity.setLocale(activity, code)
|
||||||
settingsManager.edit().putString(getString(R.string.locale_key), code).apply()
|
settingsManager.edit().putString(getString(R.string.locale_key), code)
|
||||||
|
.apply()
|
||||||
activity?.recreate()
|
activity?.recreate()
|
||||||
}
|
}
|
||||||
|
|
||||||
next_btt?.setOnClickListener {
|
nextBtt.setOnClickListener {
|
||||||
// If no plugins go to plugins page
|
// If no plugins go to plugins page
|
||||||
val nextDestination = if (
|
val nextDestination = if (
|
||||||
PluginManager.getPluginsOnline().isEmpty()
|
PluginManager.getPluginsOnline().isEmpty()
|
||||||
|
@ -92,10 +102,11 @@ class SetupFragmentLanguage : Fragment() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
skip_btt?.setOnClickListener {
|
skipBtt.setOnClickListener {
|
||||||
findNavController().navigate(R.id.navigation_home)
|
findNavController().navigate(R.id.navigation_home)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,30 +10,39 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentSetupLayoutBinding
|
||||||
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import kotlinx.android.synthetic.main.fragment_setup_layout.*
|
|
||||||
import kotlinx.android.synthetic.main.fragment_setup_media.listview1
|
|
||||||
import kotlinx.android.synthetic.main.fragment_setup_media.next_btt
|
|
||||||
import kotlinx.android.synthetic.main.fragment_setup_media.prev_btt
|
|
||||||
import kotlinx.android.synthetic.main.fragment_setup_media.setup_root
|
|
||||||
import org.acra.ACRA
|
import org.acra.ACRA
|
||||||
|
|
||||||
|
|
||||||
class SetupFragmentLayout : Fragment() {
|
class SetupFragmentLayout : Fragment() {
|
||||||
|
|
||||||
|
var binding: FragmentSetupLayoutBinding? = null
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
return inflater.inflate(R.layout.fragment_setup_layout, container, false)
|
val localBinding = FragmentSetupLayoutBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root
|
||||||
|
//return inflater.inflate(R.layout.fragment_setup_layout, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
context?.fixPaddingStatusbar(setup_root)
|
fixPaddingStatusbar(binding?.setupRoot)
|
||||||
|
|
||||||
with(context) {
|
normalSafeApiCall {
|
||||||
if (this == null) return
|
val ctx = context ?: return@normalSafeApiCall
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
|
||||||
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
|
||||||
val prefNames = resources.getStringArray(R.array.app_layout)
|
val prefNames = resources.getStringArray(R.array.app_layout)
|
||||||
val prefValues = resources.getIntArray(R.array.app_layout_values)
|
val prefValues = resources.getIntArray(R.array.app_layout_values)
|
||||||
|
@ -42,48 +51,48 @@ class SetupFragmentLayout : Fragment() {
|
||||||
settingsManager.getInt(getString(R.string.app_layout_key), -1)
|
settingsManager.getInt(getString(R.string.app_layout_key), -1)
|
||||||
|
|
||||||
val arrayAdapter =
|
val arrayAdapter =
|
||||||
ArrayAdapter<String>(this, R.layout.sort_bottom_single_choice)
|
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
||||||
|
|
||||||
arrayAdapter.addAll(prefNames.toList())
|
arrayAdapter.addAll(prefNames.toList())
|
||||||
listview1?.adapter = arrayAdapter
|
binding?.apply {
|
||||||
listview1?.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
listview1.adapter = arrayAdapter
|
||||||
listview1?.setItemChecked(
|
listview1.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
||||||
prefValues.indexOf(currentLayout), true
|
listview1.setItemChecked(
|
||||||
)
|
prefValues.indexOf(currentLayout), true
|
||||||
|
|
||||||
listview1?.setOnItemClickListener { _, _, position, _ ->
|
|
||||||
settingsManager.edit()
|
|
||||||
.putInt(getString(R.string.app_layout_key), prefValues[position])
|
|
||||||
.apply()
|
|
||||||
activity?.recreate()
|
|
||||||
}
|
|
||||||
|
|
||||||
acra_switch?.setOnCheckedChangeListener { _, enableCrashReporting ->
|
|
||||||
// Use same pref as in settings
|
|
||||||
settingsManager.edit().putBoolean(ACRA.PREF_DISABLE_ACRA, !enableCrashReporting)
|
|
||||||
.apply()
|
|
||||||
val text =
|
|
||||||
if (enableCrashReporting) R.string.bug_report_settings_off else R.string.bug_report_settings_on
|
|
||||||
crash_reporting_text?.text = getText(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
val enableCrashReporting = !settingsManager.getBoolean(ACRA.PREF_DISABLE_ACRA, true)
|
|
||||||
acra_switch.isChecked = enableCrashReporting
|
|
||||||
crash_reporting_text.text =
|
|
||||||
getText(
|
|
||||||
if (enableCrashReporting) R.string.bug_report_settings_off else R.string.bug_report_settings_on
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
listview1.setOnItemClickListener { _, _, position, _ ->
|
||||||
|
settingsManager.edit()
|
||||||
|
.putInt(getString(R.string.app_layout_key), prefValues[position])
|
||||||
|
.apply()
|
||||||
|
activity?.recreate()
|
||||||
|
}
|
||||||
|
acraSwitch.setOnCheckedChangeListener { _, enableCrashReporting ->
|
||||||
|
// Use same pref as in settings
|
||||||
|
settingsManager.edit().putBoolean(ACRA.PREF_DISABLE_ACRA, !enableCrashReporting)
|
||||||
|
.apply()
|
||||||
|
val text =
|
||||||
|
if (enableCrashReporting) R.string.bug_report_settings_off else R.string.bug_report_settings_on
|
||||||
|
crashReportingText.text = getText(text)
|
||||||
|
}
|
||||||
|
|
||||||
next_btt?.setOnClickListener {
|
val enableCrashReporting = !settingsManager.getBoolean(ACRA.PREF_DISABLE_ACRA, true)
|
||||||
findNavController().navigate(R.id.navigation_home)
|
|
||||||
}
|
|
||||||
|
|
||||||
prev_btt?.setOnClickListener {
|
acraSwitch.isChecked = enableCrashReporting
|
||||||
findNavController().popBackStack()
|
crashReportingText.text =
|
||||||
|
getText(
|
||||||
|
if (enableCrashReporting) R.string.bug_report_settings_off else R.string.bug_report_settings_on
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
nextBtt.setOnClickListener {
|
||||||
|
findNavController().navigate(R.id.navigation_home)
|
||||||
|
}
|
||||||
|
|
||||||
|
prevBtt.setOnClickListener {
|
||||||
|
findNavController().popBackStack()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -10,72 +10,85 @@ import androidx.core.util.forEach
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.removeKey
|
import com.lagradost.cloudstream3.databinding.FragmentSetupMediaBinding
|
||||||
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import kotlinx.android.synthetic.main.fragment_setup_media.*
|
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
|
||||||
|
|
||||||
|
|
||||||
class SetupFragmentMedia : Fragment() {
|
class SetupFragmentMedia : Fragment() {
|
||||||
|
var binding: FragmentSetupMediaBinding? = null
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
return inflater.inflate(R.layout.fragment_setup_media, container, false)
|
val localBinding = FragmentSetupMediaBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root
|
||||||
|
//return inflater.inflate(R.layout.fragment_setup_media, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
context?.fixPaddingStatusbar(setup_root)
|
normalSafeApiCall {
|
||||||
|
fixPaddingStatusbar(binding?.setupRoot)
|
||||||
|
|
||||||
with(context) {
|
val ctx = context ?: return@normalSafeApiCall
|
||||||
if (this == null) return
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
|
||||||
|
|
||||||
val arrayAdapter =
|
val arrayAdapter =
|
||||||
ArrayAdapter<String>(this, R.layout.sort_bottom_single_choice)
|
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
||||||
|
|
||||||
val names = enumValues<TvType>().sorted().map { it.name }
|
val names = enumValues<TvType>().sorted().map { it.name }
|
||||||
val selected = mutableListOf<Int>()
|
val selected = mutableListOf<Int>()
|
||||||
|
|
||||||
arrayAdapter.addAll(names)
|
arrayAdapter.addAll(names)
|
||||||
listview1?.let {
|
binding?.apply {
|
||||||
it.adapter = arrayAdapter
|
listview1.let {
|
||||||
it.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
|
it.adapter = arrayAdapter
|
||||||
|
it.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
|
||||||
|
|
||||||
it.setOnItemClickListener { _, _, _, _ ->
|
it.setOnItemClickListener { _, _, _, _ ->
|
||||||
it.checkedItemPositions?.forEach { key, value ->
|
it.checkedItemPositions?.forEach { key, value ->
|
||||||
if (value) {
|
if (value) {
|
||||||
selected.add(key)
|
selected.add(key)
|
||||||
} else {
|
} else {
|
||||||
selected.remove(key)
|
selected.remove(key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
val prefValues = selected.mapNotNull { pos ->
|
||||||
|
val item =
|
||||||
|
it.getItemAtPosition(pos)?.toString() ?: return@mapNotNull null
|
||||||
|
val itemVal = TvType.valueOf(item)
|
||||||
|
itemVal.ordinal.toString()
|
||||||
|
}.toSet()
|
||||||
|
settingsManager.edit()
|
||||||
|
.putStringSet(getString(R.string.prefer_media_type_key), prefValues)
|
||||||
|
.apply()
|
||||||
|
|
||||||
|
// Regenerate set homepage
|
||||||
|
removeKey(USER_SELECTED_HOMEPAGE_API)
|
||||||
}
|
}
|
||||||
val prefValues = selected.mapNotNull { pos ->
|
|
||||||
val item = it.getItemAtPosition(pos)?.toString() ?: return@mapNotNull null
|
|
||||||
val itemVal = TvType.valueOf(item)
|
|
||||||
itemVal.ordinal.toString()
|
|
||||||
}.toSet()
|
|
||||||
settingsManager.edit()
|
|
||||||
.putStringSet(getString(R.string.prefer_media_type_key), prefValues)
|
|
||||||
.apply()
|
|
||||||
|
|
||||||
// Regenerate set homepage
|
|
||||||
removeKey(USER_SELECTED_HOMEPAGE_API)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
next_btt?.setOnClickListener {
|
nextBtt.setOnClickListener {
|
||||||
findNavController().navigate(R.id.navigation_setup_media_to_navigation_setup_layout)
|
findNavController().navigate(R.id.navigation_setup_media_to_navigation_setup_layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
prev_btt?.setOnClickListener {
|
prevBtt.setOnClickListener {
|
||||||
findNavController().popBackStack()
|
findNavController().popBackStack()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -14,33 +14,45 @@ import com.lagradost.cloudstream3.APIHolder
|
||||||
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
|
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
|
||||||
import com.lagradost.cloudstream3.AllLanguagesName
|
import com.lagradost.cloudstream3.AllLanguagesName
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.FragmentSetupProviderLanguagesBinding
|
||||||
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import kotlinx.android.synthetic.main.fragment_setup_media.*
|
|
||||||
|
|
||||||
class SetupFragmentProviderLanguage : Fragment() {
|
class SetupFragmentProviderLanguage : Fragment() {
|
||||||
|
var binding: FragmentSetupProviderLanguagesBinding? = null
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
// Inflate the layout for this fragment
|
val localBinding = FragmentSetupProviderLanguagesBinding.inflate(inflater, container, false)
|
||||||
return inflater.inflate(R.layout.fragment_setup_provider_languages, container, false)
|
binding = localBinding
|
||||||
|
return localBinding.root
|
||||||
|
//return inflater.inflate(R.layout.fragment_setup_provider_languages, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
context?.fixPaddingStatusbar(setup_root)
|
fixPaddingStatusbar(binding?.setupRoot)
|
||||||
|
|
||||||
with(context) {
|
normalSafeApiCall {
|
||||||
if (this == null) return
|
val ctx = context ?: return@normalSafeApiCall
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
|
||||||
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
|
||||||
val arrayAdapter =
|
val arrayAdapter =
|
||||||
ArrayAdapter<String>(this, R.layout.sort_bottom_single_choice)
|
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
||||||
|
|
||||||
val current = this.getApiProviderLangSettings()
|
val current = ctx.getApiProviderLangSettings()
|
||||||
val langs = APIHolder.apis.map { it.lang }.toSet()
|
val langs = synchronized(APIHolder.apis) { APIHolder.apis.map { it.lang }.toSet()
|
||||||
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName
|
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName}
|
||||||
|
|
||||||
val currentList =
|
val currentList =
|
||||||
current.map { langs.indexOf(it) }.filter { it != -1 } // TODO LOOK INTO
|
current.map { langs.indexOf(it) }.filter { it != -1 } // TODO LOOK INTO
|
||||||
|
@ -56,31 +68,31 @@ class SetupFragmentProviderLanguage : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
arrayAdapter.addAll(languageNames)
|
arrayAdapter.addAll(languageNames)
|
||||||
|
binding?.apply {
|
||||||
listview1?.adapter = arrayAdapter
|
listview1.adapter = arrayAdapter
|
||||||
listview1?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
|
listview1.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
|
||||||
currentList.forEach {
|
currentList.forEach {
|
||||||
listview1.setItemChecked(it, true)
|
listview1.setItemChecked(it, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
listview1?.setOnItemClickListener { _, _, _, _ ->
|
listview1.setOnItemClickListener { _, _, _, _ ->
|
||||||
val currentLanguages = mutableListOf<String>()
|
val currentLanguages = mutableListOf<String>()
|
||||||
listview1?.checkedItemPositions?.forEach { key, value ->
|
listview1.checkedItemPositions?.forEach { key, value ->
|
||||||
if (value) currentLanguages.add(langs[key])
|
if (value) currentLanguages.add(langs[key])
|
||||||
}
|
}
|
||||||
settingsManager.edit().putStringSet(
|
settingsManager.edit().putStringSet(
|
||||||
this.getString(R.string.provider_lang_key),
|
ctx.getString(R.string.provider_lang_key),
|
||||||
currentLanguages.toSet()
|
currentLanguages.toSet()
|
||||||
).apply()
|
).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
next_btt?.setOnClickListener {
|
nextBtt.setOnClickListener {
|
||||||
findNavController().navigate(R.id.navigation_setup_provider_languages_to_navigation_setup_media)
|
findNavController().navigate(R.id.navigation_setup_provider_languages_to_navigation_setup_media)
|
||||||
}
|
}
|
||||||
|
|
||||||
prev_btt?.setOnClickListener {
|
prevBtt.setOnClickListener {
|
||||||
findNavController().popBackStack()
|
findNavController().popBackStack()
|
||||||
}
|
} }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent
|
||||||
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
|
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.ChromecastSubtitleSettingsBinding
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
import com.lagradost.cloudstream3.utils.Event
|
import com.lagradost.cloudstream3.utils.Event
|
||||||
|
@ -31,7 +32,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
|
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
||||||
import kotlinx.android.synthetic.main.subtitle_settings.*
|
|
||||||
|
|
||||||
const val CHROME_SUBTITLE_KEY = "chome_subtitle_settings"
|
const val CHROME_SUBTITLE_KEY = "chome_subtitle_settings"
|
||||||
|
|
||||||
|
@ -137,12 +137,21 @@ class ChromecastSubtitlesFragment : Fragment() {
|
||||||
//subtitle_text?.setStyle(fromSaveToStyle(state))
|
//subtitle_text?.setStyle(fromSaveToStyle(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var binding : ChromecastSubtitleSettingsBinding? = null
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?,
|
savedInstanceState: Bundle?,
|
||||||
): View? {
|
): View {
|
||||||
return inflater.inflate(R.layout.chromecast_subtitle_settings, container, false)
|
val localBinding = ChromecastSubtitleSettingsBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root//inflater.inflate(R.layout.chromecast_subtitle_settings, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var state: SaveChromeCaptionStyle
|
private lateinit var state: SaveChromeCaptionStyle
|
||||||
|
@ -159,7 +168,7 @@ class ChromecastSubtitlesFragment : Fragment() {
|
||||||
onColorSelectedEvent += ::onColorSelected
|
onColorSelectedEvent += ::onColorSelected
|
||||||
onDialogDismissedEvent += ::onDialogDismissed
|
onDialogDismissedEvent += ::onDialogDismissed
|
||||||
|
|
||||||
context?.fixPaddingStatusbar(subs_root)
|
fixPaddingStatusbar(binding?.subsRoot)
|
||||||
|
|
||||||
state = getCurrentSavedStyle()
|
state = getCurrentSavedStyle()
|
||||||
context?.updateState()
|
context?.updateState()
|
||||||
|
@ -185,22 +194,25 @@ class ChromecastSubtitlesFragment : Fragment() {
|
||||||
|
|
||||||
this.setOnLongClickListener {
|
this.setOnLongClickListener {
|
||||||
it.context.setColor(id, null)
|
it.context.setColor(id, null)
|
||||||
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
showToast(R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||||
return@setOnLongClickListener true
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subs_text_color.setup(0)
|
binding?.apply {
|
||||||
subs_outline_color.setup(1)
|
subsTextColor.setup(0)
|
||||||
subs_background_color.setup(2)
|
subsOutlineColor.setup(1)
|
||||||
|
subsBackgroundColor.setup(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
val dismissCallback = {
|
val dismissCallback = {
|
||||||
if (hide)
|
if (hide)
|
||||||
activity?.hideSystemUI()
|
activity?.hideSystemUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
subs_edge_type.setFocusableInTv()
|
binding?.subsEdgeType?.setFocusableInTv()
|
||||||
subs_edge_type.setOnClickListener { textView ->
|
binding?.subsEdgeType?.setOnClickListener { textView ->
|
||||||
val edgeTypes = listOf(
|
val edgeTypes = listOf(
|
||||||
Pair(
|
Pair(
|
||||||
EDGE_TYPE_NONE,
|
EDGE_TYPE_NONE,
|
||||||
|
@ -237,15 +249,15 @@ class ChromecastSubtitlesFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subs_edge_type.setOnLongClickListener {
|
binding?.subsEdgeType?.setOnLongClickListener {
|
||||||
state.edgeType = defaultState.edgeType
|
state.edgeType = defaultState.edgeType
|
||||||
it.context.updateState()
|
it.context.updateState()
|
||||||
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
showToast(R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||||
return@setOnLongClickListener true
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
subs_font_size.setFocusableInTv()
|
binding?.subsFontSize?.setFocusableInTv()
|
||||||
subs_font_size.setOnClickListener { textView ->
|
binding?.subsFontSize?.setOnClickListener { textView ->
|
||||||
val fontSizes = listOf(
|
val fontSizes = listOf(
|
||||||
Pair(0.75f, "75%"),
|
Pair(0.75f, "75%"),
|
||||||
Pair(0.80f, "80%"),
|
Pair(0.80f, "80%"),
|
||||||
|
@ -278,24 +290,26 @@ class ChromecastSubtitlesFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subs_font_size.setOnLongClickListener { _ ->
|
binding?.subsFontSize?.setOnLongClickListener { _ ->
|
||||||
state.fontScale = defaultState.fontScale
|
state.fontScale = defaultState.fontScale
|
||||||
//textView.context.updateState() // font size not changed
|
//textView.context.updateState() // font size not changed
|
||||||
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
showToast(R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||||
return@setOnLongClickListener true
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
subs_font.setFocusableInTv()
|
|
||||||
subs_font.setOnClickListener { textView ->
|
|
||||||
|
binding?.subsFont?.setFocusableInTv()
|
||||||
|
binding?.subsFont?.setOnClickListener { textView ->
|
||||||
val fontTypes = listOf(
|
val fontTypes = listOf(
|
||||||
Pair(null, textView.context.getString(R.string.normal)),
|
null to textView.context.getString(R.string.normal),
|
||||||
Pair("Droid Sans", "Droid Sans"),
|
"Droid Sans" to "Droid Sans",
|
||||||
Pair("Droid Sans Mono", "Droid Sans Mono"),
|
"Droid Sans Mono" to "Droid Sans Mono",
|
||||||
Pair("Droid Serif Regular", "Droid Serif Regular"),
|
"Droid Serif Regular" to "Droid Serif Regular",
|
||||||
Pair("Cutive Mono", "Cutive Mono"),
|
"Cutive Mono" to "Cutive Mono",
|
||||||
Pair("Short Stack", "Short Stack"),
|
"Short Stack" to "Short Stack",
|
||||||
Pair("Quintessential", "Quintessential"),
|
"Quintessential" to "Quintessential",
|
||||||
Pair("Alegreya Sans SC", "Alegreya Sans SC"),
|
"Alegreya Sans SC" to "Alegreya Sans SC",
|
||||||
)
|
)
|
||||||
|
|
||||||
//showBottomDialog
|
//showBottomDialog
|
||||||
|
@ -310,35 +324,35 @@ class ChromecastSubtitlesFragment : Fragment() {
|
||||||
textView.context.updateState()
|
textView.context.updateState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding?.subsFont?.setOnLongClickListener { textView ->
|
||||||
subs_font.setOnLongClickListener { textView ->
|
|
||||||
state.fontFamily = defaultState.fontFamily
|
state.fontFamily = defaultState.fontFamily
|
||||||
textView.context.updateState()
|
textView.context.updateState()
|
||||||
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||||
return@setOnLongClickListener true
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel_btt.setOnClickListener {
|
binding?.cancelBtt?.setOnClickListener {
|
||||||
activity?.popCurrentPage()
|
activity?.popCurrentPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
apply_btt.setOnClickListener {
|
binding?.applyBtt?.setOnClickListener {
|
||||||
it.context.saveStyle(state)
|
it.context.saveStyle(state)
|
||||||
applyStyleEvent.invoke(state)
|
applyStyleEvent.invoke(state)
|
||||||
//it.context.fromSaveToStyle(state)
|
//it.context.fromSaveToStyle(state)
|
||||||
activity?.popCurrentPage()
|
activity?.popCurrentPage()
|
||||||
}
|
}
|
||||||
|
binding?.subtitleText?.apply {
|
||||||
subtitle_text.setCues(
|
setCues(
|
||||||
listOf(
|
listOf(
|
||||||
Cue.Builder()
|
Cue.Builder()
|
||||||
.setTextSize(
|
.setTextSize(
|
||||||
getPixels(TypedValue.COMPLEX_UNIT_SP, 25.0f).toFloat(),
|
getPixels(TypedValue.COMPLEX_UNIT_SP, 25.0f).toFloat(),
|
||||||
Cue.TEXT_SIZE_TYPE_ABSOLUTE
|
Cue.TEXT_SIZE_TYPE_ABSOLUTE
|
||||||
)
|
)
|
||||||
.setText(subtitle_text.context.getString(R.string.subtitles_example_text))
|
.setText(context.getString(R.string.subtitles_example_text))
|
||||||
.build()
|
.build()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent
|
||||||
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
|
import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.SubtitleSettingsBinding
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||||
import com.lagradost.cloudstream3.utils.Event
|
import com.lagradost.cloudstream3.utils.Event
|
||||||
|
@ -37,8 +38,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
|
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
||||||
import kotlinx.android.synthetic.main.subtitle_settings.*
|
|
||||||
import kotlinx.android.synthetic.main.toast.view.*
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
const val SUBTITLE_KEY = "subtitle_settings"
|
const val SUBTITLE_KEY = "subtitle_settings"
|
||||||
|
@ -184,10 +183,10 @@ class SubtitlesFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.updateState() {
|
private fun Context.updateState() {
|
||||||
subtitle_text?.setStyle(fromSaveToStyle(state))
|
binding?.subtitleText?.setStyle(fromSaveToStyle(state))
|
||||||
val text = subtitle_text.context.getString(R.string.subtitles_example_text)
|
val text = getString(R.string.subtitles_example_text)
|
||||||
val fixedText = if (state.upperCase) text.uppercase() else text
|
val fixedText = if (state.upperCase) text.uppercase() else text
|
||||||
subtitle_text?.setCues(
|
binding?.subtitleText?.setCues(
|
||||||
listOf(
|
listOf(
|
||||||
Cue.Builder()
|
Cue.Builder()
|
||||||
.setTextSize(
|
.setTextSize(
|
||||||
|
@ -213,12 +212,21 @@ class SubtitlesFragment : Fragment() {
|
||||||
return if (color == Color.TRANSPARENT) Color.BLACK else color
|
return if (color == Color.TRANSPARENT) Color.BLACK else color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
binding = null
|
||||||
|
super.onDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
|
var binding: SubtitleSettingsBinding? = null
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?,
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View {
|
||||||
return inflater.inflate(R.layout.subtitle_settings, container, false)
|
val localBinding = SubtitleSettingsBinding.inflate(inflater, container, false)
|
||||||
|
binding = localBinding
|
||||||
|
return localBinding.root
|
||||||
|
//return inflater.inflate(R.layout.subtitle_settings, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var state: SaveCaptionStyle
|
private lateinit var state: SaveCaptionStyle
|
||||||
|
@ -234,11 +242,11 @@ class SubtitlesFragment : Fragment() {
|
||||||
hide = arguments?.getBoolean("hide") ?: true
|
hide = arguments?.getBoolean("hide") ?: true
|
||||||
onColorSelectedEvent += ::onColorSelected
|
onColorSelectedEvent += ::onColorSelected
|
||||||
onDialogDismissedEvent += ::onDialogDismissed
|
onDialogDismissedEvent += ::onDialogDismissed
|
||||||
subs_import_text?.text = getString(R.string.subs_import_text).format(
|
binding?.subsImportText?.text = getString(R.string.subs_import_text).format(
|
||||||
context?.getExternalFilesDir(null)?.absolutePath.toString() + "/Fonts"
|
context?.getExternalFilesDir(null)?.absolutePath.toString() + "/Fonts"
|
||||||
)
|
)
|
||||||
|
|
||||||
context?.fixPaddingStatusbar(subs_root)
|
fixPaddingStatusbar(binding?.subsRoot)
|
||||||
|
|
||||||
state = getCurrentSavedStyle()
|
state = getCurrentSavedStyle()
|
||||||
context?.updateState()
|
context?.updateState()
|
||||||
|
@ -264,317 +272,318 @@ class SubtitlesFragment : Fragment() {
|
||||||
|
|
||||||
this.setOnLongClickListener {
|
this.setOnLongClickListener {
|
||||||
it.context.setColor(id, null)
|
it.context.setColor(id, null)
|
||||||
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
showToast(R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||||
return@setOnLongClickListener true
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding?.apply {
|
||||||
|
subsTextColor.setup(0)
|
||||||
|
subsOutlineColor.setup(1)
|
||||||
|
subsBackgroundColor.setup(2)
|
||||||
|
subsWindowColor.setup(3)
|
||||||
|
|
||||||
subs_text_color.setup(0)
|
val dismissCallback = {
|
||||||
subs_outline_color.setup(1)
|
|
||||||
subs_background_color.setup(2)
|
|
||||||
subs_window_color.setup(3)
|
|
||||||
|
|
||||||
val dismissCallback = {
|
|
||||||
if (hide)
|
|
||||||
activity?.hideSystemUI()
|
|
||||||
}
|
|
||||||
|
|
||||||
subs_subtitle_elevation.setFocusableInTv()
|
|
||||||
subs_subtitle_elevation.setOnClickListener { textView ->
|
|
||||||
val suffix = "dp"
|
|
||||||
val elevationTypes = listOf(
|
|
||||||
Pair(0, textView.context.getString(R.string.none)),
|
|
||||||
Pair(10, "10$suffix"),
|
|
||||||
Pair(20, "20$suffix"),
|
|
||||||
Pair(30, "30$suffix"),
|
|
||||||
Pair(40, "40$suffix"),
|
|
||||||
Pair(50, "50$suffix"),
|
|
||||||
Pair(60, "60$suffix"),
|
|
||||||
Pair(70, "70$suffix"),
|
|
||||||
Pair(80, "80$suffix"),
|
|
||||||
Pair(90, "90$suffix"),
|
|
||||||
Pair(100, "100$suffix"),
|
|
||||||
)
|
|
||||||
|
|
||||||
//showBottomDialog
|
|
||||||
activity?.showDialog(
|
|
||||||
elevationTypes.map { it.second },
|
|
||||||
elevationTypes.map { it.first }.indexOf(state.elevation),
|
|
||||||
(textView as TextView).text.toString(),
|
|
||||||
false,
|
|
||||||
dismissCallback
|
|
||||||
) { index ->
|
|
||||||
state.elevation = elevationTypes.map { it.first }[index]
|
|
||||||
textView.context.updateState()
|
|
||||||
if (hide)
|
if (hide)
|
||||||
activity?.hideSystemUI()
|
activity?.hideSystemUI()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
subs_subtitle_elevation.setOnLongClickListener {
|
subsSubtitleElevation.setFocusableInTv()
|
||||||
state.elevation = DEF_SUBS_ELEVATION
|
subsSubtitleElevation.setOnClickListener { textView ->
|
||||||
it.context.updateState()
|
val suffix = "dp"
|
||||||
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
val elevationTypes = listOf(
|
||||||
return@setOnLongClickListener true
|
Pair(0, textView.context.getString(R.string.none)),
|
||||||
}
|
Pair(10, "10$suffix"),
|
||||||
|
Pair(20, "20$suffix"),
|
||||||
|
Pair(30, "30$suffix"),
|
||||||
|
Pair(40, "40$suffix"),
|
||||||
|
Pair(50, "50$suffix"),
|
||||||
|
Pair(60, "60$suffix"),
|
||||||
|
Pair(70, "70$suffix"),
|
||||||
|
Pair(80, "80$suffix"),
|
||||||
|
Pair(90, "90$suffix"),
|
||||||
|
Pair(100, "100$suffix"),
|
||||||
|
)
|
||||||
|
|
||||||
subs_edge_type.setFocusableInTv()
|
//showBottomDialog
|
||||||
subs_edge_type.setOnClickListener { textView ->
|
activity?.showDialog(
|
||||||
val edgeTypes = listOf(
|
elevationTypes.map { it.second },
|
||||||
Pair(
|
elevationTypes.map { it.first }.indexOf(state.elevation),
|
||||||
CaptionStyleCompat.EDGE_TYPE_NONE,
|
(textView as TextView).text.toString(),
|
||||||
textView.context.getString(R.string.subtitles_none)
|
false,
|
||||||
),
|
dismissCallback
|
||||||
Pair(
|
) { index ->
|
||||||
CaptionStyleCompat.EDGE_TYPE_OUTLINE,
|
state.elevation = elevationTypes.map { it.first }[index]
|
||||||
textView.context.getString(R.string.subtitles_outline)
|
textView.context.updateState()
|
||||||
),
|
if (hide)
|
||||||
Pair(
|
activity?.hideSystemUI()
|
||||||
CaptionStyleCompat.EDGE_TYPE_DEPRESSED,
|
|
||||||
textView.context.getString(R.string.subtitles_depressed)
|
|
||||||
),
|
|
||||||
Pair(
|
|
||||||
CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW,
|
|
||||||
textView.context.getString(R.string.subtitles_shadow)
|
|
||||||
),
|
|
||||||
Pair(
|
|
||||||
CaptionStyleCompat.EDGE_TYPE_RAISED,
|
|
||||||
textView.context.getString(R.string.subtitles_raised)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
//showBottomDialog
|
|
||||||
activity?.showDialog(
|
|
||||||
edgeTypes.map { it.second },
|
|
||||||
edgeTypes.map { it.first }.indexOf(state.edgeType),
|
|
||||||
(textView as TextView).text.toString(),
|
|
||||||
false,
|
|
||||||
dismissCallback
|
|
||||||
) { index ->
|
|
||||||
state.edgeType = edgeTypes.map { it.first }[index]
|
|
||||||
textView.context.updateState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subs_edge_type.setOnLongClickListener {
|
|
||||||
state.edgeType = CaptionStyleCompat.EDGE_TYPE_OUTLINE
|
|
||||||
it.context.updateState()
|
|
||||||
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
|
||||||
return@setOnLongClickListener true
|
|
||||||
}
|
|
||||||
|
|
||||||
subs_font_size.setFocusableInTv()
|
|
||||||
subs_font_size.setOnClickListener { textView ->
|
|
||||||
val suffix = "sp"
|
|
||||||
val fontSizes = listOf(
|
|
||||||
Pair(null, textView.context.getString(R.string.normal)),
|
|
||||||
Pair(6f, "6$suffix"),
|
|
||||||
Pair(7f, "7$suffix"),
|
|
||||||
Pair(8f, "8$suffix"),
|
|
||||||
Pair(9f, "9$suffix"),
|
|
||||||
Pair(10f, "10$suffix"),
|
|
||||||
Pair(11f, "11$suffix"),
|
|
||||||
Pair(12f, "12$suffix"),
|
|
||||||
Pair(13f, "13$suffix"),
|
|
||||||
Pair(14f, "14$suffix"),
|
|
||||||
Pair(15f, "15$suffix"),
|
|
||||||
Pair(16f, "16$suffix"),
|
|
||||||
Pair(17f, "17$suffix"),
|
|
||||||
Pair(18f, "18$suffix"),
|
|
||||||
Pair(19f, "19$suffix"),
|
|
||||||
Pair(20f, "20$suffix"),
|
|
||||||
Pair(21f, "21$suffix"),
|
|
||||||
Pair(22f, "22$suffix"),
|
|
||||||
Pair(23f, "23$suffix"),
|
|
||||||
Pair(24f, "24$suffix"),
|
|
||||||
Pair(25f, "25$suffix"),
|
|
||||||
Pair(26f, "26$suffix"),
|
|
||||||
Pair(28f, "28$suffix"),
|
|
||||||
Pair(30f, "30$suffix"),
|
|
||||||
Pair(32f, "32$suffix"),
|
|
||||||
Pair(34f, "34$suffix"),
|
|
||||||
Pair(36f, "36$suffix"),
|
|
||||||
Pair(38f, "38$suffix"),
|
|
||||||
Pair(40f, "40$suffix"),
|
|
||||||
Pair(42f, "42$suffix"),
|
|
||||||
Pair(44f, "44$suffix"),
|
|
||||||
Pair(48f, "48$suffix"),
|
|
||||||
Pair(60f, "60$suffix"),
|
|
||||||
)
|
|
||||||
|
|
||||||
//showBottomDialog
|
|
||||||
activity?.showDialog(
|
|
||||||
fontSizes.map { it.second },
|
|
||||||
fontSizes.map { it.first }.indexOf(state.fixedTextSize),
|
|
||||||
(textView as TextView).text.toString(),
|
|
||||||
false,
|
|
||||||
dismissCallback
|
|
||||||
) { index ->
|
|
||||||
state.fixedTextSize = fontSizes.map { it.first }[index]
|
|
||||||
//textView.context.updateState() // font size not changed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subtitles_remove_bloat?.isChecked = state.removeBloat
|
|
||||||
subtitles_remove_bloat?.setOnCheckedChangeListener { _, b ->
|
|
||||||
state.removeBloat = b
|
|
||||||
}
|
|
||||||
subtitles_uppercase?.isChecked = state.upperCase
|
|
||||||
subtitles_uppercase?.setOnCheckedChangeListener { _, b ->
|
|
||||||
state.upperCase = b
|
|
||||||
context?.updateState()
|
|
||||||
}
|
|
||||||
|
|
||||||
subtitles_remove_captions?.isChecked = state.removeCaptions
|
|
||||||
subtitles_remove_captions?.setOnCheckedChangeListener { _, b ->
|
|
||||||
state.removeCaptions = b
|
|
||||||
}
|
|
||||||
|
|
||||||
subs_font_size.setOnLongClickListener { _ ->
|
|
||||||
state.fixedTextSize = null
|
|
||||||
//textView.context.updateState() // font size not changed
|
|
||||||
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
|
||||||
return@setOnLongClickListener true
|
|
||||||
}
|
|
||||||
|
|
||||||
//Fetch current value from preference
|
|
||||||
context?.let { ctx ->
|
|
||||||
subtitles_filter_sub_lang?.isChecked =
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(ctx)
|
|
||||||
.getBoolean(getString(R.string.filter_sub_lang_key), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
subtitles_filter_sub_lang?.setOnCheckedChangeListener { _, b ->
|
|
||||||
context?.let { ctx ->
|
|
||||||
PreferenceManager.getDefaultSharedPreferences(ctx)
|
|
||||||
.edit()
|
|
||||||
.putBoolean(getString(R.string.filter_sub_lang_key), b)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subs_font.setFocusableInTv()
|
|
||||||
subs_font.setOnClickListener { textView ->
|
|
||||||
val fontTypes = listOf(
|
|
||||||
Pair(null, textView.context.getString(R.string.normal)),
|
|
||||||
Pair(R.font.trebuchet_ms, "Trebuchet MS"),
|
|
||||||
Pair(R.font.netflix_sans, "Netflix Sans"),
|
|
||||||
Pair(R.font.google_sans, "Google Sans"),
|
|
||||||
Pair(R.font.open_sans, "Open Sans"),
|
|
||||||
Pair(R.font.futura, "Futura"),
|
|
||||||
Pair(R.font.consola, "Consola"),
|
|
||||||
Pair(R.font.gotham, "Gotham"),
|
|
||||||
Pair(R.font.lucida_grande, "Lucida Grande"),
|
|
||||||
Pair(R.font.stix_general, "STIX General"),
|
|
||||||
Pair(R.font.times_new_roman, "Times New Roman"),
|
|
||||||
Pair(R.font.verdana, "Verdana"),
|
|
||||||
Pair(R.font.ubuntu_regular, "Ubuntu"),
|
|
||||||
Pair(R.font.comic_sans, "Comic Sans"),
|
|
||||||
Pair(R.font.poppins_regular, "Poppins"),
|
|
||||||
)
|
|
||||||
val savedFontTypes = textView.context.getSavedFonts()
|
|
||||||
|
|
||||||
val currentIndex =
|
|
||||||
savedFontTypes.indexOfFirst { it.absolutePath == state.typefaceFilePath }
|
|
||||||
.let { index ->
|
|
||||||
if (index == -1)
|
|
||||||
fontTypes.indexOfFirst { it.first == state.typeface }
|
|
||||||
else index + fontTypes.size
|
|
||||||
}
|
|
||||||
|
|
||||||
//showBottomDialog
|
|
||||||
activity?.showDialog(
|
|
||||||
fontTypes.map { it.second } + savedFontTypes.map { it.name },
|
|
||||||
currentIndex,
|
|
||||||
(textView as TextView).text.toString(),
|
|
||||||
false,
|
|
||||||
dismissCallback
|
|
||||||
) { index ->
|
|
||||||
if (index < fontTypes.size) {
|
|
||||||
state.typeface = fontTypes[index].first
|
|
||||||
state.typefaceFilePath = null
|
|
||||||
} else {
|
|
||||||
state.typefaceFilePath = savedFontTypes[index - fontTypes.size].absolutePath
|
|
||||||
state.typeface = null
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subsSubtitleElevation.setOnLongClickListener {
|
||||||
|
state.elevation = DEF_SUBS_ELEVATION
|
||||||
|
it.context.updateState()
|
||||||
|
showToast(R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||||
|
return@setOnLongClickListener true
|
||||||
|
}
|
||||||
|
|
||||||
|
subsEdgeType.setFocusableInTv()
|
||||||
|
subsEdgeType.setOnClickListener { textView ->
|
||||||
|
val edgeTypes = listOf(
|
||||||
|
Pair(
|
||||||
|
CaptionStyleCompat.EDGE_TYPE_NONE,
|
||||||
|
textView.context.getString(R.string.subtitles_none)
|
||||||
|
),
|
||||||
|
Pair(
|
||||||
|
CaptionStyleCompat.EDGE_TYPE_OUTLINE,
|
||||||
|
textView.context.getString(R.string.subtitles_outline)
|
||||||
|
),
|
||||||
|
Pair(
|
||||||
|
CaptionStyleCompat.EDGE_TYPE_DEPRESSED,
|
||||||
|
textView.context.getString(R.string.subtitles_depressed)
|
||||||
|
),
|
||||||
|
Pair(
|
||||||
|
CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW,
|
||||||
|
textView.context.getString(R.string.subtitles_shadow)
|
||||||
|
),
|
||||||
|
Pair(
|
||||||
|
CaptionStyleCompat.EDGE_TYPE_RAISED,
|
||||||
|
textView.context.getString(R.string.subtitles_raised)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
//showBottomDialog
|
||||||
|
activity?.showDialog(
|
||||||
|
edgeTypes.map { it.second },
|
||||||
|
edgeTypes.map { it.first }.indexOf(state.edgeType),
|
||||||
|
(textView as TextView).text.toString(),
|
||||||
|
false,
|
||||||
|
dismissCallback
|
||||||
|
) { index ->
|
||||||
|
state.edgeType = edgeTypes.map { it.first }[index]
|
||||||
|
textView.context.updateState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subsEdgeType.setOnLongClickListener {
|
||||||
|
state.edgeType = CaptionStyleCompat.EDGE_TYPE_OUTLINE
|
||||||
|
it.context.updateState()
|
||||||
|
showToast(R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||||
|
return@setOnLongClickListener true
|
||||||
|
}
|
||||||
|
|
||||||
|
subsFontSize.setFocusableInTv()
|
||||||
|
subsFontSize.setOnClickListener { textView ->
|
||||||
|
val suffix = "sp"
|
||||||
|
val fontSizes = listOf(
|
||||||
|
Pair(null, textView.context.getString(R.string.normal)),
|
||||||
|
Pair(6f, "6$suffix"),
|
||||||
|
Pair(7f, "7$suffix"),
|
||||||
|
Pair(8f, "8$suffix"),
|
||||||
|
Pair(9f, "9$suffix"),
|
||||||
|
Pair(10f, "10$suffix"),
|
||||||
|
Pair(11f, "11$suffix"),
|
||||||
|
Pair(12f, "12$suffix"),
|
||||||
|
Pair(13f, "13$suffix"),
|
||||||
|
Pair(14f, "14$suffix"),
|
||||||
|
Pair(15f, "15$suffix"),
|
||||||
|
Pair(16f, "16$suffix"),
|
||||||
|
Pair(17f, "17$suffix"),
|
||||||
|
Pair(18f, "18$suffix"),
|
||||||
|
Pair(19f, "19$suffix"),
|
||||||
|
Pair(20f, "20$suffix"),
|
||||||
|
Pair(21f, "21$suffix"),
|
||||||
|
Pair(22f, "22$suffix"),
|
||||||
|
Pair(23f, "23$suffix"),
|
||||||
|
Pair(24f, "24$suffix"),
|
||||||
|
Pair(25f, "25$suffix"),
|
||||||
|
Pair(26f, "26$suffix"),
|
||||||
|
Pair(28f, "28$suffix"),
|
||||||
|
Pair(30f, "30$suffix"),
|
||||||
|
Pair(32f, "32$suffix"),
|
||||||
|
Pair(34f, "34$suffix"),
|
||||||
|
Pair(36f, "36$suffix"),
|
||||||
|
Pair(38f, "38$suffix"),
|
||||||
|
Pair(40f, "40$suffix"),
|
||||||
|
Pair(42f, "42$suffix"),
|
||||||
|
Pair(44f, "44$suffix"),
|
||||||
|
Pair(48f, "48$suffix"),
|
||||||
|
Pair(60f, "60$suffix"),
|
||||||
|
)
|
||||||
|
|
||||||
|
//showBottomDialog
|
||||||
|
activity?.showDialog(
|
||||||
|
fontSizes.map { it.second },
|
||||||
|
fontSizes.map { it.first }.indexOf(state.fixedTextSize),
|
||||||
|
(textView as TextView).text.toString(),
|
||||||
|
false,
|
||||||
|
dismissCallback
|
||||||
|
) { index ->
|
||||||
|
state.fixedTextSize = fontSizes.map { it.first }[index]
|
||||||
|
//textView.context.updateState() // font size not changed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subtitlesRemoveBloat.isChecked = state.removeBloat
|
||||||
|
subtitlesRemoveBloat.setOnCheckedChangeListener { _, b ->
|
||||||
|
state.removeBloat = b
|
||||||
|
}
|
||||||
|
subtitlesUppercase.isChecked = state.upperCase
|
||||||
|
subtitlesUppercase.setOnCheckedChangeListener { _, b ->
|
||||||
|
state.upperCase = b
|
||||||
|
context?.updateState()
|
||||||
|
}
|
||||||
|
|
||||||
|
subtitlesRemoveCaptions.isChecked = state.removeCaptions
|
||||||
|
subtitlesRemoveCaptions.setOnCheckedChangeListener { _, b ->
|
||||||
|
state.removeCaptions = b
|
||||||
|
}
|
||||||
|
|
||||||
|
subsFontSize.setOnLongClickListener { _ ->
|
||||||
|
state.fixedTextSize = null
|
||||||
|
//textView.context.updateState() // font size not changed
|
||||||
|
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||||
|
return@setOnLongClickListener true
|
||||||
|
}
|
||||||
|
|
||||||
|
//Fetch current value from preference
|
||||||
|
context?.let { ctx ->
|
||||||
|
subtitlesFilterSubLang.isChecked =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
.getBoolean(getString(R.string.filter_sub_lang_key), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
subtitlesFilterSubLang.setOnCheckedChangeListener { _, b ->
|
||||||
|
context?.let { ctx ->
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(ctx)
|
||||||
|
.edit()
|
||||||
|
.putBoolean(getString(R.string.filter_sub_lang_key), b)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subsFont.setFocusableInTv()
|
||||||
|
subsFont.setOnClickListener { textView ->
|
||||||
|
val fontTypes = listOf(
|
||||||
|
Pair(null, textView.context.getString(R.string.normal)),
|
||||||
|
Pair(R.font.trebuchet_ms, "Trebuchet MS"),
|
||||||
|
Pair(R.font.netflix_sans, "Netflix Sans"),
|
||||||
|
Pair(R.font.google_sans, "Google Sans"),
|
||||||
|
Pair(R.font.open_sans, "Open Sans"),
|
||||||
|
Pair(R.font.futura, "Futura"),
|
||||||
|
Pair(R.font.consola, "Consola"),
|
||||||
|
Pair(R.font.gotham, "Gotham"),
|
||||||
|
Pair(R.font.lucida_grande, "Lucida Grande"),
|
||||||
|
Pair(R.font.stix_general, "STIX General"),
|
||||||
|
Pair(R.font.times_new_roman, "Times New Roman"),
|
||||||
|
Pair(R.font.verdana, "Verdana"),
|
||||||
|
Pair(R.font.ubuntu_regular, "Ubuntu"),
|
||||||
|
Pair(R.font.comic_sans, "Comic Sans"),
|
||||||
|
Pair(R.font.poppins_regular, "Poppins"),
|
||||||
|
)
|
||||||
|
val savedFontTypes = textView.context.getSavedFonts()
|
||||||
|
|
||||||
|
val currentIndex =
|
||||||
|
savedFontTypes.indexOfFirst { it.absolutePath == state.typefaceFilePath }
|
||||||
|
.let { index ->
|
||||||
|
if (index == -1)
|
||||||
|
fontTypes.indexOfFirst { it.first == state.typeface }
|
||||||
|
else index + fontTypes.size
|
||||||
|
}
|
||||||
|
|
||||||
|
//showBottomDialog
|
||||||
|
activity?.showDialog(
|
||||||
|
fontTypes.map { it.second } + savedFontTypes.map { it.name },
|
||||||
|
currentIndex,
|
||||||
|
(textView as TextView).text.toString(),
|
||||||
|
false,
|
||||||
|
dismissCallback
|
||||||
|
) { index ->
|
||||||
|
if (index < fontTypes.size) {
|
||||||
|
state.typeface = fontTypes[index].first
|
||||||
|
state.typefaceFilePath = null
|
||||||
|
} else {
|
||||||
|
state.typefaceFilePath = savedFontTypes[index - fontTypes.size].absolutePath
|
||||||
|
state.typeface = null
|
||||||
|
}
|
||||||
|
textView.context.updateState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subsFont.setOnLongClickListener { textView ->
|
||||||
|
state.typeface = null
|
||||||
|
state.typefaceFilePath = null
|
||||||
textView.context.updateState()
|
textView.context.updateState()
|
||||||
|
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||||
|
return@setOnLongClickListener true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
subs_font.setOnLongClickListener { textView ->
|
subsAutoSelectLanguage.setFocusableInTv()
|
||||||
state.typeface = null
|
subsAutoSelectLanguage.setOnClickListener { textView ->
|
||||||
state.typefaceFilePath = null
|
val langMap = arrayListOf(
|
||||||
textView.context.updateState()
|
SubtitleHelper.Language639(
|
||||||
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
textView.context.getString(R.string.none),
|
||||||
return@setOnLongClickListener true
|
textView.context.getString(R.string.none),
|
||||||
}
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
),
|
||||||
|
)
|
||||||
|
langMap.addAll(SubtitleHelper.languages)
|
||||||
|
|
||||||
subs_auto_select_language.setFocusableInTv()
|
val lang639_1 = langMap.map { it.ISO_639_1 }
|
||||||
subs_auto_select_language.setOnClickListener { textView ->
|
activity?.showDialog(
|
||||||
val langMap = arrayListOf(
|
langMap.map { it.languageName },
|
||||||
SubtitleHelper.Language639(
|
lang639_1.indexOf(getAutoSelectLanguageISO639_1()),
|
||||||
textView.context.getString(R.string.none),
|
(textView as TextView).text.toString(),
|
||||||
textView.context.getString(R.string.none),
|
true,
|
||||||
"",
|
dismissCallback
|
||||||
"",
|
) { index ->
|
||||||
"",
|
setKey(SUBTITLE_AUTO_SELECT_KEY, lang639_1[index])
|
||||||
"",
|
}
|
||||||
""
|
|
||||||
),
|
|
||||||
)
|
|
||||||
langMap.addAll(SubtitleHelper.languages)
|
|
||||||
|
|
||||||
val lang639_1 = langMap.map { it.ISO_639_1 }
|
|
||||||
activity?.showDialog(
|
|
||||||
langMap.map { it.languageName },
|
|
||||||
lang639_1.indexOf(getAutoSelectLanguageISO639_1()),
|
|
||||||
(textView as TextView).text.toString(),
|
|
||||||
true,
|
|
||||||
dismissCallback
|
|
||||||
) { index ->
|
|
||||||
setKey(SUBTITLE_AUTO_SELECT_KEY, lang639_1[index])
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
subs_auto_select_language.setOnLongClickListener {
|
subsAutoSelectLanguage.setOnLongClickListener {
|
||||||
setKey(SUBTITLE_AUTO_SELECT_KEY, "en")
|
setKey(SUBTITLE_AUTO_SELECT_KEY, "en")
|
||||||
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||||
return@setOnLongClickListener true
|
return@setOnLongClickListener true
|
||||||
}
|
|
||||||
|
|
||||||
subs_download_languages.setFocusableInTv()
|
|
||||||
subs_download_languages.setOnClickListener { textView ->
|
|
||||||
val langMap = SubtitleHelper.languages
|
|
||||||
val lang639_1 = langMap.map { it.ISO_639_1 }
|
|
||||||
val keys = getDownloadSubsLanguageISO639_1()
|
|
||||||
val keyMap = keys.map { lang639_1.indexOf(it) }.filter { it >= 0 }
|
|
||||||
|
|
||||||
activity?.showMultiDialog(
|
|
||||||
langMap.map { it.languageName },
|
|
||||||
keyMap,
|
|
||||||
(textView as TextView).text.toString(),
|
|
||||||
dismissCallback
|
|
||||||
) { indexList ->
|
|
||||||
setKey(SUBTITLE_DOWNLOAD_KEY, indexList.map { lang639_1[it] }.toList())
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
subs_download_languages.setOnLongClickListener {
|
subsDownloadLanguages.setFocusableInTv()
|
||||||
setKey(SUBTITLE_DOWNLOAD_KEY, listOf("en"))
|
subsDownloadLanguages.setOnClickListener { textView ->
|
||||||
|
val langMap = SubtitleHelper.languages
|
||||||
|
val lang639_1 = langMap.map { it.ISO_639_1 }
|
||||||
|
val keys = getDownloadSubsLanguageISO639_1()
|
||||||
|
val keyMap = keys.map { lang639_1.indexOf(it) }.filter { it >= 0 }
|
||||||
|
|
||||||
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
activity?.showMultiDialog(
|
||||||
return@setOnLongClickListener true
|
langMap.map { it.languageName },
|
||||||
}
|
keyMap,
|
||||||
|
(textView as TextView).text.toString(),
|
||||||
|
dismissCallback
|
||||||
|
) { indexList ->
|
||||||
|
setKey(SUBTITLE_DOWNLOAD_KEY, indexList.map { lang639_1[it] }.toList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cancel_btt.setOnClickListener {
|
subsDownloadLanguages.setOnLongClickListener {
|
||||||
activity?.popCurrentPage()
|
setKey(SUBTITLE_DOWNLOAD_KEY, listOf("en"))
|
||||||
}
|
|
||||||
|
|
||||||
apply_btt.setOnClickListener {
|
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||||
it.context.saveStyle(state)
|
return@setOnLongClickListener true
|
||||||
applyStyleEvent.invoke(state)
|
}
|
||||||
it.context.fromSaveToStyle(state)
|
|
||||||
activity?.popCurrentPage()
|
cancelBtt.setOnClickListener {
|
||||||
|
activity?.popCurrentPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
applyBtt.setOnClickListener {
|
||||||
|
it.context.saveStyle(state)
|
||||||
|
applyStyleEvent.invoke(state)
|
||||||
|
it.context.fromSaveToStyle(state)
|
||||||
|
activity?.popCurrentPage()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ import android.os.*
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.LAYOUT_DIRECTION_LTR
|
||||||
|
import android.view.View.LAYOUT_DIRECTION_RTL
|
||||||
import android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
@ -33,6 +36,7 @@ import androidx.core.widget.ContentLoadingProgressBar
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
@ -47,12 +51,14 @@ import com.google.android.gms.common.GoogleApiAvailability
|
||||||
import com.google.android.gms.common.wrappers.Wrappers
|
import com.google.android.gms.common.wrappers.Wrappers
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.CommonActivity.activity
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent
|
import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||||
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
||||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching
|
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching
|
||||||
|
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
|
||||||
import com.lagradost.cloudstream3.ui.WebviewFragment
|
import com.lagradost.cloudstream3.ui.WebviewFragment
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||||
|
@ -87,6 +93,9 @@ object AppUtils {
|
||||||
return if (layoutManager == null || adapter == null) false else layoutManager.findLastCompletelyVisibleItemPosition() < adapter.itemCount - 7 // bit more than 1 to make it more seamless
|
return if (layoutManager == null || adapter == null) false else layoutManager.findLastCompletelyVisibleItemPosition() < adapter.itemCount - 7 // bit more than 1 to make it more seamless
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun View.isLtr() = this.layoutDirection == LAYOUT_DIRECTION_LTR
|
||||||
|
fun View.isRtl() = this.layoutDirection == LAYOUT_DIRECTION_RTL
|
||||||
|
|
||||||
fun BottomSheetDialog?.ownHide() {
|
fun BottomSheetDialog?.ownHide() {
|
||||||
this?.hide()
|
this?.hide()
|
||||||
}
|
}
|
||||||
|
@ -198,7 +207,11 @@ object AppUtils {
|
||||||
animation.start()
|
animation.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.createNotificationChannel(channelId: String, channelName: String, description: String) {
|
fun Context.createNotificationChannel(
|
||||||
|
channelId: String,
|
||||||
|
channelName: String,
|
||||||
|
description: String
|
||||||
|
) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
||||||
val channel =
|
val channel =
|
||||||
|
@ -288,6 +301,7 @@ object AppUtils {
|
||||||
|
|
||||||
// https://github.com/googlearchive/leanback-homescreen-channels/blob/master/app/src/main/java/com/google/android/tvhomescreenchannels/SampleTvProvider.java
|
// https://github.com/googlearchive/leanback-homescreen-channels/blob/master/app/src/main/java/com/google/android/tvhomescreenchannels/SampleTvProvider.java
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
|
@Throws
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
suspend fun Context.addProgramsToContinueWatching(data: List<DataStoreHelper.ResumeWatchingResult>) {
|
suspend fun Context.addProgramsToContinueWatching(data: List<DataStoreHelper.ResumeWatchingResult>) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||||
|
@ -369,7 +383,6 @@ object AppUtils {
|
||||||
)
|
)
|
||||||
main {
|
main {
|
||||||
showToast(
|
showToast(
|
||||||
this@loadRepository,
|
|
||||||
getString(R.string.player_loaded_subtitles, repo.name),
|
getString(R.string.player_loaded_subtitles, repo.name),
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
)
|
)
|
||||||
|
@ -577,12 +590,29 @@ object AppUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadResult(
|
||||||
|
url: String,
|
||||||
|
apiName: String,
|
||||||
|
startAction: Int = 0,
|
||||||
|
startValue: Int = 0
|
||||||
|
) {
|
||||||
|
(activity as FragmentActivity?)?.loadResult(url, apiName, startAction, startValue)
|
||||||
|
}
|
||||||
|
|
||||||
fun FragmentActivity.loadResult(
|
fun FragmentActivity.loadResult(
|
||||||
url: String,
|
url: String,
|
||||||
apiName: String,
|
apiName: String,
|
||||||
startAction: Int = 0,
|
startAction: Int = 0,
|
||||||
startValue: Int = 0
|
startValue: Int = 0
|
||||||
) {
|
) {
|
||||||
|
try {
|
||||||
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
Kitsu.isEnabled =
|
||||||
|
settingsManager.getBoolean(this.getString(R.string.show_kitsu_posters_key), true)
|
||||||
|
}catch (t : Throwable) {
|
||||||
|
logError(t)
|
||||||
|
}
|
||||||
|
|
||||||
this.runOnUiThread {
|
this.runOnUiThread {
|
||||||
// viewModelStore.clear()
|
// viewModelStore.clear()
|
||||||
this.navigate(
|
this.navigate(
|
||||||
|
@ -592,6 +622,14 @@ object AppUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadSearchResult(
|
||||||
|
card: SearchResponse,
|
||||||
|
startAction: Int = 0,
|
||||||
|
startValue: Int? = null,
|
||||||
|
) {
|
||||||
|
activity?.loadSearchResult(card, startAction, startValue)
|
||||||
|
}
|
||||||
|
|
||||||
fun Activity?.loadSearchResult(
|
fun Activity?.loadSearchResult(
|
||||||
card: SearchResponse,
|
card: SearchResponse,
|
||||||
startAction: Int = 0,
|
startAction: Int = 0,
|
||||||
|
@ -776,12 +814,12 @@ object AppUtils {
|
||||||
return networkInfo.any {
|
return networkInfo.any {
|
||||||
conManager.getNetworkCapabilities(it)
|
conManager.getNetworkCapabilities(it)
|
||||||
?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true
|
?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true
|
||||||
} &&
|
} &&
|
||||||
!networkInfo.any {
|
!networkInfo.any {
|
||||||
conManager.getNetworkCapabilities(it)
|
conManager.getNetworkCapabilities(it)
|
||||||
?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
|
?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun Activity?.cacheClass(clazz: String?) {
|
private fun Activity?.cacheClass(clazz: String?) {
|
||||||
|
|
|
@ -149,7 +149,7 @@ object BackupUtils {
|
||||||
fun FragmentActivity.backup() {
|
fun FragmentActivity.backup() {
|
||||||
try {
|
try {
|
||||||
if (!checkWrite()) {
|
if (!checkWrite()) {
|
||||||
showToast(this, getString(R.string.backup_failed), Toast.LENGTH_LONG)
|
showToast(getString(R.string.backup_failed), Toast.LENGTH_LONG)
|
||||||
requestRW()
|
requestRW()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,6 @@ object BackupUtils {
|
||||||
printStream.close()
|
printStream.close()
|
||||||
|
|
||||||
showToast(
|
showToast(
|
||||||
this,
|
|
||||||
R.string.backup_success,
|
R.string.backup_success,
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
)
|
)
|
||||||
|
@ -209,7 +208,6 @@ object BackupUtils {
|
||||||
logError(e)
|
logError(e)
|
||||||
try {
|
try {
|
||||||
showToast(
|
showToast(
|
||||||
this,
|
|
||||||
getString(R.string.backup_failed_error_format).format(e.toString()),
|
getString(R.string.backup_failed_error_format).format(e.toString()),
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
)
|
)
|
||||||
|
@ -243,7 +241,6 @@ object BackupUtils {
|
||||||
logError(e)
|
logError(e)
|
||||||
main { // smth can fail in .format
|
main { // smth can fail in .format
|
||||||
showToast(
|
showToast(
|
||||||
activity,
|
|
||||||
getString(R.string.restore_failed_format).format(e.toString())
|
getString(R.string.restore_failed_format).format(e.toString())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -270,7 +267,7 @@ object BackupUtils {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
showToast(this, e.message)
|
showToast(e.message)
|
||||||
logError(e)
|
logError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ const val USER_PROVIDER_API = "user_custom_sites"
|
||||||
|
|
||||||
const val PREFERENCES_NAME = "rebuild_preference"
|
const val PREFERENCES_NAME = "rebuild_preference"
|
||||||
|
|
||||||
|
// TODO degelgate by value for get & set
|
||||||
|
|
||||||
object DataStore {
|
object DataStore {
|
||||||
val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
|
val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
||||||
|
|
|
@ -3,6 +3,8 @@ package com.lagradost.cloudstream3.utils
|
||||||
class Event<T> {
|
class Event<T> {
|
||||||
private val observers = mutableSetOf<(T) -> Unit>()
|
private val observers = mutableSetOf<(T) -> Unit>()
|
||||||
|
|
||||||
|
val size : Int get() = observers.size
|
||||||
|
|
||||||
operator fun plusAssign(observer: (T) -> Unit) {
|
operator fun plusAssign(observer: (T) -> Unit) {
|
||||||
observers.add(observer)
|
observers.add(observer)
|
||||||
}
|
}
|
||||||
|
|
|
@ -399,6 +399,7 @@ val extractorApis: MutableList<ExtractorApi> = arrayListOf(
|
||||||
Moviehab(),
|
Moviehab(),
|
||||||
MoviehabNet(),
|
MoviehabNet(),
|
||||||
Jeniusplay(),
|
Jeniusplay(),
|
||||||
|
StreamoUpload(),
|
||||||
|
|
||||||
Gdriveplayerapi(),
|
Gdriveplayerapi(),
|
||||||
Gdriveplayerapp(),
|
Gdriveplayerapp(),
|
||||||
|
@ -548,4 +549,4 @@ abstract class ExtractorApi {
|
||||||
open fun getExtractorUrl(id: String): String {
|
open fun getExtractorUrl(id: String): String {
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -300,7 +300,7 @@ class InAppUpdater {
|
||||||
// Forcefully start any delayed installations
|
// Forcefully start any delayed installations
|
||||||
if (ApkInstaller.delayedInstaller?.startInstallation() == true) return@setPositiveButton
|
if (ApkInstaller.delayedInstaller?.startInstallation() == true) return@setPositiveButton
|
||||||
|
|
||||||
showToast(context, R.string.download_started, Toast.LENGTH_LONG)
|
showToast(R.string.download_started, Toast.LENGTH_LONG)
|
||||||
|
|
||||||
// Check if the setting hasn't been changed
|
// Check if the setting hasn't been changed
|
||||||
if (settingsManager.getInt(
|
if (settingsManager.getInt(
|
||||||
|
@ -335,7 +335,6 @@ class InAppUpdater {
|
||||||
if (!downloadUpdate(update.updateURL))
|
if (!downloadUpdate(update.updateURL))
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
showToast(
|
showToast(
|
||||||
context,
|
|
||||||
R.string.download_failed,
|
R.string.download_failed,
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,19 +2,28 @@ package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.*
|
import android.widget.AbsListView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.ListView
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.*
|
import androidx.core.view.isGone
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.marginLeft
|
||||||
|
import androidx.core.view.marginRight
|
||||||
|
import androidx.core.view.marginTop
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.databinding.BottomSelectionDialogBinding
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
|
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||||
import kotlinx.android.synthetic.main.add_account_input.*
|
|
||||||
import kotlinx.android.synthetic.main.add_account_input.text1
|
|
||||||
import kotlinx.android.synthetic.main.bottom_selection_dialog_direct.*
|
|
||||||
|
|
||||||
object SingleSelectionHelper {
|
object SingleSelectionHelper {
|
||||||
fun Activity?.showOptionSelectStringRes(
|
fun Activity?.showOptionSelectStringRes(
|
||||||
|
@ -82,6 +91,7 @@ object SingleSelectionHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity?.showDialog(
|
fun Activity?.showDialog(
|
||||||
|
binding: BottomSelectionDialogBinding,
|
||||||
dialog: Dialog,
|
dialog: Dialog,
|
||||||
items: List<String>,
|
items: List<String>,
|
||||||
selectedIndex: List<Int>,
|
selectedIndex: List<Int>,
|
||||||
|
@ -95,39 +105,39 @@ object SingleSelectionHelper {
|
||||||
if (this == null) return
|
if (this == null) return
|
||||||
|
|
||||||
val realShowApply = showApply || isMultiSelect
|
val realShowApply = showApply || isMultiSelect
|
||||||
val listView = dialog.listview1//.findViewById<ListView>(R.id.listview1)!!
|
val listView = binding.listview1//.findViewById<ListView>(R.id.listview1)!!
|
||||||
val textView = dialog.text1//.findViewById<TextView>(R.id.text1)!!
|
val textView = binding.text1//.findViewById<TextView>(R.id.text1)!!
|
||||||
val applyButton = dialog.apply_btt//.findViewById<TextView>(R.id.apply_btt)
|
val applyButton = binding.applyBtt//.findViewById<TextView>(R.id.apply_btt)
|
||||||
val cancelButton = dialog.cancel_btt//findViewById<TextView>(R.id.cancel_btt)
|
val cancelButton = binding.cancelBtt//findViewById<TextView>(R.id.cancel_btt)
|
||||||
val applyHolder =
|
val applyHolder =
|
||||||
dialog.apply_btt_holder//.findViewById<LinearLayout>(R.id.apply_btt_holder)
|
binding.applyBttHolder//.findViewById<LinearLayout>(R.id.apply_btt_holder)
|
||||||
|
|
||||||
applyHolder?.isVisible = realShowApply
|
applyHolder.isVisible = realShowApply
|
||||||
if (!realShowApply) {
|
if (!realShowApply) {
|
||||||
val params = listView.layoutParams as LinearLayout.LayoutParams
|
val params = listView.layoutParams as LinearLayout.LayoutParams
|
||||||
params.setMargins(listView.marginLeft, listView.marginTop, listView.marginRight, 0)
|
params.setMargins(listView.marginLeft, listView.marginTop, listView.marginRight, 0)
|
||||||
listView.layoutParams = params
|
listView.layoutParams = params
|
||||||
}
|
}
|
||||||
|
|
||||||
textView?.text = name
|
textView.text = name
|
||||||
textView?.isGone = name.isBlank()
|
textView.isGone = name.isBlank()
|
||||||
|
|
||||||
val arrayAdapter = ArrayAdapter<String>(this, itemLayout)
|
val arrayAdapter = ArrayAdapter<String>(this, itemLayout)
|
||||||
arrayAdapter.addAll(items)
|
arrayAdapter.addAll(items)
|
||||||
|
|
||||||
listView?.adapter = arrayAdapter
|
listView.adapter = arrayAdapter
|
||||||
if (isMultiSelect) {
|
if (isMultiSelect) {
|
||||||
listView?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
|
listView.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE
|
||||||
} else {
|
} else {
|
||||||
listView?.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
listView.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
||||||
}
|
}
|
||||||
|
|
||||||
for (select in selectedIndex) {
|
for (select in selectedIndex) {
|
||||||
listView?.setItemChecked(select, true)
|
listView.setItemChecked(select, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedIndex.minOrNull()?.let {
|
selectedIndex.minOrNull()?.let {
|
||||||
listView?.setSelection(it)
|
listView.setSelection(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// var lastSelectedIndex = if(selectedIndex.isNotEmpty()) selectedIndex.first() else -1
|
// var lastSelectedIndex = if(selectedIndex.isNotEmpty()) selectedIndex.first() else -1
|
||||||
|
@ -136,7 +146,7 @@ object SingleSelectionHelper {
|
||||||
dismissCallback.invoke()
|
dismissCallback.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
listView?.setOnItemClickListener { _, _, which, _ ->
|
listView.setOnItemClickListener { _, _, which, _ ->
|
||||||
// lastSelectedIndex = which
|
// lastSelectedIndex = which
|
||||||
if (realShowApply) {
|
if (realShowApply) {
|
||||||
if (!isMultiSelect) {
|
if (!isMultiSelect) {
|
||||||
|
@ -148,7 +158,7 @@ object SingleSelectionHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (realShowApply) {
|
if (realShowApply) {
|
||||||
applyButton?.setOnClickListener {
|
applyButton.setOnClickListener {
|
||||||
val list = ArrayList<Int>()
|
val list = ArrayList<Int>()
|
||||||
for (index in 0 until listView.count) {
|
for (index in 0 until listView.count) {
|
||||||
if (listView.checkedItemPositions[index])
|
if (listView.checkedItemPositions[index])
|
||||||
|
@ -157,7 +167,7 @@ object SingleSelectionHelper {
|
||||||
callback.invoke(list)
|
callback.invoke(list)
|
||||||
dialog.dismissSafe(this)
|
dialog.dismissSafe(this)
|
||||||
}
|
}
|
||||||
cancelButton?.setOnClickListener {
|
cancelButton.setOnClickListener {
|
||||||
dialog.dismissSafe(this)
|
dialog.dismissSafe(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,13 +223,26 @@ object SingleSelectionHelper {
|
||||||
) {
|
) {
|
||||||
if (this == null) return
|
if (this == null) return
|
||||||
|
|
||||||
|
val binding: BottomSelectionDialogBinding = BottomSelectionDialogBinding.inflate(
|
||||||
|
LayoutInflater.from(this)
|
||||||
|
)
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
||||||
.setView(R.layout.bottom_selection_dialog)
|
.setView(binding.root)
|
||||||
|
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
dialog.show()
|
dialog.show()
|
||||||
showDialog(dialog, items, selectedIndex, name, true, true, callback, dismissCallback)
|
showDialog(
|
||||||
|
binding,
|
||||||
|
dialog,
|
||||||
|
items,
|
||||||
|
selectedIndex,
|
||||||
|
name,
|
||||||
|
showApply = true,
|
||||||
|
isMultiSelect = true,
|
||||||
|
callback,
|
||||||
|
dismissCallback
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity?.showDialog(
|
fun Activity?.showDialog(
|
||||||
|
@ -232,13 +255,19 @@ object SingleSelectionHelper {
|
||||||
) {
|
) {
|
||||||
if (this == null) return
|
if (this == null) return
|
||||||
|
|
||||||
|
val binding: BottomSelectionDialogBinding = BottomSelectionDialogBinding.inflate(
|
||||||
|
LayoutInflater.from(this)
|
||||||
|
)
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
AlertDialog.Builder(this, R.style.AlertDialogCustom)
|
||||||
.setView(R.layout.bottom_selection_dialog)
|
.setView(binding.root)
|
||||||
|
|
||||||
val dialog = builder.create()
|
val dialog = builder.create()
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
|
binding,
|
||||||
dialog,
|
dialog,
|
||||||
items,
|
items,
|
||||||
listOf(selectedIndex),
|
listOf(selectedIndex),
|
||||||
|
@ -260,12 +289,18 @@ object SingleSelectionHelper {
|
||||||
callback: (Int) -> Unit,
|
callback: (Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (this == null) return
|
if (this == null) return
|
||||||
|
|
||||||
|
val binding: BottomSelectionDialogBinding = BottomSelectionDialogBinding.inflate(
|
||||||
|
LayoutInflater.from(this)
|
||||||
|
)
|
||||||
|
|
||||||
val builder =
|
val builder =
|
||||||
BottomSheetDialog(this)
|
BottomSheetDialog(this)
|
||||||
builder.setContentView(R.layout.bottom_selection_dialog)
|
builder.setContentView(binding.root)
|
||||||
|
|
||||||
builder.show()
|
builder.show()
|
||||||
showDialog(
|
showDialog(
|
||||||
|
binding,
|
||||||
builder,
|
builder,
|
||||||
items,
|
items,
|
||||||
listOf(selectedIndex),
|
listOf(selectedIndex),
|
||||||
|
@ -285,13 +320,19 @@ object SingleSelectionHelper {
|
||||||
): BottomSheetDialog {
|
): BottomSheetDialog {
|
||||||
val builder =
|
val builder =
|
||||||
BottomSheetDialog(this)
|
BottomSheetDialog(this)
|
||||||
builder.setContentView(R.layout.bottom_selection_dialog_direct)
|
|
||||||
|
|
||||||
|
val binding: BottomSelectionDialogBinding = BottomSelectionDialogBinding.inflate(
|
||||||
|
LayoutInflater.from(this)
|
||||||
|
)
|
||||||
|
|
||||||
|
//builder.setContentView(R.layout.bottom_selection_dialog_direct)
|
||||||
|
builder.setContentView(binding.root)
|
||||||
builder.show()
|
builder.show()
|
||||||
showDialog(
|
showDialog(
|
||||||
|
binding,
|
||||||
builder,
|
builder,
|
||||||
items,
|
items,
|
||||||
listOf(),
|
emptyList(),
|
||||||
name,
|
name,
|
||||||
showApply = false,
|
showApply = false,
|
||||||
isMultiSelect = false,
|
isMultiSelect = false,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue