优化阅读记录界面

This commit is contained in:
HapeLee committed 2025-11-26 02:29:07 +08:00
1 parent d29c889de3
commit 3dfc1f5c25
1 file changed
+125 -71
@@ -2,6 +2,8 @@ package io.legado.app.ui.book.readRecord
import android.os.Bundle import android.os.Bundle
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
@@ -36,10 +38,13 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import cn.hutool.core.date.DateUtil import cn.hutool.core.date.DateUtil
import io.legado.app.base.BaseComposeActivity import io.legado.app.base.BaseComposeActivity
import io.legado.app.data.entities.readRecord.ReadRecord import io.legado.app.data.entities.readRecord.ReadRecord
import io.legado.app.data.entities.readRecord.ReadRecordSession import io.legado.app.data.entities.readRecord.ReadRecordSession
import io.legado.app.ui.widget.compose.AnimatedTextLine
import io.legado.app.ui.widget.compose.EmptyMessageView
import io.legado.app.utils.StringUtils.formatFriendlyDate import io.legado.app.utils.StringUtils.formatFriendlyDate
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@@ -108,8 +113,25 @@ fun ReadRecordScreen(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
Column { Column {
LargeTopAppBar( MediumTopAppBar(
title = { Text("阅读记录") }, title = {
val (mainTitle, subTitle) = when (displayMode) {
DisplayMode.AGGREGATE -> "阅读记录" to "汇总视图"
DisplayMode.TIMELINE -> "阅读记录" to "时间线视图"
DisplayMode.LATEST -> "阅读记录" to "最后阅读"
}
Column {
Text(
text = mainTitle,
style = MaterialTheme.typography.titleLarge,
)
AnimatedTextLine(
text = subTitle,
style = MaterialTheme.typography.titleMedium
)
}
},
navigationIcon = { navigationIcon = {
IconButton(onClick = onBackClick) { IconButton(onClick = onBackClick) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null) Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null)
@@ -151,66 +173,87 @@ fun ReadRecordScreen(
Column(modifier = Modifier.padding(padding).fillMaxSize()) { Column(modifier = Modifier.padding(padding).fillMaxSize()) {
TotalTimeHeader(state.totalReadTime) TotalTimeHeader(state.totalReadTime)
val isEmpty = when (displayMode) {
DisplayMode.AGGREGATE -> state.groupedRecords.isEmpty()
DisplayMode.TIMELINE -> state.timelineRecords.isEmpty()
DisplayMode.LATEST -> state.latestRecords.isEmpty()
}
Crossfade(
targetState = isEmpty,
animationSpec = tween(durationMillis = 500),
label = "ContentCrossfade"
) { isListEmpty ->
if (isListEmpty){
Box(
modifier = Modifier
.fillMaxSize(),
contentAlignment = Alignment.Center
) {
EmptyMessageView(
message = "没有记录"
)
}
} else {
LazyColumn {
when(displayMode){
DisplayMode.AGGREGATE -> {
state.groupedRecords.forEach { (date, details) ->
LazyColumn { val dailyTotalTime = details.sumOf { it.readTime }
when(displayMode){
DisplayMode.AGGREGATE -> {
state.groupedRecords.forEach { (date, details) ->
val dailyTotalTime = details.sumOf { it.readTime } stickyHeader {
DateHeader(date, dailyTotalTime)
}
stickyHeader { items(
DateHeader(date, dailyTotalTime) items = details,
key = { it.bookName + it.readTime.toString() }
) { detail ->
ReadRecordItem(
detail = detail,
viewModel = viewModel,
onClick = { onBookClick(detail.bookName) },
onDelete = { viewModel.deleteDetail(detail) },
modifier = Modifier.animateItem()
)
}
} }
}
DisplayMode.TIMELINE -> {
state.timelineRecords.forEach { (date, sessions) ->
val dailyTotalTime = sessions.sumOf { it.endTime - it.startTime }
stickyHeader { DateHeader(date, dailyTotalTime) }
items( val timelineItems = sessions.mapIndexed { index, session ->
items = details, val previousSession = sessions.getOrNull(index - 1)
key = { it.bookName + it.readTime.toString() }
) { detail -> val showHeader = index == 0 || session.bookName != previousSession?.bookName
ReadRecordItem( TimelineItem(session, showHeader)
detail = detail, }
items(items = timelineItems, key = { it.session.id }) { item ->
TimelineSessionItem(
item = item,
onBookClick = onBookClick,
viewModel = viewModel
)
}
}
}
DisplayMode.LATEST -> {
items(items = state.latestRecords, key = { it.bookName + it.deviceId }) { record ->
LatestReadItem(
record = record,
viewModel = viewModel, viewModel = viewModel,
onClick = { onBookClick(detail.bookName) }, onClick = { onBookClick(record.bookName) },
onDelete = { viewModel.deleteDetail(detail) },
modifier = Modifier.animateItem() modifier = Modifier.animateItem()
) )
} }
} }
} }
DisplayMode.TIMELINE -> {
state.timelineRecords.forEach { (date, sessions) ->
val dailyTotalTime = sessions.sumOf { it.endTime - it.startTime }
stickyHeader { DateHeader(date, dailyTotalTime) }
val timelineItems = sessions.mapIndexed { index, session ->
val previousSession = sessions.getOrNull(index - 1)
val showHeader = index == 0 || session.bookName != previousSession?.bookName
TimelineItem(session, showHeader)
}
items(items = timelineItems, key = { it.session.id }) { item ->
TimelineSessionItem(
item = item,
onBookClick = onBookClick,
viewModel = viewModel
)
}
}
}
DisplayMode.LATEST -> {
items(items = state.latestRecords, key = { it.bookName + it.deviceId }) { record ->
LatestReadItem(
record = record,
viewModel = viewModel,
onClick = { onBookClick(record.bookName) },
modifier = Modifier.animateItem()
)
}
}
} }
} }
}
} }
} }
} }
@@ -232,7 +275,7 @@ fun LatestReadItem(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.clickable(onClick = onClick) .clickable(onClick = onClick)
.padding(16.dp), .padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
BookCoverWithPlaceholder(coverPath) BookCoverWithPlaceholder(coverPath)
@@ -344,11 +387,11 @@ fun TimelineSessionItem(
"时长: ${formatDuring(duration)}", "时长: ${formatDuring(duration)}",
style = MaterialTheme.typography.bodySmall style = MaterialTheme.typography.bodySmall
) )
// Text( // Text(
// "字数: ${session.words}", // "字数: ${session.words}",
// style = MaterialTheme.typography.bodySmall, // style = MaterialTheme.typography.bodySmall,
// color = MaterialTheme.colorScheme.onSurfaceVariant // color = MaterialTheme.colorScheme.onSurfaceVariant
// ) // )
} }
} }
} }
@@ -372,7 +415,7 @@ fun ReadRecordItem(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.clickable(onClick = onClick) .clickable(onClick = onClick)
.padding(16.dp), .padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
BookCoverWithPlaceholder(coverPath) BookCoverWithPlaceholder(coverPath)
@@ -431,9 +474,8 @@ fun DateHeader(
dailyTotalTime: Long dailyTotalTime: Long
) { ) {
val dateText = formatFriendlyDate(date) val dateText = formatFriendlyDate(date)
val totalTimeText = "阅读时长: ${formatDuring(dailyTotalTime)}" val totalTimeText = "已读 ${formatDuring(dailyTotalTime)}"
Surface( Surface(
color = MaterialTheme.colorScheme.surface,
tonalElevation = 2.dp, tonalElevation = 2.dp,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
@@ -446,8 +488,6 @@ fun DateHeader(
color = MaterialTheme.colorScheme.secondary color = MaterialTheme.colorScheme.secondary
) )
Spacer(modifier = Modifier.height(4.dp))
Text( Text(
text = totalTimeText, text = totalTimeText,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
@@ -459,22 +499,36 @@ fun DateHeader(
@Composable @Composable
fun TotalTimeHeader(time: Long) { fun TotalTimeHeader(time: Long) {
Column( Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainer
),
shape = MaterialTheme.shapes.medium,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .padding(horizontal = 16.dp, vertical = 8.dp)
.background(MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.2f))
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text("总阅读时长", style = MaterialTheme.typography.labelMedium) Column(
Text( modifier = Modifier
text = formatDuring(time), .padding(12.dp),
style = MaterialTheme.typography.headlineSmall, horizontalAlignment = Alignment.Start
fontWeight = FontWeight.Bold ) {
) Text(
text = "阅读时长",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = formatDuring(time),
style = MaterialTheme.typography.labelLarge,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
}
} }
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SearchBarSection( fun SearchBarSection(