Skip to content

Commit e7c5410

Browse files
committed
新增我的收藏
1 parent fb2893c commit e7c5410

File tree

7 files changed

+296
-3
lines changed

7 files changed

+296
-3
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,11 @@
237237
android:exported="false"
238238
android:label="@string/title_mobile_activity_history"
239239
android:theme="@style/Theme.BVMobile" />
240+
<activity
241+
android:name=".mobile.activities.FavoriteActivity"
242+
android:exported="false"
243+
android:label="@string/title_mobile_activity_favorite"
244+
android:theme="@style/Theme.BVMobile" />
240245
</application>
241246

242247
</manifest>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dev.aaa1115910.bv.mobile.activities
2+
3+
import android.os.Bundle
4+
import androidx.activity.ComponentActivity
5+
import androidx.activity.compose.setContent
6+
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
7+
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
8+
import dev.aaa1115910.bv.mobile.screen.FavoriteScreen
9+
import dev.aaa1115910.bv.mobile.theme.BVMobileTheme
10+
11+
class FavoriteActivity : ComponentActivity() {
12+
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
13+
override fun onCreate(savedInstanceState: Bundle?) {
14+
super.onCreate(savedInstanceState)
15+
setContent {
16+
val windowSize = calculateWindowSizeClass(this)
17+
BVMobileTheme {
18+
FavoriteScreen(
19+
windowSize = windowSize
20+
)
21+
}
22+
}
23+
}
24+
}
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
package dev.aaa1115910.bv.mobile.screen
2+
3+
import android.app.Activity
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.PaddingValues
8+
import androidx.compose.foundation.layout.height
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.foundation.lazy.grid.GridCells
11+
import androidx.compose.foundation.lazy.grid.LazyGridState
12+
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
13+
import androidx.compose.foundation.lazy.grid.itemsIndexed
14+
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
15+
import androidx.compose.material.icons.Icons
16+
import androidx.compose.material.icons.automirrored.filled.ArrowBack
17+
import androidx.compose.material3.ExperimentalMaterial3Api
18+
import androidx.compose.material3.HorizontalDivider
19+
import androidx.compose.material3.Icon
20+
import androidx.compose.material3.IconButton
21+
import androidx.compose.material3.LargeTopAppBar
22+
import androidx.compose.material3.MaterialTheme
23+
import androidx.compose.material3.PrimaryScrollableTabRow
24+
import androidx.compose.material3.Scaffold
25+
import androidx.compose.material3.Tab
26+
import androidx.compose.material3.Text
27+
import androidx.compose.material3.TopAppBarDefaults
28+
import androidx.compose.material3.rememberTopAppBarState
29+
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
30+
import androidx.compose.material3.windowsizeclass.WindowSizeClass
31+
import androidx.compose.runtime.Composable
32+
import androidx.compose.runtime.LaunchedEffect
33+
import androidx.compose.runtime.derivedStateOf
34+
import androidx.compose.runtime.getValue
35+
import androidx.compose.runtime.mutableStateOf
36+
import androidx.compose.runtime.remember
37+
import androidx.compose.runtime.setValue
38+
import androidx.compose.ui.Alignment
39+
import androidx.compose.ui.Modifier
40+
import androidx.compose.ui.input.nestedscroll.nestedScroll
41+
import androidx.compose.ui.platform.LocalContext
42+
import androidx.compose.ui.res.stringResource
43+
import androidx.compose.ui.text.style.TextAlign
44+
import androidx.compose.ui.tooling.preview.Preview
45+
import androidx.compose.ui.unit.dp
46+
import dev.aaa1115910.biliapi.entity.FavoriteFolderMetadata
47+
import dev.aaa1115910.bv.R
48+
import dev.aaa1115910.bv.entity.carddata.VideoCardData
49+
import dev.aaa1115910.bv.mobile.activities.VideoPlayerActivity
50+
import dev.aaa1115910.bv.mobile.component.videocard.SmallVideoCard
51+
import dev.aaa1115910.bv.mobile.theme.BVMobileTheme
52+
import dev.aaa1115910.bv.util.OnBottomReached
53+
import dev.aaa1115910.bv.util.calculateWindowSizeClassInPreview
54+
import dev.aaa1115910.bv.viewmodel.user.FavoriteViewModel
55+
import org.koin.androidx.compose.koinViewModel
56+
57+
@Composable
58+
fun FavoriteScreen(
59+
modifier: Modifier = Modifier,
60+
windowSize: WindowSizeClass,
61+
favoriteViewModel: FavoriteViewModel = koinViewModel()
62+
) {
63+
val context = LocalContext.current
64+
val listState = rememberLazyGridState()
65+
66+
val currentTabIndex by remember {
67+
derivedStateOf {
68+
favoriteViewModel.favoriteFolderMetadataList.indexOf(favoriteViewModel.currentFavoriteFolderMetadata)
69+
}
70+
}
71+
72+
if (favoriteViewModel.favoriteFolderMetadataList.isNotEmpty() && favoriteViewModel.favorites.isNotEmpty()) {
73+
listState.OnBottomReached(
74+
loading = favoriteViewModel.updatingFolderItems,
75+
) {
76+
favoriteViewModel.updateFolderItems()
77+
}
78+
}
79+
80+
LaunchedEffect(currentTabIndex) {
81+
favoriteViewModel.favorites.clear()
82+
favoriteViewModel.updateFolderItems(force = true)
83+
}
84+
85+
FavoriteContent(
86+
modifier = modifier,
87+
listState = listState,
88+
windowSize = windowSize,
89+
selectedTabIndex = currentTabIndex,
90+
favoriteFolders = favoriteViewModel.favoriteFolderMetadataList,
91+
favorites = favoriteViewModel.favorites,
92+
onClickTab = { folderMetadata ->
93+
favoriteViewModel.currentFavoriteFolderMetadata = folderMetadata
94+
},
95+
onClickVideo = { videoCardData ->
96+
VideoPlayerActivity.actionStart(
97+
context = context,
98+
aid = videoCardData.avid
99+
)
100+
},
101+
onBack = { (context as Activity).finish() }
102+
)
103+
}
104+
105+
@OptIn(ExperimentalMaterial3Api::class)
106+
@Composable
107+
private fun FavoriteContent(
108+
modifier: Modifier = Modifier,
109+
listState: LazyGridState = rememberLazyGridState(),
110+
windowSize: WindowSizeClass,
111+
selectedTabIndex: Int,
112+
favoriteFolders: List<FavoriteFolderMetadata>,
113+
favorites: List<VideoCardData>,
114+
onClickTab: (FavoriteFolderMetadata) -> Unit,
115+
onClickVideo: (VideoCardData) -> Unit,
116+
onBack: () -> Unit
117+
) {
118+
val scrollBehavior =
119+
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
120+
121+
Scaffold(
122+
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
123+
topBar = {
124+
Column {
125+
LargeTopAppBar(
126+
title = { Text(text = stringResource(id = R.string.title_mobile_activity_favorite)) },
127+
navigationIcon = {
128+
IconButton(onClick = onBack) {
129+
Icon(
130+
imageVector = Icons.AutoMirrored.Default.ArrowBack,
131+
contentDescription = null
132+
)
133+
}
134+
},
135+
scrollBehavior = scrollBehavior
136+
)
137+
138+
if (favoriteFolders.isNotEmpty()) {
139+
PrimaryScrollableTabRow(
140+
selectedTabIndex = selectedTabIndex,
141+
divider = { },
142+
) {
143+
favoriteFolders.forEachIndexed { index, folderMetadata ->
144+
Tab(
145+
selected = selectedTabIndex == index,
146+
onClick = { onClickTab(folderMetadata) }
147+
) {
148+
Box(
149+
modifier = Modifier.height(48.dp),
150+
contentAlignment = Alignment.Center
151+
) {
152+
Text(
153+
modifier = Modifier.padding(horizontal = 16.dp),
154+
text = folderMetadata.title,
155+
style = MaterialTheme.typography.bodyLarge,
156+
textAlign = TextAlign.Center
157+
)
158+
}
159+
160+
}
161+
}
162+
}
163+
HorizontalDivider()
164+
}
165+
}
166+
}
167+
) { innerPadding ->
168+
LazyVerticalGrid(
169+
modifier = Modifier.padding(top = innerPadding.calculateTopPadding()),
170+
state = listState,
171+
columns = GridCells.Adaptive(180.dp),
172+
contentPadding = PaddingValues(12.dp),
173+
verticalArrangement = Arrangement.spacedBy(12.dp),
174+
horizontalArrangement = Arrangement.spacedBy(12.dp)
175+
) {
176+
itemsIndexed(items = favorites) { index, data ->
177+
SmallVideoCard(
178+
data = data,
179+
onClick = { onClickVideo(data) }
180+
)
181+
}
182+
}
183+
}
184+
}
185+
186+
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
187+
@Preview(device = "spec:width=411dp,height=891dp")
188+
@Preview(device = "spec:width=1280dp,height=800dp,dpi=240")
189+
@Composable
190+
private fun FavoriteContentPreview() {
191+
val windowSize = calculateWindowSizeClassInPreview()
192+
val favoriteFolderSize = 10
193+
var currentFavoriteFolderMetadata by remember { mutableStateOf<FavoriteFolderMetadata?>(null) }
194+
195+
val favoriteFolderMetadataList = (1..favoriteFolderSize).map {
196+
FavoriteFolderMetadata(
197+
id = it.toLong(),
198+
fid = it.toLong(),
199+
mid = 0,
200+
title = "folder$it",
201+
cover = null,
202+
videoInThisFav = false,
203+
mediaCount = (30..50).random()
204+
)
205+
}
206+
207+
val generateFavorites: (Long) -> List<VideoCardData> = { folderId ->
208+
(1..(currentFavoriteFolderMetadata?.mediaCount ?: 50)).map {
209+
VideoCardData(
210+
avid = it.toLong(),
211+
title = "folder$folderId video$it",
212+
cover = "",
213+
play = it * 1000,
214+
danmaku = it * 100,
215+
upName = "upName$it",
216+
time = it * 1000L
217+
)
218+
}
219+
}
220+
221+
val currentTabIndex by remember {
222+
derivedStateOf {
223+
favoriteFolderMetadataList.indexOf(currentFavoriteFolderMetadata)
224+
.takeIf { it != -1 } ?: 0
225+
}
226+
}
227+
val favorites by remember {
228+
derivedStateOf { generateFavorites(currentFavoriteFolderMetadata?.id ?: 0) }
229+
}
230+
231+
BVMobileTheme {
232+
FavoriteContent(
233+
windowSize = windowSize,
234+
selectedTabIndex = currentTabIndex,
235+
favoriteFolders = favoriteFolderMetadataList,
236+
favorites = favorites,
237+
onClickTab = { currentFavoriteFolderMetadata = it },
238+
onClickVideo = {},
239+
onBack = {}
240+
)
241+
}
242+
}

