Skip to content

Commit

Permalink
新增我的收藏
Browse files Browse the repository at this point in the history
  • Loading branch information
aaa1115910 committed Jan 27, 2025
1 parent fb2893c commit e7c5410
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 3 deletions.
5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@
android:exported="false"
android:label="@string/title_mobile_activity_history"
android:theme="@style/Theme.BVMobile" />
<activity
android:name=".mobile.activities.FavoriteActivity"
android:exported="false"
android:label="@string/title_mobile_activity_favorite"
android:theme="@style/Theme.BVMobile" />
</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dev.aaa1115910.bv.mobile.activities

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
import dev.aaa1115910.bv.mobile.screen.FavoriteScreen
import dev.aaa1115910.bv.mobile.theme.BVMobileTheme

class FavoriteActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val windowSize = calculateWindowSizeClass(this)
BVMobileTheme {
FavoriteScreen(
windowSize = windowSize
)
}
}
}
}
242 changes: 242 additions & 0 deletions app/src/main/kotlin/dev/aaa1115910/bv/mobile/screen/FavoriteScreen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package dev.aaa1115910.bv.mobile.screen

import android.app.Activity
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryScrollableTabRow
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import dev.aaa1115910.biliapi.entity.FavoriteFolderMetadata
import dev.aaa1115910.bv.R
import dev.aaa1115910.bv.entity.carddata.VideoCardData
import dev.aaa1115910.bv.mobile.activities.VideoPlayerActivity
import dev.aaa1115910.bv.mobile.component.videocard.SmallVideoCard
import dev.aaa1115910.bv.mobile.theme.BVMobileTheme
import dev.aaa1115910.bv.util.OnBottomReached
import dev.aaa1115910.bv.util.calculateWindowSizeClassInPreview
import dev.aaa1115910.bv.viewmodel.user.FavoriteViewModel
import org.koin.androidx.compose.koinViewModel

@Composable
fun FavoriteScreen(
modifier: Modifier = Modifier,
windowSize: WindowSizeClass,
favoriteViewModel: FavoriteViewModel = koinViewModel()
) {
val context = LocalContext.current
val listState = rememberLazyGridState()

val currentTabIndex by remember {
derivedStateOf {
favoriteViewModel.favoriteFolderMetadataList.indexOf(favoriteViewModel.currentFavoriteFolderMetadata)
}
}

if (favoriteViewModel.favoriteFolderMetadataList.isNotEmpty() && favoriteViewModel.favorites.isNotEmpty()) {
listState.OnBottomReached(
loading = favoriteViewModel.updatingFolderItems,
) {
favoriteViewModel.updateFolderItems()
}
}

LaunchedEffect(currentTabIndex) {
favoriteViewModel.favorites.clear()
favoriteViewModel.updateFolderItems(force = true)
}

FavoriteContent(
modifier = modifier,
listState = listState,
windowSize = windowSize,
selectedTabIndex = currentTabIndex,
favoriteFolders = favoriteViewModel.favoriteFolderMetadataList,
favorites = favoriteViewModel.favorites,
onClickTab = { folderMetadata ->
favoriteViewModel.currentFavoriteFolderMetadata = folderMetadata
},
onClickVideo = { videoCardData ->
VideoPlayerActivity.actionStart(
context = context,
aid = videoCardData.avid
)
},
onBack = { (context as Activity).finish() }
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun FavoriteContent(
modifier: Modifier = Modifier,
listState: LazyGridState = rememberLazyGridState(),
windowSize: WindowSizeClass,
selectedTabIndex: Int,
favoriteFolders: List<FavoriteFolderMetadata>,
favorites: List<VideoCardData>,
onClickTab: (FavoriteFolderMetadata) -> Unit,
onClickVideo: (VideoCardData) -> Unit,
onBack: () -> Unit
) {
val scrollBehavior =
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())

Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
Column {
LargeTopAppBar(
title = { Text(text = stringResource(id = R.string.title_mobile_activity_favorite)) },
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = null
)
}
},
scrollBehavior = scrollBehavior
)

