Skip to content

Commit

Permalink
在条目详情页增加"选集播放", 调整追番状态按钮
Browse files Browse the repository at this point in the history
  • Loading branch information
Him188 committed May 25, 2024
1 parent 117a11f commit 509131d
Show file tree
Hide file tree
Showing 15 changed files with 391 additions and 180 deletions.
2 changes: 1 addition & 1 deletion app/shared/data/common/data/models/UISettings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import me.him188.ani.app.ui.collection.EpisodeProgressTheme
import me.him188.ani.app.ui.collection.progress.EpisodeProgressTheme

@Serializable
@Immutable
Expand Down
14 changes: 7 additions & 7 deletions app/shared/data/common/data/subject/SubjectManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import me.him188.ani.app.tools.caching.mutate
import me.him188.ani.app.tools.caching.removeFirstOrNull
import me.him188.ani.app.tools.caching.setEach
import me.him188.ani.app.ui.collection.ContinueWatchingStatus
import me.him188.ani.app.ui.collection.EpisodeProgressItem
import me.him188.ani.app.ui.collection.progress.EpisodeProgressItem
import me.him188.ani.datasources.api.paging.map
import me.him188.ani.datasources.api.topic.UnifiedCollectionType
import me.him188.ani.datasources.bangumi.processing.airSeason
Expand Down Expand Up @@ -289,6 +289,12 @@ class SubjectManagerImpl(
episodeId: Int,
collectionType: UnifiedCollectionType
) {
episodeRepository.setEpisodeCollection(
subjectId,
listOf(episodeId),
collectionType.toEpisodeCollectionType(),
)

val cache = findSubjectCacheById(subjectId) ?: return

cache.mutate {
Expand All @@ -298,12 +304,6 @@ class SubjectManagerImpl(
})
}
}

episodeRepository.setEpisodeCollection(
subjectId,
listOf(episodeId),
collectionType.toEpisodeCollectionType(),
)
}

