优化阅读记录界面
This commit is contained in:
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(
|
||||||
|
|||||||
Reference in new issue
Block a user