优化阅读记录总览(在阅读记录的成就卡片进入,稳定后会加入主页面)

This commit is contained in:
HapeLee
2026-05-13 01:58:31 +08:00
parent b3c4f7d09f
commit c1f7faf050
7 changed files with 119 additions and 41 deletions
@@ -69,6 +69,18 @@ class GetReadRecordOverviewUseCase {
val dailyTimeData = if (period == ReadPeriod.ALL) {
emptyList()
} else if (period == ReadPeriod.YEAR) {
val dateToTime = filteredDetails.groupBy {
LocalDate.parse(it.date, DateTimeFormatter.ISO_LOCAL_DATE).with(TemporalAdjusters.firstDayOfMonth())
}.mapValues { it.value.sumOf { d -> d.readTime } }
val monthList = mutableListOf<Pair<LocalDate, Long>>()
var curr = startDate.with(TemporalAdjusters.firstDayOfMonth())
while (!curr.isAfter(endDate)) {
monthList.add(curr to (dateToTime[curr] ?: 0L))
curr = curr.plusMonths(1)
}
monthList
} else {
val dateToTime = filteredDetails.groupBy { it.date }
.mapValues { it.value.sumOf { d -> d.readTime } }
@@ -29,7 +29,7 @@ import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.button.MediumIconButton
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.cover.Cover
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.heatmap.HeatmapMode
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
@@ -223,8 +223,12 @@ fun TopReadingListCard(
.fillMaxWidth()
.adaptiveHorizontalPadding(vertical = 8.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
AppText("阅读时长榜", style = LegadoTheme.typography.titleMedium)
Column(modifier = Modifier.padding(vertical = 16.dp)) {
AppText(
modifier = Modifier.padding(horizontal = 16.dp),
text = "阅读时长榜",
style = LegadoTheme.typography.titleMedium
)
Spacer(modifier = Modifier.height(8.dp))
topBooks.forEachIndexed { index, book ->
@@ -237,7 +241,7 @@ fun TopReadingListCard(
modifier = Modifier
.fillMaxWidth()
.clickable { onBookClick(book.bookName, book.bookAuthor) }
.padding(vertical = 8.dp),
.padding(vertical = 8.dp, horizontal = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
AppText(
@@ -247,7 +251,12 @@ fun TopReadingListCard(
textAlign = TextAlign.Center,
color = if (index < 3) LegadoTheme.colorScheme.primary else LegadoTheme.colorScheme.onSurfaceVariant
)
Cover(path = coverPath, modifier = Modifier.size(40.dp, 56.dp))
CoilBookCover(
name = book.bookName,
author = book.bookAuthor,
path = coverPath,
modifier = Modifier.width(40.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Column(modifier = Modifier.weight(1f)) {
AppText(
@@ -264,6 +273,7 @@ fun TopReadingListCard(
)
}
AppText(
modifier = Modifier.padding(end = 8.dp),
text = ReadRecordFormatter.formatDuration(book.readTime),
style = LegadoTheme.typography.bodySmall,
color = LegadoTheme.colorScheme.primary
@@ -355,10 +365,14 @@ fun CalendarDayCell(
.background(LegadoTheme.colorScheme.surfaceVariant),
contentAlignment = Alignment.Center
) {
if (coverPath != null) {
Cover(
if (topBook != null) {
CoilBookCover(
name = topBook.first,
author = topBook.second,
path = coverPath,
modifier = Modifier.fillMaxSize().alpha(0.4f)
modifier = Modifier
.fillMaxSize()
.alpha(0.4f)
)
}
@@ -83,7 +83,7 @@ import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.checkBox.CheckboxItem
import io.legado.app.ui.widget.components.cover.Cover
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.heatmap.HEATMAP_CALENDAR_TITLE
import io.legado.app.ui.widget.components.heatmap.HeatmapCalendarEndAction
import io.legado.app.ui.widget.components.heatmap.HeatmapCalendarStartAction
@@ -728,7 +728,12 @@ fun LatestReadItem(
.adaptiveHorizontalPadding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Cover(coverPath)
CoilBookCover(
name = record.bookName,
author = record.bookAuthor,
path = coverPath,
modifier = Modifier.width(44.dp)
)
Spacer(modifier = Modifier.width(16.dp))
@@ -839,7 +844,12 @@ fun TimelineSessionItem(
Row(
verticalAlignment = Alignment.CenterVertically
) {
Cover(coverPath)
CoilBookCover(
name = session.bookName,
author = session.bookAuthor,
path = coverPath,
modifier = Modifier.width(44.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Column {
AppText(
@@ -889,7 +899,12 @@ fun ReadRecordItem(
.adaptiveHorizontalPadding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Cover(coverPath)
CoilBookCover(
name = detail.bookName,
author = detail.bookAuthor,
path = coverPath,
modifier = Modifier.width(44.dp)
)
Spacer(modifier = Modifier.width(16.dp))
@@ -1017,24 +1032,25 @@ fun ReadingSummaryCard(
}
if (bookNamesForCover.isNotEmpty()) {
BookStackView(coverPaths = coverPaths)
val combined = bookNamesForCover.zip(coverPaths)
BookStackView(books = combined)
}
}
}
}
@Composable
fun BookStackView(coverPaths: List<String?>) {
fun BookStackView(books: List<Pair<Pair<String, String>, String?>>) {
val xOffsetStep = 12.dp
val stackWidth = 48.dp + (xOffsetStep * (coverPaths.size - 1).coerceAtLeast(0))
val stackWidth = 44.dp + (xOffsetStep * (books.size - 1).coerceAtLeast(0))
Box(
modifier = Modifier
.width(stackWidth)
.height(72.dp),
.height(64.dp),
contentAlignment = Alignment.CenterStart
) {
coverPaths.forEachIndexed { index, path ->
books.forEachIndexed { index, (info, path) ->
Box(
modifier = Modifier
.padding(start = xOffsetStep * index)
@@ -1046,7 +1062,12 @@ fun BookStackView(coverPaths: List<String?>) {
shape = RoundedCornerShape(4.dp),
color = Color.Transparent
) {
Cover(path = path)
CoilBookCover(
name = info.first,
author = info.second,
path = path,
modifier = Modifier.width(44.dp)
)
}
}
}
@@ -14,7 +14,6 @@ import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.formatReadDuration
import java.time.LocalDate
@Composable
@@ -48,7 +47,7 @@ fun ReadingTimeBarChartCard(
horizontalAlignment = Alignment.End
) {
AppText(
text = formatReadDuration(maxTime),
text = formatChartDuration(maxTime),
style = LegadoTheme.typography.labelSmall,
fontSize = 8.sp,
color = LegadoTheme.colorScheme.onSurfaceVariant
@@ -76,16 +75,16 @@ fun ReadingTimeBarChartCard(
val showLabel = when (period) {
ReadPeriod.DAY -> true
ReadPeriod.WEEK -> date.dayOfWeek.value in listOf(2, 4, 6)
ReadPeriod.MONTH -> date.dayOfMonth == 1 || date.dayOfMonth % 7 == 0 || index == data.lastIndex
ReadPeriod.YEAR -> date.dayOfMonth == 1
ReadPeriod.WEEK -> true
ReadPeriod.MONTH -> date.dayOfMonth == 1 || date.dayOfMonth == 15 || index == data.lastIndex
ReadPeriod.YEAR -> true
else -> false
}
val labelText = when (period) {
ReadPeriod.YEAR -> "${date.monthValue}"
ReadPeriod.WEEK -> when (date.dayOfWeek.value) {
2 -> ""; 4 -> ""; 6 -> ""; else -> ""
1 -> ""; 2 -> ""; 3 -> ""; 4 -> ""; 5 -> ""; 6 -> ""; 7 -> ""; else -> ""
}
else -> date.dayOfMonth.toString()
}
@@ -94,20 +93,26 @@ fun ReadingTimeBarChartCard(
modifier = Modifier
.weight(1f)
.fillMaxHeight(),
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.fillMaxWidth(0.6f)
.fillMaxHeight(heightFactor.coerceAtLeast(0.01f))
.padding(bottom = 4.dp)
.clip(RoundedCornerShape(topStart = 4.dp, topEnd = 4.dp))
.background(
if (time > 0) LegadoTheme.colorScheme.primary
else LegadoTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)
)
)
.weight(1f)
.fillMaxWidth(),
contentAlignment = Alignment.BottomCenter
) {
Box(
modifier = Modifier
.fillMaxWidth(if (period == ReadPeriod.MONTH) 0.8f else 0.6f)
.fillMaxHeight(heightFactor.coerceAtLeast(0.01f))
.padding(horizontal = 1.dp)
.clip(RoundedCornerShape(topStart = 4.dp, topEnd = 4.dp))
.background(
if (time > 0) LegadoTheme.colorScheme.primary
else LegadoTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)
)
)
}
Box(modifier = Modifier.height(20.dp), contentAlignment = Alignment.TopCenter) {
if (showLabel) {
@@ -126,3 +131,10 @@ fun ReadingTimeBarChartCard(
}
}
}
private fun formatChartDuration(millis: Long): String {
val totalMinutes = millis / (1000 * 60)
val hours = totalMinutes / 60
val minutes = totalMinutes % 60
return if (hours > 0) "${hours}h${if (minutes > 0) "${minutes}m" else ""}" else "${minutes}m"
}
@@ -16,7 +16,7 @@ import io.legado.app.ui.book.readRecord.ReadRecordFormatter
import io.legado.app.ui.book.readRecord.ReadRecordViewModel
import io.legado.app.ui.book.readRecord.TimelineItem
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.cover.Cover
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.StringUtils.formatFriendlyDate
@@ -39,7 +39,12 @@ fun LatestReadItem(
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Cover(path = coverPath, modifier = Modifier.size(48.dp, 64.dp))
CoilBookCover(
name = record.bookName,
author = record.bookAuthor,
path = coverPath,
modifier = Modifier.size(48.dp, 64.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
AppText(
@@ -89,7 +94,12 @@ fun TimelineSessionItem(
.padding(horizontal = 16.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Cover(path = coverPath, modifier = Modifier.size(40.dp, 56.dp))
CoilBookCover(
name = session.bookName,
author = session.bookAuthor,
path = coverPath,
modifier = Modifier.size(40.dp, 56.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Column(modifier = Modifier.weight(1f)) {
AppText(
@@ -131,7 +141,12 @@ fun ReadRecordItem(
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Cover(path = coverPath, modifier = Modifier.size(40.dp, 56.dp))
CoilBookCover(
name = detail.bookName,
author = detail.bookAuthor,
path = coverPath,
modifier = Modifier.size(40.dp, 56.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
AppText(
@@ -12,7 +12,7 @@ import io.legado.app.ui.book.readRecord.ReadRecordViewModel
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.cover.Cover
import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.text.AppText
import java.time.format.DateTimeFormatter
@@ -107,11 +107,13 @@ fun BookStackView(
coverPath = viewModel.getBookCover(name, author)
}
Cover(
CoilBookCover(
name = name,
author = author,
path = coverPath,
modifier = Modifier
.padding(end = (index * 12).dp)
.size(32.dp, 44.dp)
.width(32.dp)
)
}
}
@@ -13,6 +13,7 @@ import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@@ -78,6 +79,7 @@ fun CoilBookCover(
Box(
modifier = modifier
.aspectRatio(5f / 7f)
.then(
if (CoverConfig.coverShowShadow) {
Modifier.shadow(4.dp, RoundedCornerShape(4.dp))