前端设计之 主页面、书架页面、数据分析页面

下面的代码我把导入第三方包相关的代码都省略了

1. 主页面

  1. HomeScreen:这是一个主屏幕的界面,它接收一个GoogleSignInClient对象、一个NavController对象和一个可选的modifier参数。它使用了LocalFocusManager来管理焦点,以及LocalBooksViewModel和LocalreadingRecordViewModel来获取和更新书籍和阅读记录的状态。它还包含了一个搜索框,用于过滤正在阅读的书籍列表,并且有一个悬浮按钮用于添加书籍。

  2. Books:这个Composable函数用于显示一个书籍列表。它接收一个NavController对象、一个书籍列表books、一个可选的modifier参数、一个BookViewModel和一个ReadingRecordViewModel。它使用LazyColumn来懒加载列表项,并为每本书创建一个卡片,显示书籍的标题、封面、阅读状态和其他信息。还有一个按钮用于编辑书籍的笔记。

  3. InputPageNumberDialogButton:这个Composable函数用于显示一个按钮,当点击时会弹出一个对话框,允许用户输入当前阅读的页码。它还包含了逻辑来更新书籍的阅读进度和阅读记录。

  4. SmallAddButton:这是一个小的悬浮按钮,用于触发添加书籍的操作。

  5. 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. 书架页面

以下是代码的概括性解释:

  1. BookShelf:这是一个书架界面的Composable函数,它接收一个NavController对象和一个可选的modifier参数。它使用了LocalFocusManager来管理焦点,以及LocalBooksViewModel来获取书籍的状态。它还包含了一个搜索框,用于过滤书架上的书籍列表,并且有一个可滚动的Tab行,用于在"读完"和"搁置"的书籍列表之间切换。

  2. 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. 数据分析页面

  1. 颜色常量定义:定义了一系列颜色常量,用于图表、文本和背景等UI元素的颜色设置。

  2. 数据示例:提供了条形图和饼图的示例数据,但这些数据被注释掉了,表明它们可能用于测试或示例。

  3. BarChart:这是一个Composable函数,用于绘制一个简单的条形图,显示每天阅读的页数。它使用Canvas API来绘制条形和文本。

  4. DisplayTodayDate:这个Composable函数显示今天的日期,格式为"dd/MM/yyyy"。

  5. PieChart:这是一个Composable函数,用于绘制一个饼图,显示书籍状态的分布。它使用Canvas API来绘制饼图的扇区和中心文本。

  6. Legend:这个Composable函数为饼图提供一个图例,显示每个类别的颜色块和对应的数量。

  7. BookStatusPieChart:这个Composable函数组合了PieChart和Legend,创建了一个书籍状态饼图及其图例。

  8. TimeRangeSelection:这个Composable函数允许用户选择时间范围,例如"最近7天"或"最近15天",并更新状态。

  9. AnalyticsPage:这是一个主要的Composable函数,它组合了上述函数来构建一个完整的阅读统计页面。它包括页面标题、日期显示、时间范围选择、条形图、总阅读页数显示和书籍状态饼图。

  10. 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()
}
相关推荐
网易独家音乐人Mike Zhou1 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
安静读书1 小时前
Python解析视频FPS(帧率)、分辨率信息
python·opencv·音视频
活宝小娜2 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点2 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow2 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o2 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
小二·3 小时前
java基础面试题笔记(基础篇)
java·笔记·python
开心工作室_kaic3 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā3 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
小喵要摸鱼4 小时前
Python 神经网络项目常用语法
python