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 e7c5410 commit 11a651f
Show file tree
Hide file tree
Showing 8 changed files with 376 additions and 4 deletions.
5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@
android:exported="false"
android:label="@string/title_mobile_activity_favorite"
android:theme="@style/Theme.BVMobile" />
<activity
android:name=".mobile.activities.FollowingSeasonActivity"
android:exported="false"
android:label="@string/title_mobile_activity_following_season"
android:theme="@style/Theme.BVMobile" />
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,15 @@ data class SeasonCardData(
badge = null
)
}

fun fromFollowingSeason(followingSeason: dev.aaa1115910.biliapi.entity.season.FollowingSeason): SeasonCardData {
return SeasonCardData(
seasonId = followingSeason.seasonId,
title = followingSeason.title,
cover = followingSeason.cover,
rating = null,
badge = null
)
}
}
}
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.FollowingSeasonScreen
import dev.aaa1115910.bv.mobile.theme.BVMobileTheme

class FollowingSeasonActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val windowSize = calculateWindowSizeClass(this)
BVMobileTheme {
FollowingSeasonScreen(
windowSize = windowSize
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package dev.aaa1115910.bv.mobile.component.videocard

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import dev.aaa1115910.bv.entity.carddata.SeasonCardData
import dev.aaa1115910.bv.mobile.theme.BVMobileTheme

@Composable
fun SeasonCard(
modifier: Modifier = Modifier,
data: SeasonCardData,
coverHeight: Dp? = null,
onClick: () -> Unit = {},
) {
val localDensity = LocalDensity.current
var coverRealWidth by remember { mutableStateOf(0.dp) }

Card(
modifier = modifier,
onClick = onClick,
shape = MaterialTheme.shapes.large,
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
)
) {
Column {
val coverModifier = if (coverHeight != null) {
Modifier.height(coverHeight)
} else {
Modifier.fillMaxWidth()
}
val textBoxModifier = if (coverHeight != null) {
Modifier.width((0.75 * coverHeight.value).dp)
} else {
Modifier
}

Box(
modifier = Modifier.clip(MaterialTheme.shapes.large),
contentAlignment = Alignment.BottomCenter
) {
AsyncImage(
modifier = coverModifier
.aspectRatio(0.75f)
.clip(MaterialTheme.shapes.large)
.onGloballyPositioned { coordinates ->
coverRealWidth = with(localDensity) { coordinates.size.width.toDp() }
},
model = data.cover,
contentDescription = null,
contentScale = ContentScale.FillBounds
)

if (data.rating != null) {
Box(
modifier = Modifier
.height(48.dp)
// 无法使用 fillMaxWidth 来确定宽度
.width(coverRealWidth)
.background(
Brush.verticalGradient(
colors = listOf(
Color.Transparent,
Color.Black.copy(alpha = 0.8f)
)
)
)
)
Text(
modifier = Modifier
.align(Alignment.BottomEnd)
.fillMaxWidth()
.padding(8.dp, 0.dp),
text = data.rating,
fontStyle = FontStyle.Italic,
fontWeight = FontWeight.Bold,
fontSize = 24.sp,
textAlign = TextAlign.End,
color = Color.White
)
}
}

Column(
modifier = textBoxModifier.padding(8.dp)
) {
Text(
text = data.title,
style = MaterialTheme.typography.titleMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (data.subTitle != null) {
Text(
text = data.subTitle,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}
}
}

@Preview(device = "id:tv_1080p")
@Composable
private fun SeasonCardPreview() {
BVMobileTheme {
LazyVerticalGrid(columns = GridCells.Fixed(6)) {
repeat(6) {
item {
SeasonCard(
data = SeasonCardData(
seasonId = 40794,
title = "007:没空去死",
cover = "http://i0.hdslb.com/bfs/bangumi/image/8d211c396aad084d6fa413015200dda6ed260768.png",
rating = "8.6"
)
)
}
}
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package dev.aaa1115910.bv.mobile.screen

import android.app.Activity
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
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.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeTopAppBar
import androidx.compose.material3.PrimaryTabRow
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.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import dev.aaa1115910.biliapi.entity.season.FollowingSeasonType
import dev.aaa1115910.bv.entity.carddata.SeasonCardData
import dev.aaa1115910.bv.mobile.component.videocard.SeasonCard
import dev.aaa1115910.bv.mobile.theme.BVMobileTheme
import dev.aaa1115910.bv.util.OnBottomReached
import dev.aaa1115910.bv.util.calculateWindowSizeClassInPreview
import dev.aaa1115910.bv.util.getDisplayName
import dev.aaa1115910.bv.viewmodel.user.FollowingSeasonViewModel
import org.koin.androidx.compose.koinViewModel

@Composable
fun FollowingSeasonScreen(
modifier: Modifier = Modifier,
windowSize: WindowSizeClass,
followingSeasonViewModel: FollowingSeasonViewModel = koinViewModel()
) {
val context = LocalContext.current
val listState = rememberLazyGridState()

listState.OnBottomReached(
loading = followingSeasonViewModel.updating
) {
if (followingSeasonViewModel.noMore) return@OnBottomReached
followingSeasonViewModel.loadMore()
}

FollowingSeasonContent(
modifier = modifier,
windowSize = windowSize,
type = followingSeasonViewModel.followingSeasonType,
seasons = followingSeasonViewModel.followingSeasons.map(SeasonCardData::fromFollowingSeason),
onBack = { (context as Activity).finish() },
onTypeChange = {
followingSeasonViewModel.followingSeasonType = it
followingSeasonViewModel.clearData()
followingSeasonViewModel.loadMore()
},
onClickSeason = {}
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun FollowingSeasonContent(
modifier: Modifier = Modifier,
windowSize: WindowSizeClass,
type: FollowingSeasonType,
seasons: List<SeasonCardData>,
onBack: () -> Unit,
onTypeChange: (FollowingSeasonType) -> Unit,
onClickSeason: (SeasonCardData) -> Unit,
) {
val context = LocalContext.current
val scrollBehavior =
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())

Scaffold(
modifier = modifier
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
Column {
LargeTopAppBar(
title = { Text(text = "我的追番") },
navigationIcon = {
IconButton(onClick = onBack) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = null
)
}
},
scrollBehavior = scrollBehavior
)
PrimaryTabRow(
selectedTabIndex = type.ordinal,
) {
FollowingSeasonType.entries.forEach { seasonType ->
Tab(
selected = type == seasonType,
text = { Text(text = seasonType.getDisplayName(context)) },
onClick = { onTypeChange(seasonType) }
)
}
}
}
},
) { innerPadding ->
LazyVerticalGrid(
modifier = Modifier.padding(top = innerPadding.calculateTopPadding()),
columns = GridCells.Adaptive(100.dp),
contentPadding = PaddingValues(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
itemsIndexed(seasons) { index, season ->
SeasonCard(
data = season,
onClick = { onClickSeason(season) }
)
}
}
}
}

@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
@Preview
@Composable
private fun FollowingSeasonContentPreview() {
val windowSize = calculateWindowSizeClassInPreview()
var selectedType by remember { mutableStateOf(FollowingSeasonType.Bangumi) }

val seasons = (1..50).map {
SeasonCardData(
seasonId = it,
title = "Title $it",
cover = "http://i0.hdslb.com/bfs/bangumi/image/8d211c396aad084d6fa413015200dda6ed260768.png",
rating = "8.6"
)
}

BVMobileTheme {
FollowingSeasonContent(
windowSize = windowSize,
type = selectedType,
seasons = seasons,
onBack = {},
onTypeChange = { selectedType = it },
onClickSeason = {}
)
}
}
Loading

0 comments on commit 11a651f

Please sign in to comment.