mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
download button + resume watching
This commit is contained in:
parent
d763b4a7aa
commit
2c10df6231
4 changed files with 158 additions and 133 deletions
|
@ -40,22 +40,24 @@ import com.lagradost.cloudstream3.mvvm.*
|
|||
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
|
||||
import com.lagradost.cloudstream3.ui.WatchType
|
||||
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
|
||||
import com.lagradost.cloudstream3.ui.download.EasyDownloadButton
|
||||
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
|
||||
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
|
||||
import com.lagradost.cloudstream3.ui.search.SearchAdapter
|
||||
import com.lagradost.cloudstream3.ui.search.SearchHelper
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioWork
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||
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.fixPaddingStatusbar
|
||||
|
@ -225,10 +227,11 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
return inflater.inflate(R.layout.fragment_result_swipe, container, false)
|
||||
}
|
||||
|
||||
private var downloadButton: EasyDownloadButton? = null
|
||||
override fun onDestroyView() {
|
||||
updateUIListener = null
|
||||
(result_episodes?.adapter as EpisodeAdapter?)?.killAdapter()
|
||||
//downloadButton?.dispose() //TODO READD
|
||||
downloadButton?.dispose()
|
||||
//somehow this still leaks and I dont know why????
|
||||
// todo look at https://github.com/discord/OverlappingPanels/blob/70b4a7cf43c6771873b1e091029d332896d41a1a/sample_app/src/main/java/com/discord/sampleapp/MainActivity.kt
|
||||
PanelsChildGestureRegionObserver.Provider.get().removeGestureRegionsUpdateListener(this)
|
||||
|
@ -741,128 +744,53 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
result_overlapping_panels?.setStartPanelLockState(if (closed) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
|
||||
}
|
||||
|
||||
/*
|
||||
observe(viewModel.episodes) { episodeList ->
|
||||
lateFixDownloadButton(episodeList.size <= 1) // movies can have multible parts but still be *movies* this will fix this
|
||||
var isSeriesVisible = false
|
||||
var isProgressVisible = false
|
||||
DataStoreHelper.getLastWatched(currentId)?.let { resume ->
|
||||
if (currentIsMovie == false) {
|
||||
isSeriesVisible = true
|
||||
observe(viewModel.resumeWatching) { resume ->
|
||||
when (resume) {
|
||||
is Some.Success -> {
|
||||
result_resume_parent?.isVisible = true
|
||||
val value = resume.value
|
||||
value.progress?.let { progress ->
|
||||
//TODO FIX
|
||||
result_resume_series_title?.apply {
|
||||
isVisible = !value.isMovie
|
||||
text =
|
||||
if (value.isMovie) null else activity?.getNameFull(
|
||||
value.result.name,
|
||||
value.result.episode,
|
||||
value.result.season
|
||||
)
|
||||
}
|
||||
result_resume_series_progress_text.setText(progress.progressLeft)
|
||||
result_resume_series_progress?.apply {
|
||||
isVisible = true
|
||||
this.max = progress.maxProgress
|
||||
this.progress = progress.progress
|
||||
}
|
||||
result_resume_progress_holder?.isVisible = true
|
||||
} ?: run {
|
||||
result_resume_progress_holder?.isVisible = false
|
||||
result_resume_series_progress?.isVisible = false
|
||||
result_resume_series_title?.isVisible = false
|
||||
result_resume_series_progress_text?.isVisible = false
|
||||
}
|
||||
|
||||
result_resume_series_button?.isVisible = !value.isMovie
|
||||
|
||||
result_resume_series_button?.setOnClickListener {
|
||||
episodeList.firstOrNull { it.id == resume.episodeId }?.let {
|
||||
handleAction(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, it))
|
||||
}
|
||||
}
|
||||
|
||||
result_resume_series_title?.text =
|
||||
if (resume.season == null)
|
||||
"${getString(R.string.episode)} ${resume.episode}"
|
||||
else
|
||||
" \"${getString(R.string.season_short)}${resume.season}:${getString(R.string.episode_short)}${resume.episode}\""
|
||||
}
|
||||
|
||||
getViewPos(resume.episodeId)?.let { viewPos ->
|
||||
if (viewPos.position > 30_000L || currentIsMovie == false) { // first 30s will not show for movies
|
||||
result_resume_series_progress?.apply {
|
||||
max = (viewPos.duration / 1000).toInt()
|
||||
progress = (viewPos.position / 1000).toInt()
|
||||
}
|
||||
result_resume_series_progress_text?.text =
|
||||
getString(R.string.resume_time_left).format((viewPos.duration - viewPos.position) / (60_000))
|
||||
isProgressVisible = true
|
||||
} else {
|
||||
isProgressVisible = false
|
||||
isSeriesVisible = false
|
||||
}
|
||||
} ?: run {
|
||||
isProgressVisible = false
|
||||
isSeriesVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
result_series_parent?.isVisible = isSeriesVisible
|
||||
if (isSeriesVisible && activity?.currentFocus?.id == R.id.result_back && context?.isTrueTvSettings() == true) {
|
||||
result_resume_series_button?.requestFocus()
|
||||
}
|
||||
|
||||
if (isSeriesVisible) {
|
||||
val down = when {
|
||||
result_season_button?.isVisible == true -> result_season_button
|
||||
result_episode_select?.isVisible == true -> result_episode_select
|
||||
result_dub_select?.isVisible == true -> result_dub_select
|
||||
else -> null
|
||||
}
|
||||
setFocusUpAndDown(result_resume_series_button, down)
|
||||
setFocusUpAndDown(result_bookmark_button, result_resume_series_button)
|
||||
}
|
||||
|
||||
result_resume_progress_holder?.isVisible = isProgressVisible
|
||||
context?.getString(
|
||||
when {
|
||||
currentType?.isLiveStream() == true -> R.string.play_livestream_button
|
||||
isProgressVisible -> R.string.resume
|
||||
else -> R.string.play_movie_button
|
||||
}
|
||||
)?.let {
|
||||
result_play_movie?.text = it
|
||||
}
|
||||
//println("startAction = $startAction")
|
||||
|
||||
when (startAction) {
|
||||
START_ACTION_RESUME_LATEST -> {
|
||||
for (ep in episodeList) {
|
||||
//println("WATCH STATUS::: S${ep.season} E ${ep.episode} - ${ep.getWatchProgress()}")
|
||||
if (ep.getWatchProgress() > 0.90f) { // watched too much
|
||||
continue
|
||||
}
|
||||
handleAction(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, ep))
|
||||
break
|
||||
}
|
||||
}
|
||||
START_ACTION_LOAD_EP -> {
|
||||
if (episodeList.size == 1) {
|
||||
handleAction(
|
||||
viewModel.handleAction(
|
||||
activity,
|
||||
EpisodeClickEvent(
|
||||
ACTION_PLAY_EPISODE_IN_PLAYER,
|
||||
episodeList.first()
|
||||
ACTION_PLAY_EPISODE_IN_PLAYER, value.result
|
||||
)
|
||||
)
|
||||
} else {
|
||||
var found = false
|
||||
for (ep in episodeList) {
|
||||
if (ep.id == startValue) { // watched too much
|
||||
//println("WATCH STATUS::: START_ACTION_LOAD_EP S${ep.season} E ${ep.episode} - ${ep.getWatchProgress()}")
|
||||
handleAction(EpisodeClickEvent(ACTION_PLAY_EPISODE_IN_PLAYER, ep))
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
for (ep in episodeList) {
|
||||
if (ep.episode == resumeEpisode && ep.season == resumeSeason) {
|
||||
//println("WATCH STATUS::: START_ACTION_LOAD_EP S${ep.season} E ${ep.episode} - ${ep.getWatchProgress()}")
|
||||
handleAction(
|
||||
EpisodeClickEvent(
|
||||
ACTION_PLAY_EPISODE_IN_PLAYER,
|
||||
ep
|
||||
)
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else -> Unit
|
||||
is Some.None -> {
|
||||
result_resume_parent?.isVisible = false
|
||||
}
|
||||
}
|
||||
arguments?.remove("startValue")
|
||||
arguments?.remove("startAction")
|
||||
startAction = null
|
||||
startValue = null
|
||||
}
|
||||
*/
|
||||
|
||||
observe(viewModel.episodes) { episodes ->
|
||||
when (episodes) {
|
||||
is ResourceSome.None -> {
|
||||
|
@ -886,7 +814,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
|
||||
// If the season button is visible the result season button will be next focus down
|
||||
if (result_season_button?.isVisible == true)
|
||||
if (result_series_parent?.isVisible == true)
|
||||
if (result_resume_parent?.isVisible == true)
|
||||
setFocusUpAndDown(result_resume_series_button, result_season_button)
|
||||
else
|
||||
setFocusUpAndDown(result_bookmark_button, result_season_button)
|
||||
|
@ -897,7 +825,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
|
||||
if (result_dub_select?.isVisible == true)
|
||||
if (result_season_button?.isVisible != true && result_episode_select?.isVisible != true) {
|
||||
if (result_series_parent?.isVisible == true)
|
||||
if (result_resume_parent?.isVisible == true)
|
||||
setFocusUpAndDown(result_resume_series_button, result_dub_select)
|
||||
else
|
||||
setFocusUpAndDown(result_bookmark_button, result_dub_select)
|
||||
|
@ -973,7 +901,7 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
// If Season button is invisible then the bookmark button next focus is episode select
|
||||
if (result_episode_select?.isVisible == true)
|
||||
if (result_season_button?.isVisible != true) {
|
||||
if (result_series_parent?.isVisible == true)
|
||||
if (result_resume_parent?.isVisible == true)
|
||||
setFocusUpAndDown(result_resume_series_button, result_episode_select)
|
||||
else
|
||||
setFocusUpAndDown(result_bookmark_button, result_episode_select)
|
||||
|
@ -1071,11 +999,60 @@ class ResultFragment : ResultTrailerPlayer() {
|
|||
)
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
|
||||
main {
|
||||
val file =
|
||||
ioWork {
|
||||
context?.let {
|
||||
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
|
||||
it,
|
||||
ep.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
downloadButton?.dispose()
|
||||
downloadButton = EasyDownloadButton()
|
||||
downloadButton?.setUpMoreButton(
|
||||
file?.fileLength,
|
||||
file?.totalBytes,
|
||||
result_movie_progress_downloaded,
|
||||
result_movie_download_icon,
|
||||
result_movie_download_text,
|
||||
result_movie_download_text_precentage,
|
||||
result_download_movie,
|
||||
true,
|
||||
VideoDownloadHelper.DownloadEpisodeCached(
|
||||
ep.name,
|
||||
ep.poster,
|
||||
0,
|
||||
null,
|
||||
ep.id,
|
||||
ep.id,
|
||||
null,
|
||||
null,
|
||||
System.currentTimeMillis(),
|
||||
)
|
||||
) { click ->
|
||||
//when(click.action) {
|
||||
// DOWNLOAD_ACTION_LONG_CLICK -> {
|
||||
// viewModel.handleAction(
|
||||
// activity,
|
||||
// EpisodeClickEvent(ACTION_DOWNLOAD_MIRROR, ep)
|
||||
// )
|
||||
// }
|
||||
//
|
||||
//}
|
||||
handleDownloadClick(activity, click)
|
||||
// handleDownloadClick(activity,)
|
||||
}
|
||||
result_movie_progress_downloaded_holder?.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
result_movie_progress_downloaded_holder?.isVisible = false
|
||||
result_play_movie?.isVisible = false
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ import com.lagradost.cloudstream3.utils.CastHelper.startCast
|
|||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
||||
|
@ -222,6 +223,18 @@ data class LinkProgress(
|
|||
val subsLoaded: Int,
|
||||
)
|
||||
|
||||
data class ResumeProgress(
|
||||
val progress: Int,
|
||||
val maxProgress: Int,
|
||||
val progressLeft: UiText,
|
||||
)
|
||||
|
||||
data class ResumeWatchingStatus(
|
||||
val progress: ResumeProgress?,
|
||||
val isMovie: Boolean,
|
||||
val result: ResultEpisode,
|
||||
)
|
||||
|
||||
data class LinkLoadingResult(
|
||||
val links: List<ExtractorLink>,
|
||||
val subs: List<SubtitleData>,
|
||||
|
@ -352,6 +365,10 @@ class ResultViewModel2 : ViewModel() {
|
|||
private val _loadedLinks: MutableLiveData<Some<LinkProgress>> = MutableLiveData(Some.None)
|
||||
val loadedLinks: LiveData<Some<LinkProgress>> = _loadedLinks
|
||||
|
||||
private val _resumeWatching: MutableLiveData<Some<ResumeWatchingStatus>> =
|
||||
MutableLiveData(Some.None)
|
||||
val resumeWatching: LiveData<Some<ResumeWatchingStatus>> = _resumeWatching
|
||||
|
||||
companion object {
|
||||
const val TAG = "RVM2"
|
||||
private const val EPISODE_RANGE_SIZE = 50
|
||||
|
@ -646,7 +663,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
|
||||
if (currentLinks.isEmpty()) {
|
||||
Coroutines.main {
|
||||
CommonActivity.showToast(
|
||||
showToast(
|
||||
activity,
|
||||
R.string.no_links_found_toast,
|
||||
Toast.LENGTH_SHORT
|
||||
|
@ -655,7 +672,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
return@ioSafe
|
||||
} else {
|
||||
Coroutines.main {
|
||||
CommonActivity.showToast(
|
||||
showToast(
|
||||
activity,
|
||||
R.string.download_started,
|
||||
Toast.LENGTH_SHORT
|
||||
|
@ -950,7 +967,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
act.startActivityForResult(vlcIntent, VLC_REQUEST_CODE)
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
CommonActivity.showToast(act, e.toString(), Toast.LENGTH_LONG)
|
||||
showToast(act, e.toString(), Toast.LENGTH_LONG)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1043,7 +1060,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
response.type
|
||||
)
|
||||
)
|
||||
CommonActivity.showToast(
|
||||
showToast(
|
||||
activity,
|
||||
R.string.download_started,
|
||||
Toast.LENGTH_SHORT
|
||||
|
@ -1051,7 +1068,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
}
|
||||
}
|
||||
ACTION_SHOW_TOAST -> {
|
||||
CommonActivity.showToast(activity, R.string.play_episode_toast, Toast.LENGTH_SHORT)
|
||||
showToast(activity, R.string.play_episode_toast, Toast.LENGTH_SHORT)
|
||||
}
|
||||
ACTION_DOWNLOAD_EPISODE -> {
|
||||
val response = currentResponse ?: return
|
||||
|
@ -1087,7 +1104,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
listOf(result.links[index]),
|
||||
result.subs,
|
||||
)
|
||||
CommonActivity.showToast(
|
||||
showToast(
|
||||
activity,
|
||||
R.string.download_started,
|
||||
Toast.LENGTH_SHORT
|
||||
|
@ -1139,7 +1156,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
val link = result.links[index]
|
||||
val clip = ClipData.newPlainText(link.name, link.url)
|
||||
serviceClipboard.setPrimaryClip(clip)
|
||||
CommonActivity.showToast(act, R.string.copy_link_toast, Toast.LENGTH_SHORT)
|
||||
showToast(act, R.string.copy_link_toast, Toast.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
ACTION_CHROME_CAST_EPISODE -> {
|
||||
|
@ -1304,7 +1321,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
|
||||
private fun getMovie(): ResultEpisode? {
|
||||
return currentEpisodes.entries.firstOrNull()?.value?.firstOrNull()?.let { ep ->
|
||||
val posDur = DataStoreHelper.getViewPos(ep.id)
|
||||
val posDur = getViewPos(ep.id)
|
||||
ep.copy(position = posDur?.position ?: 0, duration = posDur?.duration ?: 0)
|
||||
}
|
||||
}
|
||||
|
@ -1318,7 +1335,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
val start = minOf(list.size, startIndex)
|
||||
val end = minOf(list.size, start + length)
|
||||
list.subList(start, end).map {
|
||||
val posDur = DataStoreHelper.getViewPos(it.id)
|
||||
val posDur = getViewPos(it.id)
|
||||
it.copy(position = posDur?.position ?: 0, duration = posDur?.duration ?: 0)
|
||||
}
|
||||
}
|
||||
|
@ -1369,6 +1386,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
)
|
||||
_movie.postValue(ResourceSome.None)
|
||||
}
|
||||
postResume()
|
||||
}
|
||||
|
||||
private fun postEpisodeRange(indexer: EpisodeIndexer?, range: EpisodeRange?) {
|
||||
|
@ -1667,8 +1685,38 @@ class ResultViewModel2 : ViewModel() {
|
|||
} ?: ranger?.lastOrNull()
|
||||
|
||||
postEpisodeRange(min, range)
|
||||
postResume()
|
||||
}
|
||||
|
||||
fun postResume() {
|
||||
_resumeWatching.postValue(some(resume()))
|
||||
}
|
||||
|
||||
private fun resume(): ResumeWatchingStatus? {
|
||||
val correctId = currentId ?: return null
|
||||
val resume = DataStoreHelper.getLastWatched(correctId)
|
||||
val resumeParentId = resume?.parentId
|
||||
if (resumeParentId != correctId) return null // is null or smth went wrong with getLastWatched
|
||||
val resumeId = resume.episodeId ?: return null// invalid episode id
|
||||
val response = currentResponse ?: return null
|
||||
// kinda ugly ik
|
||||
val episode =
|
||||
currentEpisodes.values.flatten().firstOrNull { it.id == resumeId } ?: return null
|
||||
|
||||
val isMovie = response.isMovie()
|
||||
|
||||
val progress = getViewPos(resume.episodeId)?.let { viewPos ->
|
||||
ResumeProgress(
|
||||
progress = (viewPos.position / 1000).toInt(),
|
||||
maxProgress = (viewPos.duration / 1000).toInt(),
|
||||
txt(R.string.resume_time_left, (viewPos.duration - viewPos.position) / (60_000))
|
||||
)
|
||||
}
|
||||
|
||||
return ResumeWatchingStatus(progress = progress, isMovie = isMovie, result = episode)
|
||||
}
|
||||
|
||||
|
||||
// this instantly updates the metadata on the page
|
||||
private fun postPage(loadResponse: LoadResponse, apiRepository: APIRepository) {
|
||||
_recommendations.postValue(loadResponse.recommendations ?: emptyList())
|
||||
|
|
|
@ -154,10 +154,10 @@ fun TextView?.setTextHtml(text: UiText?) {
|
|||
}
|
||||
}
|
||||
|
||||
fun TextView?.setTextHtml(text: Some<UiText>) {
|
||||
fun TextView?.setTextHtml(text: Some<UiText>?) {
|
||||
setTextHtml(if(text is Some.Success) text.value else null)
|
||||
}
|
||||
|
||||
fun TextView?.setText(text: Some<UiText>) {
|
||||
fun TextView?.setText(text: Some<UiText>?) {
|
||||
setText(if(text is Some.Success) text.value else null)
|
||||
}
|
|
@ -678,7 +678,7 @@
|
|||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/result_series_parent"
|
||||
android:id="@+id/result_resume_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
|
|
Loading…
Reference in a new issue