Skip to content
This repository was archived by the owner on Jul 18, 2026. It is now read-only.

Commit fe3edb5

Browse files
authored
Merge pull request #359 from misaka10032w/develop
[Fix] 保持视频页和播放清单的“BottomSheet”展开状态、修复播放清单页只加载第一页的问题
2 parents 4c83b26 + d6a8ce9 commit fe3edb5

4 files changed

Lines changed: 154 additions & 22 deletions

File tree

app/src/main/java/com/yenaly/han1meviewer/ui/fragment/PlaylistBottomSheetDialogFragment.kt

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.yenaly.han1meviewer.ui.fragment
22

33
import android.os.Bundle
4+
import android.os.Parcelable
45
import android.view.LayoutInflater
56
import android.view.View
67
import android.view.ViewGroup
@@ -25,6 +26,9 @@ class PlaylistBottomSheetFragment : BottomSheetDialogFragment() {
2526
private val viewModel: VideoViewModel by viewModels({ requireParentFragment() })
2627
private var videoCount = 0
2728
private lateinit var adapter: HanimeVideoRvAdapter
29+
private lateinit var recyclerView: RecyclerView
30+
private var savedSheetState: Int = BottomSheetBehavior.STATE_COLLAPSED
31+
private var savedLayoutManagerState: Parcelable? = null
2832

2933
override fun onStart() {
3034
super.onStart()
@@ -33,11 +37,19 @@ class PlaylistBottomSheetFragment : BottomSheetDialogFragment() {
3337
val behavior = BottomSheetBehavior.from(it)
3438
behavior.peekHeight = resources.getDimensionPixelSize(R.dimen.bottom_sheet_min_height)
3539
behavior.isFitToContents = true
36-
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
40+
behavior.state = savedSheetState
3741
it.minimumHeight = resources.getDimensionPixelSize(R.dimen.bottom_sheet_min_height)
3842
}
3943
}
4044

45+
override fun onStop() {
46+
super.onStop()
47+
dialog?.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)?.let {
48+
savedSheetState = BottomSheetBehavior.from(it).state
49+
}
50+
savedLayoutManagerState = recyclerView.layoutManager?.onSaveInstanceState()
51+
}
52+
4153
override fun onCreateView(
4254
inflater: LayoutInflater,
4355
container: ViewGroup?,
@@ -49,9 +61,8 @@ class PlaylistBottomSheetFragment : BottomSheetDialogFragment() {
4961
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
5062
adapter = HanimeVideoRvAdapter(onItemClick = { item ->
5163
openVideo(item.videoCode)
52-
dismiss()
5364
})
54-
val recyclerView = view.findViewById<RecyclerView>(R.id.rv_vertical_list)
65+
recyclerView = view.findViewById(R.id.rv_vertical_list)
5566
val countText = view.findViewById<TextView>(R.id.video_count)
5667
recyclerView.adapter = adapter
5768
recyclerView.viewTreeObserver.addOnGlobalLayoutListener(
@@ -68,16 +79,22 @@ class PlaylistBottomSheetFragment : BottomSheetDialogFragment() {
6879
videoCount = list.size
6980
countText.text = getString(R.string.blank_brackets,videoCount)
7081
adapter.submitList(list)
82+
if (savedLayoutManagerState != null) {
83+
recyclerView.post {
84+
recyclerView.layoutManager?.onRestoreInstanceState(savedLayoutManagerState)
85+
savedLayoutManagerState = null
86+
}
87+
}
7188
}
7289

7390
recyclerView.addOnLayoutChangeListener { _, left, _, right, _, _, _, _, _ ->
7491
val newWidth = right - left
7592
if (newWidth > 0) {
76-
val spanCount = calculateSpanCount(
77-
recyclerView,
78-
180
79-
)
80-
(recyclerView.layoutManager as? GridLayoutManager)?.spanCount = spanCount
93+
val newSpanCount = calculateSpanCount(recyclerView, 180)
94+
val existingManager = recyclerView.layoutManager as? GridLayoutManager
95+
if (existingManager == null || existingManager.spanCount != newSpanCount) {
96+
recyclerView.layoutManager = GridLayoutManager(requireContext(), newSpanCount)
97+
}
8198
}
8299
}
83100
}

app/src/main/java/com/yenaly/han1meviewer/ui/fragment/home/MyPlayListFragmentV2.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class MyPlayListFragmentV2 : Fragment(), LoginNeededFragmentMixin {
6161
}
6262

6363
private fun navigateToVideo(videoCode: String) {
64+
exitTransition = null
6465
val bundle = bundleOf(VIDEO_CODE to videoCode)
6566
findNavController().navigate(R.id.videoFragment, bundle)
6667
}

app/src/main/java/com/yenaly/han1meviewer/ui/fragment/home/myplaylist/MyPlayListScreen.kt

Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,24 @@ import androidx.compose.foundation.layout.Column
1515
import androidx.compose.foundation.layout.PaddingValues
1616
import androidx.compose.foundation.layout.Spacer
1717
import androidx.compose.foundation.layout.fillMaxSize
18+
import androidx.compose.foundation.layout.fillMaxWidth
1819
import androidx.compose.foundation.layout.height
1920
import androidx.compose.foundation.layout.padding
2021
import androidx.compose.foundation.lazy.grid.GridCells
22+
import androidx.compose.foundation.lazy.grid.GridItemSpan
2123
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
2224
import androidx.compose.foundation.lazy.grid.items
25+
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
2326
import androidx.compose.material.icons.Icons
2427
import androidx.compose.material.icons.automirrored.filled.ArrowBack
2528
import androidx.compose.material.icons.filled.Add
2629
import androidx.compose.material3.Button
2730
import androidx.compose.material3.CenterAlignedTopAppBar
28-
import androidx.compose.material3.CircularProgressIndicator
2931
import androidx.compose.material3.ExperimentalMaterial3Api
3032
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
3133
import androidx.compose.material3.Icon
3234
import androidx.compose.material3.IconButton
35+
import androidx.compose.material3.LoadingIndicator
3336
import androidx.compose.material3.MaterialTheme
3437
import androidx.compose.material3.Scaffold
3538
import androidx.compose.material3.Text
@@ -46,6 +49,7 @@ import androidx.compose.runtime.getValue
4649
import androidx.compose.runtime.mutableStateOf
4750
import androidx.compose.runtime.remember
4851
import androidx.compose.runtime.setValue
52+
import androidx.compose.runtime.snapshotFlow
4953
import androidx.compose.ui.Alignment
5054
import androidx.compose.ui.Modifier
5155
import androidx.compose.ui.graphics.graphicsLayer
@@ -80,6 +84,8 @@ fun MyPlayListScreen(
8084
var isRefreshing by remember { mutableStateOf(false) }
8185
val refreshState = rememberPullToRefreshState()
8286
val showSheet by viewModel.showSheet.collectAsState()
87+
val isLoadingMorePlaylists by viewModel.isLoadingMorePlaylists.collectAsState()
88+
val noMorePlaylists by viewModel.noMorePlaylists.collectAsState()
8389
val selectedListCode = remember { mutableStateOf("") }
8490
val listTitle = remember { mutableStateOf("") }
8591
val context = LocalContext.current
@@ -184,12 +190,18 @@ fun MyPlayListScreen(
184190
when (val result = state) {
185191
is WebsiteState.Loading -> {
186192
if (playlists.isEmpty()) {
187-
CircularProgressIndicator(Modifier.align(Alignment.Center))
193+
LoadingIndicator(Modifier.align(Alignment.Center))
188194
} else {
189195
// 显示旧数据,刷新中不替换内容
190196
AnimatedPageContent(
191197
state,
192-
playlists = playlists
198+
playlists = playlists,
199+
playlistPage = viewModel.playlistPage,
200+
isLoadingMore = isLoadingMorePlaylists,
201+
noMorePlaylists = noMorePlaylists,
202+
onLoadNextPage = {
203+
viewModel.loadMyPlayList(viewModel.playlistPage + 1)
204+
}
193205
){ listCode, playListTitle ->
194206
selectedListCode.value = listCode
195207
viewModel.setShowSheet(true)
@@ -208,7 +220,13 @@ fun MyPlayListScreen(
208220
// 显示旧缓存内容(保持体验)
209221
AnimatedPageContent(
210222
state,
211-
playlists = playlists
223+
playlists = playlists,
224+
playlistPage = viewModel.playlistPage,
225+
isLoadingMore = isLoadingMorePlaylists,
226+
noMorePlaylists = noMorePlaylists,
227+
onLoadNextPage = {
228+
viewModel.loadMyPlayList(viewModel.playlistPage + 1)
229+
}
212230
){ listCode, playListTitle ->
213231
selectedListCode.value = listCode
214232
viewModel.setShowSheet(true)
@@ -221,7 +239,13 @@ fun MyPlayListScreen(
221239
// 统一渲染缓存(成功后缓存已更新)
222240
AnimatedPageContent(
223241
state,
224-
playlists = playlists
242+
playlists = playlists,
243+
playlistPage = viewModel.playlistPage,
244+
isLoadingMore = isLoadingMorePlaylists,
245+
noMorePlaylists = noMorePlaylists,
246+
onLoadNextPage = {
247+
viewModel.loadMyPlayList(viewModel.playlistPage + 1)
248+
}
225249
) { listCode, playListTitle ->
226250
selectedListCode.value = listCode
227251
viewModel.setShowSheet(true)
@@ -257,9 +281,9 @@ fun MyPlayListScreen(
257281
playListTitle = listTitle.value,
258282
onClickItem = { item ->
259283
onClickItem(item)
260-
viewModel.setShowSheet(false)
261-
viewModel.currentPage = 1
262-
viewModel.clearCurrentList()
284+
// viewModel.setShowSheet(false)
285+
// viewModel.currentPage = 1
286+
// viewModel.clearCurrentList()
263287
} ,
264288
onLongClickItem = onLongClickItem,
265289
vm = viewModel,
@@ -270,13 +294,37 @@ fun MyPlayListScreen(
270294
}
271295
}
272296

297+
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
273298
@Composable
274299
fun AnimatedPageContent(
275300
state: WebsiteState<Playlists>,
276301
playlists: List<Playlists.Playlist>,
302+
isLoadingMore: Boolean = false,
303+
noMorePlaylists: Boolean = false,
304+
playlistPage: Int = 1,
305+
onLoadNextPage: () -> Unit = {},
277306
onRetry: () -> Unit = {},
278307
onPlaylistClick: (listCode: String, playListTitle: String) -> Unit
279308
) {
309+
val gridState = rememberLazyGridState()
310+
311+
LaunchedEffect(gridState) {
312+
snapshotFlow {
313+
val layoutInfo = gridState.layoutInfo
314+
val totalItems = layoutInfo.totalItemsCount
315+
val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
316+
Triple(
317+
lastVisibleItem >= totalItems - 3 && playlists.isNotEmpty(),
318+
isLoadingMore,
319+
noMorePlaylists
320+
)
321+
}
322+
.collect { (shouldLoad, loading, noMore) ->
323+
if (shouldLoad && !loading && !noMore) {
324+
onLoadNextPage()
325+
}
326+
}
327+
}
280328

281329
AnimatedContent(
282330
targetState = state,
@@ -288,7 +336,7 @@ fun AnimatedPageContent(
288336
when (target) {
289337
is WebsiteState.Loading -> {
290338
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
291-
CircularProgressIndicator()
339+
LoadingIndicator()
292340
}
293341
}
294342

@@ -305,11 +353,12 @@ fun AnimatedPageContent(
305353
}
306354

307355
is WebsiteState.Success -> {
308-
if (target.info.playlists.isEmpty()){
356+
if (target.info.playlists.isEmpty() && playlists.isEmpty()){
309357
EmptyView(stringResource(R.string.empty_content))
310358
return@AnimatedContent
311359
}
312360
LazyVerticalGrid(
361+
state = gridState,
313362
columns = GridCells.Fixed(getColumnCount(180)),
314363
contentPadding = PaddingValues(16.dp),
315364
verticalArrangement = Arrangement.spacedBy(16.dp),
@@ -324,6 +373,35 @@ fun AnimatedPageContent(
324373
onPlaylistClick(playlist.listCode, playlist.title)
325374
}
326375
}
376+
if (isLoadingMore) {
377+
item(span = { GridItemSpan(maxLineSpan) }) {
378+
Box(
379+
Modifier
380+
.fillMaxWidth()
381+
.padding(16.dp),
382+
contentAlignment = Alignment.Center
383+
) {
384+
LoadingIndicator()
385+
}
386+
}
387+
}
388+
if (noMorePlaylists && playlists.isNotEmpty()) {
389+
item(span = { GridItemSpan(maxLineSpan) }) {
390+
Box(
391+
Modifier
392+
.fillMaxWidth()
393+
.padding(vertical = 16.dp),
394+
contentAlignment = Alignment.Center
395+
) {
396+
Text(
397+
text = stringResource(R.string.load_complete_with_pages, playlistPage - 1),
398+
style = MaterialTheme.typography.bodySmall.copy(
399+
color = MaterialTheme.colorScheme.onSurfaceVariant
400+
)
401+
)
402+
}
403+
}
404+
}
327405
}
328406
}
329407
}

app/src/main/java/com/yenaly/han1meviewer/ui/viewmodel/MyPlayListViewModelV2.kt

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ class MyPlayListViewModelV2 : ViewModel() {
5050
var currentPage = 1
5151
var isLoadingMore = false
5252
private set
53+
54+
var playlistPage = 1
55+
private val _isLoadingMorePlaylists = MutableStateFlow(false)
56+
val isLoadingMorePlaylists: StateFlow<Boolean> = _isLoadingMorePlaylists.asStateFlow()
57+
private val _noMorePlaylists = MutableStateFlow(false)
58+
val noMorePlaylists: StateFlow<Boolean> = _noMorePlaylists.asStateFlow()
5359
fun setShowSheet(value: Boolean) {
5460
_showSheet.value = value
5561
}
@@ -59,13 +65,43 @@ class MyPlayListViewModelV2 : ViewModel() {
5965

6066
// 加载所有playlist
6167
fun loadMyPlayList(page: Int = 1, forceReload: Boolean = false) {
68+
Log.i("current_page",page.toString())
69+
if (page > 1 && (_isLoadingMorePlaylists.value || _noMorePlaylists.value)) return
70+
if (page == 1 || forceReload) {
71+
playlistPage = 1
72+
_noMorePlaylists.value = false
73+
}
74+
if (page > 1) {
75+
_isLoadingMorePlaylists.value = true
76+
}
6277
val userId = Preferences.savedUserId
6378
viewModelScope.launch {
6479
NetworkRepo.getPlaylists(page, userId).collect { state ->
65-
_myPlaylistsFlow.value = state
66-
if (state is WebsiteState.Success) {
67-
_cachedMyPlayList.value = state.info.playlists
68-
_refreshCompleted.emit(Unit)
80+
when (state) {
81+
is WebsiteState.Loading -> {
82+
if (page == 1 || forceReload) {
83+
_myPlaylistsFlow.value = state
84+
}
85+
}
86+
is WebsiteState.Error -> {
87+
_myPlaylistsFlow.value = state
88+
_isLoadingMorePlaylists.value = false
89+
}
90+
is WebsiteState.Success -> {
91+
val newList = state.info.playlists
92+
if (page == 1 || forceReload) {
93+
_cachedMyPlayList.value = newList
94+
} else {
95+
_cachedMyPlayList.value = _cachedMyPlayList.value + newList
96+
playlistPage = page
97+
}
98+
if (newList.isEmpty()) {
99+
_noMorePlaylists.value = true
100+
}
101+
_myPlaylistsFlow.value = state
102+
_isLoadingMorePlaylists.value = false
103+
_refreshCompleted.emit(Unit)
104+
}
69105
}
70106
}
71107
}

0 commit comments

Comments
 (0)