From f85d32a308d79f4abde121c225d2addc070aaafa Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Fri, 8 Apr 2022 21:38:19 +0200 Subject: [PATCH] autosync --- .../com/lagradost/cloudstream3/MainAPI.kt | 9 ++- .../lagradost/cloudstream3/ParCollections.kt | 4 ++ .../ui/player/AbstractPlayerFragment.kt | 6 +- .../cloudstream3/ui/player/GeneratorPlayer.kt | 49 +++++++++++-- .../cloudstream3/ui/player/IGenerator.kt | 4 +- .../cloudstream3/ui/result/ResultFragment.kt | 20 +++--- .../cloudstream3/ui/result/SyncViewModel.kt | 70 +++++++++++++++---- .../main/res/drawable/baseline_sync_24.xml | 2 +- app/src/main/res/values/strings.xml | 4 ++ app/src/main/res/xml/settings.xml | 8 +++ 10 files changed, 142 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 6d616043..e2ac29c0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -12,7 +12,6 @@ import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.module.kotlin.KotlinModule import com.lagradost.cloudstream3.animeproviders.* import com.lagradost.cloudstream3.metaproviders.CrossTmdbProvider -import com.lagradost.cloudstream3.metaproviders.MultiAnimeProvider import com.lagradost.cloudstream3.movieproviders.* import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.aniListApi import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.malApi @@ -310,7 +309,7 @@ abstract class MainAPI { var overrideData: HashMap? = null } - public fun overrideWithNewData(data: ProvidersInfoJson) { + fun overrideWithNewData(data: ProvidersInfoJson) { this.name = data.name this.mainUrl = data.url } @@ -760,6 +759,10 @@ interface LoadResponse { // TODO add kitsu sync } + fun LoadResponse.addTMDbId(id: String?) { + + } + fun LoadResponse.setDuration(input: String?) { val cleanInput = input?.trim()?.replace(" ", "") ?: return Regex("([0-9]*)h.*?([0-9]*)m").find(cleanInput)?.groupValues?.let { values -> @@ -985,7 +988,7 @@ fun fetchUrls(text: String?): List { return listOf() } val linkRegex = - Regex("""(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*))""") + Regex("""(https?://(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*))""") return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt b/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt index dc5aff1b..2009ace8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt @@ -26,6 +26,10 @@ fun Iterable.pmap( return ArrayList(destination) }*/ +fun Map.apmap(f: suspend (Map.Entry) -> R): List = runBlocking { + map { async { f(it) } }.map { it.await() } +} + fun List.apmap(f: suspend (A) -> B): List = runBlocking { map { async { f(it) } }.map { it.await() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt index 64769cd9..3e53bccd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt @@ -56,7 +56,10 @@ const val SKIP_OP_VIDEO_PERCENTAGE = 50 const val PRELOAD_NEXT_EPISODE_PERCENTAGE = 80 // when the player should mark the episode as watched and resume watching the next -const val NEXT_WATCH_EPISODE_PERCENTAGE = 95 +const val NEXT_WATCH_EPISODE_PERCENTAGE = 90 + +// when the player should sync the progress of "watched", TODO MAKE SETTING +const val UPDATE_SYNC_PROGRESS_PERCENTAGE = 80 abstract class AbstractPlayerFragment( val player: IPlayer = CS3IPlayer() @@ -329,6 +332,7 @@ abstract class AbstractPlayerFragment( SKIP_OP_VIDEO_PERCENTAGE, PRELOAD_NEXT_EPISODE_PERCENTAGE, NEXT_WATCH_EPISODE_PERCENTAGE, + UPDATE_SYNC_PROGRESS_PERCENTAGE, ), subtitlesUpdates = ::subtitlesChanged ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 6119df89..c82d3788 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.player import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -12,6 +13,7 @@ import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.lifecycle.ViewModelProvider +import androidx.preference.PreferenceManager import com.google.android.exoplayer2.util.MimeTypes import com.google.android.material.button.MaterialButton import com.hippo.unifile.UniFile @@ -25,6 +27,7 @@ import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.ResultFragment +import com.lagradost.cloudstream3.ui.result.SyncViewModel import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment import com.lagradost.cloudstream3.utils.* @@ -39,13 +42,18 @@ import kotlinx.coroutines.Job class GeneratorPlayer : FullScreenPlayer() { companion object { private var lastUsedGenerator: IGenerator? = null - fun newInstance(generator: IGenerator): Bundle { + fun newInstance(generator: IGenerator, syncData: HashMap? = null): Bundle { + Log.i(TAG, "newInstance = $syncData") lastUsedGenerator = generator - return Bundle() + return Bundle().apply { + if (syncData != null) + putSerializable("syncData", syncData) + } } } private lateinit var viewModel: PlayerGeneratorViewModel //by activityViewModels() + private lateinit var sync: SyncViewModel private var currentLinks: Set> = setOf() private var currentSubs: Set = setOf() @@ -123,7 +131,11 @@ class GeneratorPlayer : FullScreenPlayer() { if (isNextEpisode) 0L else getPos() }, currentSubs, - (if(sameEpisode) currentSelectedSubtitles else null) ?: getAutoSelectSubtitle(currentSubs, settings = true, downloads = true), + (if (sameEpisode) currentSelectedSubtitles else null) ?: getAutoSelectSubtitle( + currentSubs, + settings = true, + downloads = true + ), ) } } @@ -422,6 +434,18 @@ class GeneratorPlayer : FullScreenPlayer() { var isOpVisible = false when (val meta = currentMeta) { is ResultEpisode -> { + if (percentage >= UPDATE_SYNC_PROGRESS_PERCENTAGE) { + context?.let { ctx -> + val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) + if (settingsManager.getBoolean( + ctx.getString(R.string.episode_sync_enabled_key), + true + ) + ) + sync.modifyMaxEpisode(meta.episode) + } + } + if (meta.tvType.isAnimeOp()) isOpVisible = percentage < SKIP_OP_VIDEO_PERCENTAGE } @@ -429,7 +453,7 @@ class GeneratorPlayer : FullScreenPlayer() { player_skip_op?.isVisible = isOpVisible player_skip_episode?.isVisible = !isOpVisible && viewModel.hasNextEpisode() == true - if (percentage > PRELOAD_NEXT_EPISODE_PERCENTAGE) { + if (percentage >= PRELOAD_NEXT_EPISODE_PERCENTAGE) { viewModel.preLoadNextLinks() } } @@ -554,6 +578,13 @@ class GeneratorPlayer : FullScreenPlayer() { setPlayerDimen(widthHeight) } + private fun unwrapBundle(savedInstanceState: Bundle?) { + Log.i(TAG, "unwrapBundle = $savedInstanceState") + savedInstanceState?.let { bundle -> + sync.addSyncs(bundle.getSerializable("syncData") as? HashMap?) + } + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -564,13 +595,23 @@ class GeneratorPlayer : FullScreenPlayer() { if (context?.isTvSettings() == true) R.layout.fragment_player_tv else R.layout.fragment_player viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java] + sync = ViewModelProvider(this)[SyncViewModel::class.java] + viewModel.attachGenerator(lastUsedGenerator) + unwrapBundle(savedInstanceState) + unwrapBundle(arguments) + return super.onCreateView(inflater, container, savedInstanceState) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + unwrapBundle(savedInstanceState) + unwrapBundle(arguments) + + sync.updateUserData() + preferredAutoSelectSubtitles = SubtitlesFragment.getAutoSelectLanguageISO639_1() if (currentSelectedLink == null) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt index dbc4ff76..b575886b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt @@ -12,8 +12,8 @@ interface IGenerator { fun prev() fun goto(index: Int) - fun getCurrentId(): Int? // this is used to save data or read data about this id - fun getCurrent(offset : Int = 0): Any? // this is used to get metadata about the current playing, can return null + fun getCurrentId(): Int? // this is used to save data or read data about this id + fun getCurrent(offset : Int = 0): Any? // this is used to get metadata about the current playing, can return null /* not safe, must use try catch */ suspend fun generateLinks( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index a65a6dd5..2c6ecaa7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -428,7 +428,8 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio private var currentHeaderName: String? = null private var currentType: TvType? = null private var currentEpisodes: List? = null - var downloadButton: EasyDownloadButton? = null + private var downloadButton: EasyDownloadButton? = null + private var syncdata: Map? = null override fun onCreateView( inflater: LayoutInflater, @@ -645,6 +646,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio } private fun updateUI() { + syncModel.updateUserData() viewModel.reloadEpisodes() } @@ -1089,10 +1091,11 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio ACTION_PLAY_EPISODE_IN_PLAYER -> { viewModel.getGenerator(episodeClick.data) ?.let { generator -> + println("LANUCJ:::: $syncdata") activity?.navigate( R.id.global_to_navigation_player, GeneratorPlayer.newInstance( - generator + generator, syncdata?.let { HashMap(it) } ) ) } @@ -1296,10 +1299,16 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio list.filter { it.isSynced && it.hasAccount }.joinToString { it.name } val newList = list.filter { it.isSynced } + result_mini_sync?.isVisible = newList.isNotEmpty() (result_mini_sync?.adapter as? ImageAdapter?)?.updateList(newList.map { it.icon }) } + observe(syncModel.syncIds) { + println("VALUES::: $it") + syncdata = it + } + var currentSyncProgress = 0 fun setSyncMaxEpisodes(totalEpisodes: Int?) { @@ -1642,12 +1651,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio setActors(d.actors) if (SettingsFragment.accountEnabled) { - var isValid = false - for ((prefix, id) in d.syncData) { - isValid = isValid || syncModel.addSync(prefix, id) - } - - if (isValid) { + if (syncModel.addSyncs(d.syncData)) { syncModel.updateMetaAndUser() syncModel.updateSynced() } else { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt index 862c94fa..90f0746f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SyncViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.lagradost.cloudstream3.apmap import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.SyncApis import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.aniListApi @@ -40,7 +41,10 @@ class SyncViewModel : ViewModel() { val userData: LiveData?> get() = _userDataResponse // prefix, id - private val syncIds = hashMapOf() + private var syncs = mutableMapOf() + private val _syncIds: MutableLiveData> = + MutableLiveData(mutableMapOf()) + val syncIds: LiveData> get() = _syncIds private val _currentSynced: MutableLiveData> = MutableLiveData(getMissing()) @@ -53,7 +57,7 @@ class SyncViewModel : ViewModel() { CurrentSynced( it.name, it.idPrefix, - syncIds.containsKey(it.idPrefix), + syncs.containsKey(it.idPrefix), it.hasAccount(), it.icon, ) @@ -65,19 +69,30 @@ class SyncViewModel : ViewModel() { _currentSynced.postValue(getMissing()) } - fun addSync(idPrefix: String, id : String) : Boolean { - if(syncIds[idPrefix] == id) return false + private fun addSync(idPrefix: String, id: String): Boolean { + if (syncs[idPrefix] == id) return false Log.i(TAG, "addSync $idPrefix = $id") - syncIds[idPrefix] = id + + syncs[idPrefix] = id + _syncIds.postValue(syncs) return true } - fun setMalId(id: String?) : Boolean { - return addSync(malApi.idPrefix,id ?: return false) + fun addSyncs(map: Map?): Boolean { + var isValid = false + + map?.forEach { (prefix, id) -> + isValid = isValid || addSync(prefix, id) + } + return isValid } - fun setAniListId(id: String?) : Boolean { - return addSync(aniListApi.idPrefix,id ?: return false) + fun setMalId(id: String?): Boolean { + return addSync(malApi.idPrefix, id ?: return false) + } + + fun setAniListId(id: String?): Boolean { + return addSync(aniListApi.idPrefix, id ?: return false) } var hasAddedFromUrl: HashSet = hashSetOf() @@ -153,20 +168,45 @@ class SyncViewModel : ViewModel() { Log.i(TAG, "publishUserData") val user = userData.value if (user is Resource.Success) { - for ((prefix, id) in syncIds) { + syncs.forEach { (prefix, id) -> repos.firstOrNull { it.idPrefix == prefix }?.score(id, user.value) } } updateUserData() } - private fun updateUserData() = viewModelScope.launch { + fun modifyMaxEpisode(episodeNum: Int) { + Log.i(TAG, "modifyMaxEpisode = $episodeNum") + modifyData { status -> + status.copy(watchedEpisodes = maxOf(episodeNum, status.watchedEpisodes ?: return@modifyData null)) + } + } + + /// modifies the current sync data, return null if you don't want to change it + private fun modifyData(update: ((SyncAPI.SyncStatus) -> (SyncAPI.SyncStatus?))) = + viewModelScope.launch { + syncs.apmap { (prefix, id) -> + repos.firstOrNull { it.idPrefix == prefix }?.let { repo -> + if (repo.hasAccount()) { + val result = repo.getStatus(id) + if (result is Resource.Success) { + update(result.value)?.let { newData -> + Log.i(TAG, "modifyData ${repo.name} => $newData") + repo.score(id, newData) + } + } + } + } + } + } + + fun updateUserData() = viewModelScope.launch { Log.i(TAG, "updateUserData") _userDataResponse.postValue(Resource.Loading()) var lastError: Resource = Resource.Failure(false, null, null, "No data") - for ((prefix, id) in syncIds) { + syncs.forEach { (prefix, id) -> repos.firstOrNull { it.idPrefix == prefix }?.let { repo -> - if(repo.hasAccount()) { + if (repo.hasAccount()) { val result = repo.getStatus(id) if (result is Resource.Success) { _userDataResponse.postValue(result) @@ -185,9 +225,9 @@ class SyncViewModel : ViewModel() { _metaResponse.postValue(Resource.Loading()) var lastError: Resource = Resource.Failure(false, null, null, "No data") - for ((prefix, id) in syncIds) { + syncs.forEach { (prefix, id) -> repos.firstOrNull { it.idPrefix == prefix }?.let { repo -> - if(repo.hasAccount()) { + if (repo.hasAccount()) { val result = repo.getResult(id) if (result is Resource.Success) { _metaResponse.postValue(result) diff --git a/app/src/main/res/drawable/baseline_sync_24.xml b/app/src/main/res/drawable/baseline_sync_24.xml index c2f773a1..27e28369 100644 --- a/app/src/main/res/drawable/baseline_sync_24.xml +++ b/app/src/main/res/drawable/baseline_sync_24.xml @@ -3,7 +3,7 @@ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" - android:tint="?attr/colorControlNormal"> + android:tint="?attr/white"> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c92e9d9b..b50ec557 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,6 +39,7 @@ backup_key prefer_media_type_key app_theme_key + episode_sync_enabled_key @@ -202,6 +203,9 @@ overlay + Update watch progress + Automatically sync your current episode progress + Restore data from backup Download latest metadata from github If you want access to all providers (even broken ones) turn this off diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index 2a200ec0..3f13c3b0 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -63,6 +63,14 @@ android:title="@string/double_tap_to_pause_settings" android:summary="@string/double_tap_to_pause_settings_des" app:defaultValue="false" /> + + +