app/src/main/kotlin/dev/aaa1115910/bv/mobile/screen/MobileMainScreen.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ import com.origeek.imageViewer.previewer.VerticalDragType
8484
import com.origeek.imageViewer.previewer.rememberPreviewerState
8585
import dev.aaa1115910.biliapi.entity.Picture
8686
import dev.aaa1115910.bv.component.DevelopingTipContent
87+
import dev.aaa1115910.bv.mobile.activities.FavoriteActivity
8788
import dev.aaa1115910.bv.mobile.activities.FollowingUserActivity
8889
import dev.aaa1115910.bv.mobile.activities.HistoryActivity
8990
import dev.aaa1115910.bv.mobile.activities.LoginActivity
@@ -310,8 +311,12 @@ fun MobileMainScreen(
310311
Intent(context, HistoryActivity::class.java)
311312
)
312313
},
313-
onOpenFavorite = {},
314314
onOpenFollowingPgc = {},
315+
onOpenFavorite = {
316+
context.startActivity(
317+
Intent(context, FavoriteActivity::class.java)
318+
)
319+
},
315320
onOpenToView = {},
316321
onOpenSettings = { context.startActivity(Intent(context, SettingsActivity::class.java)) }
317322
)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package dev.aaa1115910.bv.util
2+
3+
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
4+
import androidx.compose.material3.windowsizeclass.WindowSizeClass
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.ui.platform.LocalConfiguration
7+
import androidx.compose.ui.unit.DpSize
8+
import androidx.compose.ui.unit.dp
9+
10+
@ExperimentalMaterial3WindowSizeClassApi
11+
@Composable
12+
fun calculateWindowSizeClassInPreview(): WindowSizeClass {
13+
val configuration = LocalConfiguration.current
14+
val size = DpSize(configuration.screenWidthDp.dp, configuration.screenHeightDp.dp)
15+
return WindowSizeClass.calculateFromSize(size)
16+
}

app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/user/FavoriteViewModel.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ class FavoriteViewModel(
3737
private var pageNumber = 1
3838
private var hasMore = true
3939

40-
private var updatingFolders = false
41-
private var updatingFolderItems = false
40+
var updatingFolders by mutableStateOf(false)
41+
var updatingFolderItems by mutableStateOf(false)
4242

4343
init {
4444
updateFoldersInfo()

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@
255255
<string name="title_activity_video_info">视频信息</string>
256256
<string name="title_activity_video_player_v3">视频播放</string>
257257
<string name="title_mobile_activity_dynamic_detail">动态详情</string>
258+
<string name="title_mobile_activity_favorite">我的收藏</string>
258259
<string name="title_mobile_activity_following_user">我的关注</string>
259260
<string name="title_mobile_activity_history">历史记录</string>
260261
<string name="title_mobile_activity_login">用户登录</string>

0 commit comments

Comments
 (0)