private suspend fun UserSubjectCollection.convertToItem() = coroutineScope {
Expand Down
33 changes: 33 additions & 0 deletions app/shared/foundation/common/ui/foundation/HasBackgroundScope.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@ package me.him188.ani.app.ui.foundation

import androidx.annotation.UiThread
import androidx.annotation.WorkerThread
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisallowComposableCalls
import androidx.compose.runtime.FloatState
import androidx.compose.runtime.IntState
import androidx.compose.runtime.MutableIntState
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
Expand Down Expand Up @@ -334,13 +339,41 @@ fun BackgroundScope(
parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
): HasBackgroundScope = SimpleBackgroundScope(parentCoroutineContext)

/**
* @param coroutineContext 变化不会反应到返回的 [HasBackgroundScope].
*/
@Composable
inline fun rememberBackgroundScope(
crossinline coroutineContext: @DisallowComposableCalls () -> CoroutineContext = { EmptyCoroutineContext }
): HasBackgroundScope =
remember { RememberedBackgroundScope(coroutineContext()) }

private class SimpleBackgroundScope(
parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
) : HasBackgroundScope {
override val backgroundScope: CoroutineScope =
CoroutineScope(parentCoroutineContext + SupervisorJob(parentCoroutineContext[Job]))
}

@PublishedApi
internal class RememberedBackgroundScope(
parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
) : HasBackgroundScope, RememberObserver {
override val backgroundScope: CoroutineScope =
CoroutineScope(parentCoroutineContext + SupervisorJob(parentCoroutineContext[Job]))

override fun onAbandoned() {
backgroundScope.cancel("RememberedBackgroundScope left the composition")
}

override fun onForgotten() {
backgroundScope.cancel("RememberedBackgroundScope left the composition")
}

override fun onRemembered() {
}
}

fun <V : HasBackgroundScope> V.launchInBackgroundAnimated(
isLoadingState: MutableState<Boolean>,
context: CoroutineContext = EmptyCoroutineContext,
Expand Down
2 changes: 1 addition & 1 deletion app/shared/pages/settings/common/tabs/ui/UISettingsTab.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import me.him188.ani.app.data.models.UISettings
import me.him188.ani.app.data.repositories.SettingsRepository
import me.him188.ani.app.ui.collection.EpisodeProgressTheme
import me.him188.ani.app.ui.collection.progress.EpisodeProgressTheme
import me.him188.ani.app.ui.external.placeholder.placeholder
import me.him188.ani.app.ui.foundation.rememberViewModel
import me.him188.ani.app.ui.settings.SettingsTab
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.PreviewLightDark
import me.him188.ani.app.data.media.EpisodeCacheStatus
import me.him188.ani.app.ui.collection.progress.EpisodeProgressDefaults
import me.him188.ani.app.ui.collection.progress.EpisodeProgressDialog
import me.him188.ani.app.ui.collection.progress.EpisodeProgressItem
import me.him188.ani.app.ui.collection.progress.EpisodeProgressRow
import me.him188.ani.app.ui.collection.progress.EpisodeProgressTheme
import me.him188.ani.app.ui.foundation.ProvideCompositionLocalsForPreview
import me.him188.ani.datasources.api.topic.FileSize.Companion.megaBytes
import me.him188.ani.datasources.api.topic.UnifiedCollectionType
Expand Down Expand Up @@ -82,7 +87,6 @@ private fun PreviewEpisodeProgressDialog() {
ProvideCompositionLocalsForPreview {
EpisodeProgressDialog(
onDismissRequest = {},
onClickDetails = {},
title = { Text(text = "葬送的芙莉莲") },
onClickCache = {},
) {
Expand All @@ -106,7 +110,6 @@ private fun PreviewEpisodeProgressDialogLightUp() {
ProvideCompositionLocalsForPreview {
EpisodeProgressDialog(
onDismissRequest = {},
onClickDetails = {},
title = { Text(text = "葬送的芙莉莲") },
onClickCache = {},
) {
Expand All @@ -132,7 +135,6 @@ private fun PreviewEpisodeProgressDialogVeryLong() {
ProvideCompositionLocalsForPreview {
EpisodeProgressDialog(
onDismissRequest = {},
onClickDetails = {},
title = { Text(text = "银魂") },
onClickCache = {},
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package me.him188.ani.app.ui.collection

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import me.him188.ani.app.ui.foundation.ProvideCompositionLocalsForPreview
import me.him188.ani.datasources.api.topic.UnifiedCollectionType


@Composable
@Preview
fun PreviewCollectionActionButton() = ProvideCompositionLocalsForPreview {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
Column {
for (entry in UnifiedCollectionType.entries) {
CollectionActionButton(
collected = entry != UnifiedCollectionType.NOT_COLLECTED,
type = entry,
onCollect = {},
onEdit = {},
onSetAllEpisodesDone = {},
)
}
}
}
}
46 changes: 13 additions & 33 deletions app/shared/pages/subject-collection/common/CollectionPage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SecondaryScrollableTabRow
import androidx.compose.material3.Tab
Expand All @@ -51,7 +52,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
Expand All @@ -65,13 +65,12 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import me.him188.ani.app.data.subject.SubjectCollectionItem
import me.him188.ani.app.interaction.VibrationStrength
import me.him188.ani.app.interaction.vibrateIfSupported
import me.him188.ani.app.navigation.LocalNavigator
import me.him188.ani.app.platform.LocalContext
import me.him188.ani.app.tools.caching.LazyDataCache
import me.him188.ani.app.tools.caching.RefreshOrderPolicy
import me.him188.ani.app.tools.rememberUiMonoTasker
import me.him188.ani.app.ui.collection.progress.EpisodeProgressDialog
import me.him188.ani.app.ui.collection.progress.rememberEpisodeProgressState
import me.him188.ani.app.ui.foundation.effects.OnLifecycleEvent
import me.him188.ani.app.ui.foundation.launchInBackground
import me.him188.ani.app.ui.foundation.pagerTabIndicatorOffset
Expand Down Expand Up @@ -230,47 +229,28 @@ private fun TabContent(
lazyListState: LazyListState = rememberLazyListState(),
enableAnimation: () -> Boolean = { true },
) {
val context by rememberUpdatedState(LocalContext.current)
SubjectCollectionsColumn(
cache,
onRequestMore = onRequestMore,
item = { subjectCollection ->
var showEpisodeProgressDialog by rememberSaveable { mutableStateOf(false) }

val progress by remember(vm, subjectCollection) {
vm.subjectProgress(subjectCollection.subjectId)
}.collectAsStateWithLifecycle(emptyList()) // #155: 在 dialog 弹出之前就开始加载, 否则点击 "选集" 会需要等待 1 秒左右
// 即使对话框不显示也加载, 避免打开对话框要等待一秒才能看到进度
val episodeProgressState = rememberEpisodeProgressState(subjectCollection.subjectId)

val navigator = LocalNavigator.current
if (showEpisodeProgressDialog) {
val navigator = LocalNavigator.current

EpisodeProgressDialog(
episodeProgressState,
onDismissRequest = { showEpisodeProgressDialog = false },
onClickDetails = { navigator.navigateSubjectDetails(subjectCollection.subjectId) },
title = { Text(text = subjectCollection.displayName) },
onClickCache = { navigator.navigateSubjectCaches(subjectCollection.subjectId) },
) {
EpisodeProgressRow(
episodes = { progress },
onClickEpisodeState = {
navigator.navigateEpisodeDetails(subjectCollection.subjectId, it.episodeId)
},
onLongClickEpisode = { progressItem ->
context.vibrateIfSupported(VibrationStrength.TICK)
vm.launchInBackground {
setEpisodeWatched(
subjectCollection.subjectId,
progressItem.episodeId,
watched = progressItem.watchStatus != UnifiedCollectionType.DONE,
)
}
},
colors = EpisodeProgressDefaults.colors(vm.episodeProgressSettings.theme)
)
}
actions = {
OutlinedButton({ navigator.navigateSubjectDetails(episodeProgressState.subjectId) }) {
Text("条目详情")
}
}
)
}

val navigator = LocalNavigator.current
SubjectCollectionItem(
subjectCollection,
episodeCacheStatus = { subjectId, episodeId ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import kotlinx.coroutines.flow.debounce
import me.him188.ani.app.data.media.EpisodeCacheStatus
import me.him188.ani.app.data.subject.SubjectCollectionItem
import me.him188.ani.app.tools.caching.LazyDataCache
import me.him188.ani.app.ui.collection.progress.cacheStatusIndicationColor
import me.him188.ani.app.ui.foundation.AsyncImage
import me.him188.ani.app.ui.foundation.ifThen
import me.him188.ani.app.ui.foundation.indication.HorizontalIndicator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ import me.him188.ani.app.data.models.MyCollectionsSettings
import me.him188.ani.app.data.repositories.SettingsRepository
import me.him188.ani.app.data.subject.SubjectCollectionItem
import me.him188.ani.app.data.subject.SubjectManager
import me.him188.ani.app.data.subject.setEpisodeWatched
import me.him188.ani.app.tools.MonoTasker
import me.him188.ani.app.tools.caching.ContentPolicy
import me.him188.ani.app.tools.caching.LazyDataCache
import me.him188.ani.app.tools.caching.getCachedData
import me.him188.ani.app.ui.foundation.AbstractViewModel
Expand Down Expand Up @@ -44,22 +42,12 @@ interface MyCollectionsViewModel : HasBackgroundScope, ViewModelAuthSupport {

fun requestMore(type: UnifiedCollectionType)

/**
* 返回用户观看该番剧的进度 [Flow].
*
* 该 flow 总是使用 [ContentPolicy.CACHE_ONLY]
*/
@Stable
fun subjectProgress(subjectId: Int): Flow<List<EpisodeProgressItem>>

@Stable
fun cacheStatusForEpisode(subjectId: Int, episodeId: Int): Flow<EpisodeCacheStatus>

suspend fun setCollectionType(subjectId: Int, type: UnifiedCollectionType)

suspend fun setAllEpisodesWatched(subjectId: Int)

suspend fun setEpisodeWatched(subjectId: Int, episodeId: Int, watched: Boolean)
}

fun MyCollectionsViewModel(): MyCollectionsViewModel = MyCollectionsViewModelImpl()
Expand Down Expand Up @@ -99,10 +87,6 @@ class MyCollectionsViewModelImpl : AbstractViewModel(), KoinComponent, MyCollect
}
}

@Stable
override fun subjectProgress(subjectId: Int): Flow<List<EpisodeProgressItem>> =
subjectManager.subjectProgressFlow(subjectId, ContentPolicy.CACHE_ONLY)

@Stable
override fun cacheStatusForEpisode(subjectId: Int, episodeId: Int): Flow<EpisodeCacheStatus> =
cacheManager.cacheStatusForEpisode(
Expand All @@ -115,9 +99,6 @@ class MyCollectionsViewModelImpl : AbstractViewModel(), KoinComponent, MyCollect

override suspend fun setAllEpisodesWatched(subjectId: Int) = subjectManager.setAllEpisodesWatched(subjectId)

override suspend fun setEpisodeWatched(subjectId: Int, episodeId: Int, watched: Boolean) =
subjectManager.setEpisodeWatched(subjectId, episodeId, watched)

override fun init() {
// 获取第一页, 得到数量
// 不要太快, 测试到的如果全并行就会导致 "在看" 没有数据, 不清楚是哪边问题.
Expand Down
Loading

0 comments on commit 509131d

Please sign in to comment.