forked from recloudstream/cloudstream
Added Mark as watched and fixed clicking episode synopsis
This commit is contained in:
parent
89c5cb8a46
commit
b8248d1053
5 changed files with 100 additions and 8 deletions
|
@ -218,10 +218,18 @@ class EpisodeAdapter(
|
||||||
name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name
|
name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name
|
||||||
episodeText.isSelected = true // is needed for text repeating
|
episodeText.isSelected = true // is needed for text repeating
|
||||||
|
|
||||||
val displayPos = card.getDisplayPosition()
|
if (card.videoWatchState == VideoWatchState.Watched) {
|
||||||
episodeProgress?.max = (card.duration / 1000).toInt()
|
// This cannot be done in getDisplayPosition() as when you have not watched something
|
||||||
episodeProgress?.progress = (displayPos / 1000).toInt()
|
// the duration and position is 0
|
||||||
episodeProgress?.isVisible = displayPos > 0L
|
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
|
episodePoster?.isVisible = episodePoster?.setImage(card.poster) == true
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
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.DataStoreHelper.getViewPos
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
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_RESUME_LATEST = 1
|
||||||
const val START_ACTION_LOAD_EP = 2
|
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(
|
data class ResultEpisode(
|
||||||
val headerName: String,
|
val headerName: String,
|
||||||
val name: String?,
|
val name: String?,
|
||||||
|
@ -124,6 +134,10 @@ data class ResultEpisode(
|
||||||
val isFiller: Boolean?,
|
val isFiller: Boolean?,
|
||||||
val tvType: TvType,
|
val tvType: TvType,
|
||||||
val parentId: Int,
|
val parentId: Int,
|
||||||
|
/**
|
||||||
|
* Conveys if the episode itself is marked as watched
|
||||||
|
**/
|
||||||
|
val videoWatchState: VideoWatchState
|
||||||
)
|
)
|
||||||
|
|
||||||
fun ResultEpisode.getRealPosition(): Long {
|
fun ResultEpisode.getRealPosition(): Long {
|
||||||
|
@ -160,6 +174,7 @@ fun buildResultEpisode(
|
||||||
parentId: Int,
|
parentId: Int,
|
||||||
): ResultEpisode {
|
): ResultEpisode {
|
||||||
val posDur = getViewPos(id)
|
val posDur = getViewPos(id)
|
||||||
|
val videoWatchState = getVideoWatchState(id) ?: VideoWatchState.None
|
||||||
return ResultEpisode(
|
return ResultEpisode(
|
||||||
headerName,
|
headerName,
|
||||||
name,
|
name,
|
||||||
|
@ -178,6 +193,7 @@ fun buildResultEpisode(
|
||||||
isFiller,
|
isFiller,
|
||||||
tvType,
|
tvType,
|
||||||
parentId,
|
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 ->
|
observe(viewModel.watchStatus) { watchType ->
|
||||||
result_bookmark_button?.text = getString(watchType.stringRes)
|
result_bookmark_button?.text = getString(watchType.stringRes)
|
||||||
result_bookmark_fab?.text = getString(watchType.stringRes)
|
result_bookmark_fab?.text = getString(watchType.stringRes)
|
||||||
|
|
|
@ -406,6 +406,9 @@ class ResultViewModel2 : ViewModel() {
|
||||||
MutableLiveData(Some.None)
|
MutableLiveData(Some.None)
|
||||||
val resumeWatching: LiveData<Some<ResumeWatchingStatus>> = _resumeWatching
|
val resumeWatching: LiveData<Some<ResumeWatchingStatus>> = _resumeWatching
|
||||||
|
|
||||||
|
private val _episodeSynopsis: MutableLiveData<String?> = MutableLiveData(null)
|
||||||
|
val episodeSynopsis: LiveData<String?> = _episodeSynopsis
|
||||||
|
|
||||||
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
|
||||||
|
@ -1113,6 +1116,10 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun releaseEpisodeSynopsis() {
|
||||||
|
_episodeSynopsis.postValue(null)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun handleEpisodeClickEvent(activity: Activity?, click: EpisodeClickEvent) {
|
private suspend fun handleEpisodeClickEvent(activity: Activity?, click: EpisodeClickEvent) {
|
||||||
when (click.action) {
|
when (click.action) {
|
||||||
ACTION_SHOW_OPTIONS -> {
|
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_mirror) to ACTION_DOWNLOAD_MIRROR,
|
||||||
txt(R.string.episode_action_download_subtitle) to ACTION_DOWNLOAD_EPISODE_SUBTITLE_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.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(
|
postPopup(
|
||||||
txt(
|
txt(
|
||||||
activity?.getNameFull(
|
activity?.getNameFull(
|
||||||
|
@ -1182,6 +1199,10 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ACTION_SHOW_DESCRIPTION -> {
|
||||||
|
_episodeSynopsis.postValue(click.data.description)
|
||||||
|
}
|
||||||
|
|
||||||
/* not implemented, not used
|
/* not implemented, not used
|
||||||
ACTION_DOWNLOAD_EPISODE_SUBTITLE -> {
|
ACTION_DOWNLOAD_EPISODE_SUBTITLE -> {
|
||||||
loadLinks(click.data, isVisible = false, isCasting = false) { links ->
|
loadLinks(click.data, isVisible = false, isCasting = false) { links ->
|
||||||
|
@ -1378,8 +1399,17 @@ class ResultViewModel2 : ViewModel() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
ACTION_MARK_AS_WATCHED -> {
|
ACTION_MARK_AS_WATCHED -> {
|
||||||
// TODO FIX
|
val isWatched =
|
||||||
// DataStoreHelper.setViewPos(click.data.id, 1, 1)
|
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)
|
val end = minOf(list.size, start + length)
|
||||||
list.subList(start, end).map {
|
list.subList(start, end).map {
|
||||||
val posDur = getViewPos(it.id)
|
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()
|
?: emptyList()
|
||||||
|
|
|
@ -11,8 +11,10 @@ import com.lagradost.cloudstream3.SearchQuality
|
||||||
import com.lagradost.cloudstream3.SearchResponse
|
import com.lagradost.cloudstream3.SearchResponse
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
import com.lagradost.cloudstream3.ui.WatchType
|
import com.lagradost.cloudstream3.ui.WatchType
|
||||||
|
import com.lagradost.cloudstream3.ui.result.VideoWatchState
|
||||||
|
|
||||||
const val VIDEO_POS_DUR = "video_pos_dur"
|
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 = "result_watch_state"
|
||||||
const val RESULT_WATCH_STATE_DATA = "result_watch_state_data"
|
const val RESULT_WATCH_STATE_DATA = "result_watch_state_data"
|
||||||
const val RESULT_RESUME_WATCHING = "result_resume_watching_2" // changed due to id changes
|
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)
|
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? {
|
fun getDub(id: Int): DubStatus? {
|
||||||
return DubStatus.values()
|
return DubStatus.values()
|
||||||
.getOrNull(getKey("$currentAccount/$RESULT_DUB", id.toString(), -1) ?: -1)
|
.getOrNull(getKey("$currentAccount/$RESULT_DUB", id.toString(), -1) ?: -1)
|
||||||
|
|
|
@ -605,6 +605,7 @@
|
||||||
<string name="enable_skip_op_from_database_des">Show skip popups for opening/ending</string>
|
<string name="enable_skip_op_from_database_des">Show skip popups for opening/ending</string>
|
||||||
<string name="clipboard_too_large">Too much text. Unable to save to clipboard.</string>
|
<string name="clipboard_too_large">Too much text. Unable to save to clipboard.</string>
|
||||||
<string name="action_mark_as_watched">Mark as watched</string>
|
<string name="action_mark_as_watched">Mark as watched</string>
|
||||||
|
<string name="action_remove_from_watched">Remove from watched</string>
|
||||||
<string name="confirm_exit_dialog">Are you sure you want to exit\?</string>
|
<string name="confirm_exit_dialog">Are you sure you want to exit\?</string>
|
||||||
<string name="yes">Yes</string>
|
<string name="yes">Yes</string>
|
||||||
<string name="no">No</string>
|
<string name="no">No</string>
|
||||||
|
|
Loading…
Reference in a new issue