if (favoriteFolders.isNotEmpty()) {
PrimaryScrollableTabRow(
selectedTabIndex = selectedTabIndex,
divider = { },
) {
favoriteFolders.forEachIndexed { index, folderMetadata ->
Tab(
selected = selectedTabIndex == index,
onClick = { onClickTab(folderMetadata) }
) {
Box(
modifier = Modifier.height(48.dp),
contentAlignment = Alignment.Center
) {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
text = folderMetadata.title,
style = MaterialTheme.typography.bodyLarge,
textAlign = TextAlign.Center
)
}

}
}
}
HorizontalDivider()
}
}
}
) { innerPadding ->
LazyVerticalGrid(
modifier = Modifier.padding(top = innerPadding.calculateTopPadding()),
state = listState,
columns = GridCells.Adaptive(180.dp),
contentPadding = PaddingValues(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
itemsIndexed(items = favorites) { index, data ->
SmallVideoCard(
data = data,
onClick = { onClickVideo(data) }
)
}
}
}
}

@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
@Preview(device = "spec:width=411dp,height=891dp")
@Preview(device = "spec:width=1280dp,height=800dp,dpi=240")
@Composable
private fun FavoriteContentPreview() {
val windowSize = calculateWindowSizeClassInPreview()
val favoriteFolderSize = 10
var currentFavoriteFolderMetadata by remember { mutableStateOf<FavoriteFolderMetadata?>(null) }

val favoriteFolderMetadataList = (1..favoriteFolderSize).map {
FavoriteFolderMetadata(
id = it.toLong(),
fid = it.toLong(),
mid = 0,
title = "folder$it",
cover = null,
videoInThisFav = false,
mediaCount = (30..50).random()
)
}

val generateFavorites: (Long) -> List<VideoCardData> = { folderId ->
(1..(currentFavoriteFolderMetadata?.mediaCount ?: 50)).map {
VideoCardData(
avid = it.toLong(),
title = "folder$folderId video$it",
cover = "",
play = it * 1000,
danmaku = it * 100,
upName = "upName$it",
time = it * 1000L
)
}
}

val currentTabIndex by remember {
derivedStateOf {
favoriteFolderMetadataList.indexOf(currentFavoriteFolderMetadata)
.takeIf { it != -1 } ?: 0
}
}
val favorites by remember {
derivedStateOf { generateFavorites(currentFavoriteFolderMetadata?.id ?: 0) }
}

BVMobileTheme {
FavoriteContent(
windowSize = windowSize,
selectedTabIndex = currentTabIndex,
favoriteFolders = favoriteFolderMetadataList,
favorites = favorites,
onClickTab = { currentFavoriteFolderMetadata = it },
onClickVideo = {},
onBack = {}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import com.origeek.imageViewer.previewer.VerticalDragType
import com.origeek.imageViewer.previewer.rememberPreviewerState
import dev.aaa1115910.biliapi.entity.Picture
import dev.aaa1115910.bv.component.DevelopingTipContent
import dev.aaa1115910.bv.mobile.activities.FavoriteActivity
import dev.aaa1115910.bv.mobile.activities.FollowingUserActivity
import dev.aaa1115910.bv.mobile.activities.HistoryActivity
import dev.aaa1115910.bv.mobile.activities.LoginActivity
Expand Down Expand Up @@ -310,8 +311,12 @@ fun MobileMainScreen(
Intent(context, HistoryActivity::class.java)
)
},
onOpenFavorite = {},
onOpenFollowingPgc = {},
onOpenFavorite = {
context.startActivity(
Intent(context, FavoriteActivity::class.java)
)
},
onOpenToView = {},
onOpenSettings = { context.startActivity(Intent(context, SettingsActivity::class.java)) }
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package dev.aaa1115910.bv.util

import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp

@ExperimentalMaterial3WindowSizeClassApi
@Composable
fun calculateWindowSizeClassInPreview(): WindowSizeClass {
val configuration = LocalConfiguration.current
val size = DpSize(configuration.screenWidthDp.dp, configuration.screenHeightDp.dp)
return WindowSizeClass.calculateFromSize(size)
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ class FavoriteViewModel(
private var pageNumber = 1
private var hasMore = true

private var updatingFolders = false
private var updatingFolderItems = false
var updatingFolders by mutableStateOf(false)
var updatingFolderItems by mutableStateOf(false)

init {
updateFoldersInfo()
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@
<string name="title_activity_video_info">视频信息</string>
<string name="title_activity_video_player_v3">视频播放</string>
<string name="title_mobile_activity_dynamic_detail">动态详情</string>
<string name="title_mobile_activity_favorite">我的收藏</string>
<string name="title_mobile_activity_following_user">我的关注</string>
<string name="title_mobile_activity_history">历史记录</string>
<string name="title_mobile_activity_login">用户登录</string>
Expand Down

0 comments on commit e7c5410

Please sign in to comment.