diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index e5b839a8..0932b001 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -218,10 +218,18 @@ class EpisodeAdapter( name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name episodeText.isSelected = true // is needed for text repeating - val displayPos = card.getDisplayPosition() - episodeProgress?.max = (card.duration / 1000).toInt() - episodeProgress?.progress = (displayPos / 1000).toInt() - episodeProgress?.isVisible = displayPos > 0L + 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 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 6e7e341f..68a57b7f 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 @@ -49,6 +49,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.openBrowser import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe import com.lagradost.cloudstream3.utils.Coroutines.main +import com.lagradost.cloudstream3.utils.DataStoreHelper.getVideoWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute @@ -106,6 +107,15 @@ import kotlinx.coroutines.runBlocking const val START_ACTION_RESUME_LATEST = 1 const val START_ACTION_LOAD_EP = 2 +/** + * Future proofed way to mark episodes as watched + **/ +enum class VideoWatchState { + /** Default value when no key is set */ + None, + Watched +} + data class ResultEpisode( val headerName: String, val name: String?, @@ -124,6 +134,10 @@ data class ResultEpisode( val isFiller: Boolean?, val tvType: TvType, val parentId: Int, + /** + * Conveys if the episode itself is marked as watched + **/ + val videoWatchState: VideoWatchState ) fun ResultEpisode.getRealPosition(): Long { @@ -160,6 +174,7 @@ fun buildResultEpisode( parentId: Int, ): ResultEpisode { val posDur = getViewPos(id) + val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None return ResultEpisode( headerName, name, @@ -178,6 +193,7 @@ fun buildResultEpisode( isFiller, tvType, parentId, + videoWatchState ) } @@ -559,6 +575,19 @@ open class ResultFragment : ResultTrailerPlayer() { ) + observe(viewModel.episodeSynopsis) { description -> + view.context?.let { ctx -> + val builder: AlertDialog.Builder = + AlertDialog.Builder(ctx, R.style.AlertDialogCustom) + builder.setMessage(description.html()) + .setTitle(R.string.synopsis) + .setOnDismissListener { + viewModel.releaseEpisodeSynopsis() + } + .show() + } + } + observe(viewModel.watchStatus) { watchType -> result_bookmark_button?.text = getString(watchType.stringRes) result_bookmark_fab?.text = getString(watchType.stringRes) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 994d4759..4bc7bc48 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -406,6 +406,9 @@ class ResultViewModel2 : ViewModel() { MutableLiveData(Some.None) val resumeWatching: LiveData> = _resumeWatching + private val _episodeSynopsis: MutableLiveData = MutableLiveData(null) + val episodeSynopsis: LiveData = _episodeSynopsis + companion object { const val TAG = "RVM2" private const val EPISODE_RANGE_SIZE = 20 @@ -1113,6 +1116,10 @@ class ResultViewModel2 : ViewModel() { ) ) + fun releaseEpisodeSynopsis() { + _episodeSynopsis.postValue(null) + } + private suspend fun handleEpisodeClickEvent(activity: Activity?, click: EpisodeClickEvent) { when (click.action) { ACTION_SHOW_OPTIONS -> { @@ -1146,10 +1153,20 @@ class ResultViewModel2 : ViewModel() { txt(R.string.episode_action_download_mirror) to ACTION_DOWNLOAD_MIRROR, txt(R.string.episode_action_download_subtitle) to ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR, txt(R.string.episode_action_reload_links) to ACTION_RELOAD_EPISODE, -// txt(R.string.action_mark_as_watched) to ACTION_MARK_AS_WATCHED, ) ) + // Do not add mark as watched on movies + if (!listOf(TvType.Movie, TvType.AnimeMovie).contains(click.data.tvType)) { + val isWatched = + DataStoreHelper.getVideoWatchState(click.data.id) == VideoWatchState.Watched + + val watchedText = if (isWatched) R.string.action_remove_from_watched + else R.string.action_mark_as_watched + + options.add(txt(watchedText) to ACTION_MARK_AS_WATCHED) + } + postPopup( txt( activity?.getNameFull( @@ -1182,6 +1199,10 @@ class ResultViewModel2 : ViewModel() { } } } + ACTION_SHOW_DESCRIPTION -> { + _episodeSynopsis.postValue(click.data.description) + } + /* not implemented, not used ACTION_DOWNLOAD_EPISODE_SUBTITLE -> { loadLinks(click.data, isVisible = false, isCasting = false) { links -> @@ -1378,8 +1399,17 @@ class ResultViewModel2 : ViewModel() { ) } ACTION_MARK_AS_WATCHED -> { - // TODO FIX -// DataStoreHelper.setViewPos(click.data.id, 1, 1) + val isWatched = + DataStoreHelper.getVideoWatchState(click.data.id) == VideoWatchState.Watched + + if (isWatched) { + DataStoreHelper.setVideoWatchState(click.data.id, VideoWatchState.None) + } else { + DataStoreHelper.setVideoWatchState(click.data.id, VideoWatchState.Watched) + } + + // Kinda dirty to reload all episodes :( + reloadEpisodes() } } } @@ -1529,7 +1559,13 @@ class ResultViewModel2 : ViewModel() { val end = minOf(list.size, start + length) list.subList(start, end).map { val posDur = getViewPos(it.id) - it.copy(position = posDur?.position ?: 0, duration = posDur?.duration ?: 0) + val watchState = + DataStoreHelper.getVideoWatchState(it.id) ?: VideoWatchState.None + it.copy( + position = posDur?.position ?: 0, + duration = posDur?.duration ?: 0, + videoWatchState = watchState + ) } } ?: emptyList() diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 46c29e3f..9174c481 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -11,8 +11,10 @@ import com.lagradost.cloudstream3.SearchQuality import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.ui.WatchType +import com.lagradost.cloudstream3.ui.result.VideoWatchState const val VIDEO_POS_DUR = "video_pos_dur" +const val VIDEO_WATCH_STATE = "video_watch_state" const val RESULT_WATCH_STATE = "result_watch_state" const val RESULT_WATCH_STATE_DATA = "result_watch_state_data" const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes @@ -193,6 +195,22 @@ object DataStoreHelper { return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null) } + fun getVideoWatchState(id: Int?): VideoWatchState? { + if (id == null) return null + return getKey("$currentAccount/$VIDEO_WATCH_STATE", id.toString(), null) + } + + fun setVideoWatchState(id: Int?, watchState: VideoWatchState) { + if (id == null) return + + // None == No key + if (watchState == VideoWatchState.None) { + removeKey("$currentAccount/$VIDEO_WATCH_STATE", id.toString()) + } else { + setKey("$currentAccount/$VIDEO_WATCH_STATE", id.toString(), watchState) + } + } + fun getDub(id: Int): DubStatus? { return DubStatus.values() .getOrNull(getKey("$currentAccount/$RESULT_DUB", id.toString(), -1) ?: -1) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e30abea3..6c10d865 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -605,6 +605,7 @@ Show skip popups for opening/ending Too much text. Unable to save to clipboard. Mark as watched + Remove from watched Are you sure you want to exit\? Yes No