下面的代码我把导入第三方包相关的代码都省略了
1. 主页面
-
HomeScreen:这是一个主屏幕的界面,它接收一个GoogleSignInClient对象、一个NavController对象和一个可选的modifier参数。它使用了LocalFocusManager来管理焦点,以及LocalBooksViewModel和LocalreadingRecordViewModel来获取和更新书籍和阅读记录的状态。它还包含了一个搜索框,用于过滤正在阅读的书籍列表,并且有一个悬浮按钮用于添加书籍。
-
Books:这个Composable函数用于显示一个书籍列表。它接收一个NavController对象、一个书籍列表books、一个可选的modifier参数、一个BookViewModel和一个ReadingRecordViewModel。它使用LazyColumn来懒加载列表项,并为每本书创建一个卡片,显示书籍的标题、封面、阅读状态和其他信息。还有一个按钮用于编辑书籍的笔记。
-
InputPageNumberDialogButton:这个Composable函数用于显示一个按钮,当点击时会弹出一个对话框,允许用户输入当前阅读的页码。它还包含了逻辑来更新书籍的阅读进度和阅读记录。
-
SmallAddButton:这是一个小的悬浮按钮,用于触发添加书籍的操作。
-
ExpandedDropdownMenuExample:这是一个下拉菜单,用于更改书籍的阅读状态(例如:正在阅读、已完成、搁置)。
app/src/main/java/com/example/BookRecord/HomeScreen.kt:
kotlin
@Composable
fun HomeScreen(
googleSignInClient: GoogleSignInClient,
navController: NavController,
//auth: FirebaseAuth, // 传入FirebaseAuth实例
modifier: Modifier = Modifier,
){
// 鼠标的焦点
val focusManager = LocalFocusManager.current
// 搜索文本状态
var searchText by remember { mutableStateOf("") }
val bookViewModel = LocalBooksViewModel.current
val readingRecordViewModel = LocalreadingRecordViewModel.current
// 获取正在读的书籍列表
val readingBooks by bookViewModel.readingBooks.observeAsState(initial = emptyList())
// 搜索框和列表布局
// 使得 Column 可点击,并在点击时清除焦点
Scaffold(
floatingActionButton = {
SmallAddButton(onClick = {
// 在这里定义点击悬浮按钮后的动作,比如打开添加图书的界面
//navController.navigate("AddBooks")
bookViewModel.addBook(
bookTitle = "The Great Gatsby",
bookImage = "https://images.app.goo.gl/5zUzBVRjTqKxDdeV8",
author = "F. Scott Fitzgerald",
pages = "300",
status = BookStatus.READING,
readPage = "0",
press = "xxx",
startTime = LocalDate.now()
)
})
},
floatingActionButtonPosition = FabPosition.End, // 将按钮放在右下角
){ paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.clickable(onClick = { focusManager.clearFocus() })
.padding(16.dp)
){
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically // 垂直居中对齐 Row 内的元素
) {
Column(modifier = Modifier.weight(9f)){
Text(
text = "Books you are reading",
fontSize = 25.sp,
fontWeight = FontWeight.Bold,
color = Color(0xFF6650a4),
modifier = Modifier.padding(end = 5.dp) // 根据需要调整文本的右边距
)
}
Column(modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally){
IconButton(
onClick = {
FirebaseAuth.getInstance().signOut()
googleSignInClient.signOut()
navController.navigate("LoginScreen"){
popUpTo(0) { inclusive = true }
}
}) {
Icon(
imageVector = Icons.Filled.Settings,
contentDescription = "Setting",
tint = Color(0xFF6650a4),
modifier = Modifier.size(40.dp)
)
}
}
}
// 搜索框
OutlinedTextField(
value = searchText,
onValueChange = { searchText = it },
label = { Text("Filter book", color = Color(0xFF6650a4)) },
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 4.dp)
.background(Color(0xFFF2F2F2), RoundedCornerShape(20.dp)), // 设置浅灰色背景和圆角形状
shape = RoundedCornerShape(20.dp), // 设置输入框的四个角更圆滑
trailingIcon = { // 在搜索框的右侧添加一个搜索图标
Icon(
imageVector = Icons.Filled.FilterAlt,
contentDescription = "Search",
modifier = Modifier.clickable { focusManager.clearFocus() }
)
},
)
// 基于搜索文本过滤整个书籍列表,而不仅仅是标题
val filteredBooks = readingBooks.filter { it.title.contains(searchText, ignoreCase = true) }
// 将过滤后的书籍列表传递给 Books 函数
Books(navController, books = filteredBooks,bookViewModel = bookViewModel, readingRecordViewModel = readingRecordViewModel)
}
}
}
@Composable
fun Books(navController: NavController, books: List<Book>, modifier: Modifier = Modifier,bookViewModel:BookViewModel,readingRecordViewModel:ReadingRecordViewModel) {
val context = LocalContext.current
LazyColumn {
items(books) { book -> // 正确使用 books 作为列表
Card(
modifier = Modifier
.padding(4.dp)
.fillMaxWidth()
){
Row(
modifier = Modifier
.padding(10.dp)
.fillMaxWidth(), // 确保 Row 填充父容器宽度
) {
// 用 Image 替换原有的 Column 显示书名
Box(modifier = Modifier
){
Image(
// 把图片替换成 Book.image
painter = painterResource(id = R.drawable.book1), // 确保你的图片资源正确
contentDescription = "Book Image",
modifier = Modifier
.size(100.dp)
)
}
Box(
modifier = Modifier
.weight(1f)
) {
Row(modifier = Modifier
.padding(top = 0.dp, start = 15.dp)
){
Text(text = book.title, fontSize = 13.sp)
}
Row(
modifier = Modifier
.padding(top = 40.dp, start = 15.dp)
) {
// 这个column 是一个 Expanded Dropdown Menu
Column(
modifier = Modifier
.weight(0.5f)
){
ExpandedDropdownMenuExample(bookViewModel= bookViewModel, book=book)
}
// 这个column 是一个方形的 button
Column(
modifier = Modifier
.weight(0.5f)
){
OutlinedButton(
modifier = Modifier
.height(30.dp)
.width(100.dp),
contentPadding = PaddingValues(),
shape = RoundedCornerShape(5.dp),
onClick ={navController.navigate("EditNotesScreen/${book.id}")},
//navController.navigate("EditNotesScreen/${book.id}")
) {
Text(text = "notes", fontSize = 15.sp)
Icon(
imageVector = Icons.Filled.EditNote,
contentDescription = "Select",
tint = Color(0xFF6650a4),
modifier = Modifier
.size(25.dp)
.padding(start = 5.dp, top = 5.dp)
)
}
}
}
Row(modifier = Modifier
.padding(start = 15.dp,top = 72.dp)
){
Column(
modifier = Modifier.weight(3.5f)
) {
Text(text = "From", fontSize = 12.sp)
val formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd")
val dateString = book.startTime.format(formatter)
Text(text = dateString, fontSize = 12.sp)
}
// 已经读了多少天
Column(
modifier = Modifier
.weight(2.5f)
) {
// 计算从开始阅读到现在的天数
val daysRead = ChronoUnit.DAYS.between(book.startTime, LocalDate.now()).toInt()
Text(text = daysRead.toString(), fontSize = 12.sp)
Text(text = "days", fontSize = 12.sp)
}
Column(
modifier = Modifier
.weight(4f)
){
Row(){
InputPageNumberDialogButton(bookViewModel=bookViewModel,book = book,readingRecordViewModel = readingRecordViewModel)
Box(modifier = Modifier.padding(top = 13.dp,start = 2.dp)){
Text(text = "/${book.pages}", fontSize = 12.sp)
}
}
}
}
}
}
}
}
}
}
@Composable
fun InputPageNumberDialogButton(bookViewModel: BookViewModel, book: Book,readingRecordViewModel: ReadingRecordViewModel ) {
var showDialog by remember { mutableStateOf(false) }
var pageNumber by remember { mutableStateOf(book.readpage.toString()) }
// Button to show dialog
Box(modifier = Modifier.padding(top = 5.dp)) {
OutlinedButton(
modifier = Modifier
.height(25.dp)
.width(50.dp),
contentPadding = PaddingValues(),
shape = RoundedCornerShape(5.dp),
onClick = {
showDialog = true // Show dialog when button is clicked
},
) {
Text(text = pageNumber, fontSize = 15.sp)
}
}
// Dialog for input
if (showDialog) {
Dialog(onDismissRequest = { showDialog = false }) {
// Dialog content
Surface(
shape = MaterialTheme.shapes.medium,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Enter the page number", style = MaterialTheme.typography.bodyLarge)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = pageNumber,
onValueChange = { value ->
// Update only if the input is numeric
if (value.all { it.isDigit() }) {
pageNumber = value
}
},
singleLine = true,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = {
val oldPageNumber = book.readpage.toIntOrNull() ?: 0
pageNumber.toIntOrNull()?.let { newPageNumber ->
book.readpage = newPageNumber.toString()
bookViewModel.updateBookReadPage(book, newPageNumber.toString())
val pagesToAdd = newPageNumber - oldPageNumber
if (pagesToAdd > 0) {
val newReadingRecord = ReadingRecord(
userId = book.userId,
date = LocalDate.now(),
readPages = pagesToAdd
)
readingRecordViewModel.insertReadingRecord(newReadingRecord)
}
showDialog = false
}
}
) {
Text("Done", fontSize = 15.sp)
}
}
}
}
}
}
@Composable
fun SmallAddButton(onClick: () -> Unit) {
FloatingActionButton(
onClick = { onClick() },
modifier = Modifier.padding( bottom = 35.dp)
) {
Icon(Icons.Filled.Add, "Small floating action button.")
}
}
@Composable
fun ExpandedDropdownMenuExample(bookViewModel: BookViewModel, book: Book) {
var expanded by remember { mutableStateOf(false) }
val items = listOf("Reading", "Complete", "Lay Aside") // 匹配枚举的描述
val statuses = listOf(BookStatus.READING, BookStatus.READ, BookStatus.ON_HOLD)
var selectedIndex by remember { mutableStateOf(statuses.indexOf(book.status)) } // 初始选择基于书籍的当前状态
val icons = listOf(Icons.Filled.MenuBook, Icons.Filled.Done, Icons.Filled.DeleteForever) // 对应状态的图标
Column(modifier = Modifier.fillMaxWidth()) {
OutlinedButton(
modifier = Modifier
.height(30.dp)
.width(100.dp),
contentPadding = PaddingValues(start = 5.dp),
shape = RoundedCornerShape(5.dp),
onClick = { expanded = true },
) {
Icon(
imageVector = icons[selectedIndex], // 显示当前选择的状态图标
contentDescription = "Select",
tint = Color(0xFF6650a4),
modifier = Modifier
.size(20.dp)
.padding(end = 3.dp)
)
Text(text = items[selectedIndex], fontSize = 15.sp)
Icon(
imageVector = Icons.Filled.KeyboardArrowDown,
contentDescription = "Select",
tint = Color(0xFF6650a4),
modifier = Modifier.size(20.dp)
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
items.forEachIndexed { index, title ->
DropdownMenuItem(
text = { Text(title) },
onClick = {
selectedIndex = index
expanded = false
// 更新书籍状态
bookViewModel.updateBookStatus(book, statuses[index])
},
leadingIcon = { Icon(icons[index], contentDescription = null) } // 为每个选项添加对应的图标
)
}
}
}
}
2. 书架页面
以下是代码的概括性解释:
-
BookShelf:这是一个书架界面的Composable函数,它接收一个NavController对象和一个可选的modifier参数。它使用了LocalFocusManager来管理焦点,以及LocalBooksViewModel来获取书籍的状态。它还包含了一个搜索框,用于过滤书架上的书籍列表,并且有一个可滚动的Tab行,用于在"读完"和"搁置"的书籍列表之间切换。
-
BookGrid:这个Composable函数用于以网格形式显示书籍列表。它接收一个NavController对象、一个BookViewModel和一个书籍列表books,以及一个可选的modifier参数。它使用LazyVerticalGrid来懒加载网格中的列表项,并为每本书创建一个卡片,显示书籍的封面和笔记数量。点击书籍封面会导航到一个笔记屏幕。
以下是代码中一些关键点的详细解释:
- BookShelf中的selectedTabIndex用于跟踪当前选中的Tab标签,它决定了显示"读完"的书籍列表还是"搁置"的书籍列表。
- BookShelf中的ScrollableTabRow组件用于创建可滚动的Tab行,用户可以通过点击不同的Tab来切换显示的书籍列表。
- BookGrid中的LazyVerticalGrid组件用于创建一个垂直滚动的网格布局,每行显示固定数量的书籍。
- BookGrid中的Card组件用于为每本书创建一个卡片,卡片中包含一个Image组件来显示书籍封面,以及一个Row组件来显示笔记图标和数量。
- BookGrid中的Image组件的点击事件被设置为导航到笔记屏幕,这通常用于查看或编辑书籍的笔记。
app/src/main/java/com/example/BookRecord/BookShelf.kt:
kotlin
@Composable
fun BookShelf(
navController: NavController,
modifier: Modifier = Modifier,
){
// 鼠标的焦点
val focusManager = LocalFocusManager.current
// 搜索文本状态
var searchText by remember { mutableStateOf("") }
//tab的标签
var selectedTabIndex by remember { mutableStateOf(0) }
val tabTitles = listOf("complete", "lay aside")
val bookViewModel = LocalBooksViewModel.current
// 获取读完的书籍列表
val completeBooks by bookViewModel.completeBooks.observeAsState(initial = emptyList())
// 获取搁置的书籍列表
val layasideBooks by bookViewModel.layasideBooks.observeAsState(initial = emptyList())
// 根据选中的Tab来决定展示哪个列表
val booksToShow = if (selectedTabIndex == 0) completeBooks else layasideBooks
// 搜索框和列表布局
// 使得 Column 可点击,并在点击时清除焦点
Column(
modifier = modifier
.clickable(onClick = { focusManager.clearFocus() })
.padding(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth()
.padding(0.dp),
verticalAlignment = Alignment.CenterVertically // 垂直居中对齐 Row 内的元素
) {
Column(modifier = Modifier.weight(9f)) {
Text(
text = "Bookshelf",
fontSize = 25.sp,
fontWeight = FontWeight.Bold,
color = Color(0xFF6650a4),
modifier = Modifier.padding(end = 5.dp) // 根据需要调整文本的右边距
)
}
Column(
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally
) {
// 使用一个图标,表示设置
Icon(
imageVector = Icons.Filled.Settings,
contentDescription = "Setting",
tint = Color(0xFF6650a4),
modifier = Modifier.size(40.dp) // 根据需要调整图标的大小
)
}
}
// 搜索框
OutlinedTextField(
value = searchText,
onValueChange = { searchText = it },
label = { Text("Filter book", color = Color(0xFF6650a4)) },
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 4.dp)
.background(Color(0xFFF2F2F2), RoundedCornerShape(20.dp)), // 设置浅灰色背景和圆角形状
shape = RoundedCornerShape(20.dp), // 设置输入框的四个角更圆滑
trailingIcon = { // 在搜索框的右侧添加一个搜索图标
Icon(
imageVector = Icons.Filled.FilterAlt,
contentDescription = "Search",
modifier = Modifier.clickable { focusManager.clearFocus() }
)
},
)
// Tab 行
ScrollableTabRow(
selectedTabIndex = selectedTabIndex,
edgePadding = 0.dp,
contentColor = Color(0xFF6650a4),
indicator = {},
divider = {}) {
tabTitles.forEachIndexed { index, title ->
Tab(selected = selectedTabIndex == index,
onClick = { selectedTabIndex = index },
text = {
Text(title,
fontSize = 16.sp ,
color = if (selectedTabIndex == index) Color(0xFF6650a4) else Color.Gray) })
}
}
// 图书网格列表
BookGrid(navController,viewModel= bookViewModel, books = booksToShow.filter { it.title.contains(searchText, ignoreCase = true) })
}
}
@Composable
fun BookGrid(navController:NavController,viewModel: BookViewModel,books: List<Book>, modifier: Modifier = Modifier) {
val columns = 3 // 定义每行显示的书籍数量
LazyVerticalGrid(
columns = GridCells.Fixed(columns),
modifier = modifier.padding(4.dp)
) {
items(books) { book ->
val noteCount = viewModel.getNoteCountByBookId(book.id).observeAsState(initial = 0)
Card(
modifier = Modifier
.aspectRatio(3f / 4f), // 假设每本书的尺寸比例为3:4
colors = CardDefaults.cardColors(containerColor = Color.White) // 设置卡片背景色为白色
) {
Box {
Image(
painter = painterResource(id = R.drawable.book1), // 你的图片资源
contentDescription = "Book Image",
modifier = Modifier
.fillMaxSize() // 填满 Card
.clickable {
navController.navigate("notesScreen/${book.id}")
}
)
// 笔记图标和数量显示在右下角
Row(
modifier = Modifier
.align(Alignment.BottomEnd)
) {
Icon(
imageVector = Icons.Default.Bookmark,
contentDescription = "Note Icon",
tint = Color.Gray, // 图标颜色,根据需要调整
modifier = Modifier.size(20.dp)
)
// 假设笔记数量为示例用,你可以根据实际情况动态设置
Text(
text = "${noteCount.value}",
color = Color.Gray,
fontSize = 15.sp,
)
}
}
}
}
}
}
3. 数据分析页面
-
颜色常量定义:定义了一系列颜色常量,用于图表、文本和背景等UI元素的颜色设置。
-
数据示例:提供了条形图和饼图的示例数据,但这些数据被注释掉了,表明它们可能用于测试或示例。
-
BarChart:这是一个Composable函数,用于绘制一个简单的条形图,显示每天阅读的页数。它使用Canvas API来绘制条形和文本。
-
DisplayTodayDate:这个Composable函数显示今天的日期,格式为"dd/MM/yyyy"。
-
PieChart:这是一个Composable函数,用于绘制一个饼图,显示书籍状态的分布。它使用Canvas API来绘制饼图的扇区和中心文本。
-
Legend:这个Composable函数为饼图提供一个图例,显示每个类别的颜色块和对应的数量。
-
BookStatusPieChart:这个Composable函数组合了PieChart和Legend,创建了一个书籍状态饼图及其图例。
-
TimeRangeSelection:这个Composable函数允许用户选择时间范围,例如"最近7天"或"最近15天",并更新状态。
-
AnalyticsPage:这是一个主要的Composable函数,它组合了上述函数来构建一个完整的阅读统计页面。它包括页面标题、日期显示、时间范围选择、条形图、总阅读页数显示和书籍状态饼图。
-
AnalyticsScreen:这个Composable函数可能是分析页面的入口点,它调用AnalyticsPage来显示内容。
app/src/main/java/com/example/BookRecord/chart.kt:
kotlin
// Define your color constants
val colorPrimary = Color(0xFF8C73B8)
val colorOnPrimary = Color(0xFFCCCCCC) // for text on primary color background
val colorRead = Color(0xBEB09AD8) // Green
val colorUnread = Color(0xD29CC9E7) // Red
val colorReading = Color(0xFF9EE0C2) // Blue
val colorBackground = Color(0xFFFFFFFF) // White or any other background color
// Sample data for the bar chart, showing pages read each day.
//val readingData7Days = listOf(5, 2, 7, 3, 5, 4, 13) // 近7天
//val readingData15Days = List(15) { (1..20).random() } // 随机生成近30天的数据
// Sample data for the pie chart, showing book status distribution.
//val bookShelfData = mapOf("have read" to 33, "lay aside" to 12, "reading" to 6)
@Composable
fun BarChart(data: List<ReadingRecordDao.DailyReading>, modifier: Modifier = Modifier
.fillMaxWidth()
.height(200.dp)) {
Canvas(modifier = modifier) {
val maxCount = data.maxOfOrNull { it.totalPages } ?: 1 // 保证除数不为零
val barWidth = size.width / (data.size * 2f)
data.forEachIndexed { index, dailyReading ->
val barHeight = size.height * (dailyReading.totalPages.toFloat() / maxCount)
val barTopLeft = Offset((barWidth / 2 + barWidth * 2 * index), size.height - barHeight)
drawRect(
color = colorPrimary, //
topLeft = barTopLeft,
size = Size(barWidth, barHeight)
)
// 新增部分,用于绘制文本
val textPaint = android.graphics.Paint().apply {
color = android.graphics.Color.BLACK // 文本颜色
textSize = 35f // 文本大小
textAlign = android.graphics.Paint.Align.CENTER // 文本对齐方式
}
drawContext.canvas.nativeCanvas.drawText(
dailyReading.totalPages.toString(), // 要绘制的文本
barTopLeft.x + barWidth / 2, // 文本的x坐标,使其居中于柱子之上
barTopLeft.y - 5f, // 文本的y坐标,略高于柱子顶部
textPaint // 使用的画笔
)
}
}
}
@RequiresApi(0)
@Composable
fun DisplayTodayDate() {
Column(modifier = Modifier.padding(0.dp)) {
val formatter = org.threeten.bp.format.DateTimeFormatter.ofPattern("dd/MM/yyyy")
Spacer(modifier = Modifier.height(10.dp))
androidx.compose.material3.Text(
text = "Data Until: ${LocalDate.now().format(formatter)}"
)
}
}
@Composable
fun PieChart(data: Map<String, Int>, modifier: Modifier = Modifier.size(150.dp)) {
Canvas(modifier = modifier) {
val total = data.values.sum()
val center = Offset(size.width / 2, size.height / 2)
val radius = size.minDimension / 2
val holeRadius = radius * 0.6f // 中心空洞的半径
var startAngle = -90f // 扇形图的起始角度
data.forEach { (category, count) ->
val sweepAngle = (count / total.toFloat()) * 360f // 扇形图的角度
val color = when (category) { // 根据分类获取颜色
"have read" -> colorRead
"lay aside" -> colorUnread
"reading" -> colorReading
else -> Color.LightGray
}
drawArc(
color = color, // 使用上面定义的颜色
startAngle = startAngle,
sweepAngle = sweepAngle,
useCenter = false, // 画环形
topLeft = Offset(center.x - radius, center.y - radius),
size = Size(radius * 2, radius * 2), // 环形的尺寸
style = Stroke(width = radius - holeRadius) // 环形的宽度
)
// 如果需要在每个扇区中绘制数字,可以在这里添加代码
startAngle += sweepAngle // 下一个扇形的起始角度
}
// 绘制中心的文本
val textPaint = android.graphics.Paint().apply {
color = android.graphics.Color.BLACK // 文本颜色
textSize = 40f // 文本大小
textAlign = android.graphics.Paint.Align.CENTER // 文本对齐方式
}
drawContext.canvas.nativeCanvas.drawText(
"$total Books", // 中心的文字
center.x, // 中心点x坐标
center.y - (textPaint.ascent() + textPaint.descent()) / 2, // 中心点y坐标,垂直居中
textPaint // 使用的画笔
)
}
}
@Composable
fun Legend(data: Map<String, Int>, modifier: Modifier) {
Column(modifier = modifier.padding(10.dp)) {
data.forEach { (category, count) ->
Row(verticalAlignment = Alignment.CenterVertically) {
// 这里绘制标签颜色的小方块
Box(
modifier = Modifier
.size(20.dp)
.background(
when (category) {
"have read" -> colorRead
"lay aside" -> colorUnread
"reading" -> colorReading
else -> Color.LightGray
}
)
)
Spacer(modifier = Modifier.width(4.dp))
// 这里是类别名称和数量
Text(text = "$category $count",modifier = Modifier.weight(1f))
}
Spacer(modifier = Modifier.height(2.dp))
}
}
}
@Composable
fun BookStatusPieChart(data: Map<String, Int>) {
Row(
modifier = Modifier
.fillMaxWidth()
.height(IntrinsicSize.Min)
.padding(start = 35.dp), // 这里添加左边距
verticalAlignment = Alignment.CenterVertically
) {
// 饼状图占据行的一半宽度
PieChart(data = data)
Spacer(modifier = Modifier.width(60.dp))
// 标签列表占据剩下的宽度
Legend(data = data, modifier = Modifier.weight(1f))
}
}
@Composable
fun TimeRangeSelection(timeRange: String, onTimeRangeSelected: (String) -> Unit) {
Column(verticalArrangement = Arrangement.Bottom) {
Button(
onClick = { onTimeRangeSelected("Last 7 Days") },
modifier = Modifier
.padding(end = 0.dp)
.size(129.dp, 35.dp),// 在按钮的右边添加间距
colors = ButtonDefaults.buttonColors(backgroundColor = if (timeRange == "Las 7 Days") colorOnPrimary else Color.White)
) {
Text("Last 7 Days")
}
Spacer(modifier = Modifier.height(7.dp))
Button(
modifier = Modifier.size(129.dp,35.dp),
onClick = { onTimeRangeSelected("Last 15 Days") },
colors = ButtonDefaults.buttonColors(backgroundColor = if (timeRange == "Las 7 Days") colorOnPrimary else Color.White)
) {
Text("Last 15 Days")
}
}
}
@Composable
fun AnalyticsPage() {
val bookViewModel: BookViewModel = viewModel() // 获取ViewModel
val readingRecordViewModel: ReadingRecordViewModel = viewModel() // 获取ReadingRecordViewModel
// 使用compose的方式观察LiveData
val bookCounts by bookViewModel.bookCounts.observeAsState(initial = mapOf(
"have read" to 0,
"lay aside" to 0,
"reading" to 0
))
val readPagesLast7Days by readingRecordViewModel.readPagesLast7Days.observeAsState(emptyList())
val readPagesLast15Days by readingRecordViewModel.readPagesLast15Days.observeAsState(emptyList())
// State for the time range selection for the bar chart.
var timeRange by remember { mutableStateOf("Last 7 Days") }
// Function to calculate total pages read in the selected time range.
// Function to calculate total pages read in the selected time range.
fun calculateTotalPages(readings: List<ReadingRecordDao.DailyReading>): Int {
return readings.sumOf { it.totalPages }
}
// State for the total pages read.
//val totalPages by derivedStateOf { mutableStateOf(calculateTotalPages(if (timeRange == "Last 7 Days") readPagesLast7Days else readPagesLast15Days)) }
val totalPages by derivedStateOf {
calculateTotalPages(if (timeRange == "Last 7 Days") readPagesLast7Days else readPagesLast15Days)
}
val scrollState = rememberScrollState()//创建可滚动状态
Column(modifier = Modifier
.fillMaxSize()
.padding(15.dp)
.verticalScroll(scrollState)
) { // 加上padding使内容不要紧贴屏幕边缘
androidx.compose.material3.Text(
text = "Reading Statistics",
fontSize = 25.sp,
fontWeight = FontWeight.Bold,
color = Color(0xFF6650a4),
modifier = Modifier.padding(end = 5.dp) // 根据需要调整文本的右边距
)
Spacer(modifier = Modifier.height(20.dp)) //
Text(
text = "✔️ Page Count Statistics",
style = MaterialTheme.typography.h6,
color = Color(0xFF01051D),
modifier = Modifier.align(Alignment.Start) //
)
Spacer(modifier = Modifier.height(15.dp))
Row(modifier = Modifier.fillMaxWidth()) {
DisplayTodayDate()
Spacer(modifier = Modifier.weight(1f)) // 这个Spacer会占据所有可用空间
TimeRangeSelection(timeRange) { selectedRange ->timeRange = selectedRange}
}
Spacer(modifier = Modifier.height(15.dp)) // 添加间隔
BarChart(data = if (timeRange == "Last 7 Days") readPagesLast7Days else readPagesLast15Days)
Spacer(modifier = Modifier.height(15.dp)) // 添加间隔
Text(
text = " Total Pages Read in the $timeRange: $totalPages",
style = MaterialTheme.typography.h6,
fontSize = 15.sp,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Spacer(modifier = Modifier.height(50.dp)) // 标题和扇形图之间的间隔
Text(
"📖 Book Status",
style = MaterialTheme.typography.h6,
color = Color(0xFF01051D),
modifier = Modifier.align(Alignment.Start) //
)
Spacer(modifier = Modifier.height(60.dp)) // 标题和扇形图之间的间隔
BookStatusPieChart(data = bookCounts)
}
}
@Composable
fun AnalyticsScreen(navController: NavController, modifier: Modifier = Modifier) {
AnalyticsPage()
}