download button + resume watching

This commit is contained in:
LagradOst 2022-08-03 19:27:49 +02:00
parent d763b4a7aa
commit 2c10df6231
4 changed files with 158 additions and 133 deletions

View file

@ -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
}
}
}

View file

@ -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())

View file

@ -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)
}

View file

@ -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"