加入图书页面以及和图书相关的viewmodel代码,以及和阅读记录相关的viewmodel

1.加入图书页面

  1. AddBookScreen:这是一个用于添加书籍的屏幕,它接收一个NavController对象和一个可选的modifier参数。
  • 它首先定义了一个focusManager,用于管理键盘操作。
  • 使用mutableStateOf创建了一个状态变量searchText,用于存储搜索框中的文本。
  1. Column:使用Column构建了屏幕的主体布局,并设置了内边距。

  2. Row:在Column中,使用Row布局创建了一个包含返回图标和搜索框的水平布局。

  • IconButton:定义了一个返回图标按钮,点击时会调用navController.popBackStack()返回上一个导航目的地。
  • Spacer:在返回图标和搜索框之间添加了空间。
  1. OutlinedTextField:定义了一个带有轮廓的文本字段,用于输入搜索内容。
  • value和onValueChange参数用于控制文本字段的值和更改时的回调。
  • label提供了文本字段的标签,指示用户可以搜索书名或作者。
  • modifier用于设置文本字段的背景颜色和圆角形状。
  • colors参数自定义了文本字段的颜色,包括光标颜色、聚焦时的边框颜色和非聚焦时的边框颜色。
  • singleLine设置为true,表示文本字段为单行输入。
  • trailingIcon在文本字段的右侧显示一个清除图标,当文本字段中有内容时显示,点击可以清空文本字段。
  • keyboardActions定义了键盘操作,其中onSearch用于处理搜索逻辑,同时隐藏键盘。
  • keyboardOptions设置了键盘的输入模式为搜索,通常在键盘上显示一个搜索按钮。

app/src/main/java/com/example/BookRecord/AddBooks.kt:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AddBookScreen(
    navController: NavController,
    modifier: Modifier = Modifier,
) {
    // The focus manager to handle the keyboard actions
    val focusManager = LocalFocusManager.current

    // State for search text
    var searchText by remember { mutableStateOf("") }

    Column(
        modifier = modifier
            .padding(12.dp)
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth(),
            verticalAlignment = Alignment.CenterVertically
        ) {
            // Back icon with a larger touch target for better accessibility
            IconButton(
                onClick = { navController.popBackStack() },
                modifier = Modifier.size(35.dp)
            ) {
                Icon(
                    imageVector = Icons.Filled.ArrowBack,
                    contentDescription = "Back",
                    tint = Color(0xFF6650a4)
                )
            }

            Spacer(modifier = Modifier.width(10.dp)) // Add space between the icon and the search bar

            // Search input field
            OutlinedTextField(
                value = searchText,
                onValueChange = { searchText = it },
                label = { Text("Search by title, author", color = Color(0xFF6650a4)) },
                modifier = Modifier
                    .fillMaxWidth()
                    .background(Color(0xFFF2F2F2), RoundedCornerShape(20.dp)), // 直接在这里设置背景颜色和形状
                shape = RoundedCornerShape(20.dp), // 设置输入框的形状
                colors = TextFieldDefaults.outlinedTextFieldColors(
//                    backgroundColor = Color(0xFFF2F2F2),
                    cursorColor = Color(0xFF6650a4),
                    focusedBorderColor = Color(0xFF6650a4),
                    unfocusedBorderColor = Color(0xFF6650a4)
                ),
                singleLine = true,
                trailingIcon = {
                    if (searchText.isNotEmpty()) {
                        IconButton(onClick = { searchText = "" }) {
                            Icon(
                                imageVector = Icons.Filled.Clear,
                                contentDescription = "Clear",
                                tint = Color(0xFF6650a4)
                            )
                        }
                    }
                },
                keyboardActions = KeyboardActions(
                    onSearch = {
                        focusManager.clearFocus() // Hide the keyboard
                        // TODO: Implement the search logic here
                    }
                ),
                keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Search)
            )
        }

        // TODO: Add the rest of your UI components here
    }
}

2. BookViewModel

  1. BookStatus枚举:定义了书籍的三种状态,分别是正在阅读(READING)、已阅读(READ)和搁置(ON_HOLD)。

  2. BookViewModel类:这是一个继承自AndroidViewModel的视图模型类,用于管理和存储书籍数据。

  • 类的初始化块中,通过传入的Application对象获取了数据库的访问对象bookDao和noteDao,这些对象用于与数据库进行交互。
  • bookRepository和noteRepository是用于数据操作的仓库类实例,它们封装了对数据库的直接访问。
  • currentUserUID存储了当前登录用户的用户ID,用于将书籍与用户关联。
  • allBooks是一个LiveData对象,用于观察所有书籍的数据变化。
  • bookCounts是一个MutableLiveData对象,用于存储和更新书籍状态的计数。
  • readingBooks、completeBooks和layasideBooks是MediatorLiveData对象,它们根据书籍的状态过滤allBooks中的数据。
  • 在初始化块中,设置了MediatorLiveData的源为allBooks,并根据书籍的不同状态更新这些LiveData对象。
  • updateBookCounts方法用于更新书籍状态的计数,并将其存储在bookCounts中。
  • addBook方法用于添加新书籍到数据库中。
  • deleteBook方法用于从数据库中删除书籍。
  • updateBookStatus方法用于更新书籍的状态。
  • updateBookReadPage方法用于更新书籍的已阅读页数。
  • getNoteCountByBookId方法用于获取指定书籍的笔记数量。

app/src/main/java/com/example/BookRecord/BookViewModel.kt:

enum class BookStatus {
    READING, // 正在阅读
    READ, // 已阅读
    ON_HOLD // 搁置
}

//在类的初始化块中,通过传入应用程序的 Application 对象,获取了数据库的访问对象 bookDao 和 noteDao
class BookViewModel(application: Application) : AndroidViewModel(application) {
    private val bookDao = AppDatabase.getDatabase(application).bookDao()
    private val noteDao = AppDatabase.getDatabase(application).noteDao()
    private val bookRepository = BookRepository(bookDao, viewModelScope)
    private val noteRepository = NoteRepository(noteDao)


    var currentUserUID = FirebaseAuth.getInstance().currentUser?.uid

    val allBooks: LiveData<List<Book>> = bookRepository.allBooks
    val bookCounts = MutableLiveData<Map<String, Int>>()

    // 使用 MediatorLiveData 替代 Transformations.map
    val readingBooks = MediatorLiveData<List<Book>>()
    val completeBooks = MediatorLiveData<List<Book>>()
    val layasideBooks = MediatorLiveData<List<Book>>()

    init {
        readingBooks.addSource(allBooks) { books ->
            readingBooks.value = books.filter { it.status == BookStatus.READING }
        }

        completeBooks.addSource(allBooks) { books ->
            completeBooks.value = books.filter { it.status == BookStatus.READ }
        }

        layasideBooks.addSource(allBooks) { books ->
            layasideBooks.value = books.filter { it.status == BookStatus.ON_HOLD }
        }
        // Update book counts
        updateBookCounts()
    }


    private fun updateBookCounts() {
        val counts = mutableMapOf("have read" to 0, "lay aside" to 0, "reading" to 0)
        allBooks.observeForever { books ->
            counts["have read"] = books.count { it.status == BookStatus.READ }
            counts["lay aside"] = books.count { it.status == BookStatus.ON_HOLD }
            counts["reading"] = books.count { it.status == BookStatus.READING }
            bookCounts.value = counts
        }
    }

    // 添加新书籍
    fun addBook(bookTitle: String, bookImage: String, author: String, pages: String, status: BookStatus, readPage: String, press: String,startTime: LocalDate) = viewModelScope.launch {
        val newBook = Book(
            userId = currentUserUID ?: "",//确保不会空,处理未登陆的情况
            title = bookTitle,
            image = bookImage,
            author = author,
            pages = pages,
            status = status,
            readpage = readPage,
            press = press,
            startTime = startTime // 设置当前日期为开始时间
        )
        bookRepository.insert(newBook)
    }

    // 删除书籍
    fun deleteBook(book: Book) = viewModelScope.launch {
        bookRepository.delete(book)
    }

    // 更新书籍状态
    fun updateBookStatus(book: Book, newStatus: BookStatus) = viewModelScope.launch {
        book.status = newStatus
        bookRepository.update(book)
    }

    // 更新已阅读页数
    fun updateBookReadPage(book: Book, readPage: String) = viewModelScope.launch {
        book.readpage = readPage // 确保属性名称与你的 Book 类一致
        bookRepository.update(book)
    }

    // 获取指定书籍的笔记数量
    fun getNoteCountByBookId(bookId: Int): LiveData<Int> {
        return noteRepository.getNoteCountByBookId(bookId)
    }

}

3. ReadingRecordViewModel

用于管理与阅读记录相关的数据和操作。以下是代码的主要组成部分和功能:

  1. 构造函数:接收一个 Application 对象,并在初始化块中创建 ReadingRecordRepository 实例。ReadingRecordRepository 用于访问数据库和执行数据库操作。

  2. 数据库访问:通过 AppDatabase.getDatabase(application) 获取数据库实例,然后通过 ReadingRecordDao() 方法获取 ReadingRecordDao 实例,这是与阅读记录表交互的接口。

  3. LiveData 属性:

  • readPagesLast7Days:一个 LiveData<List<ReadingRecordDao.DailyReading>> 属性,用于保存最近7天的每天阅读记录。
  • readPagesLast15Days:一个 LiveData<List<ReadingRecordDao.DailyReading>> 属性,用于保存最近15天的每天阅读记录。
  1. 初始化 LiveData:在初始化块中,使用当前日期和过去日期来计算时间范围,并调用 repository 的 getPagesReadPerDay 方法来获取过去7天和过去15天的每天阅读页数。

  2. 插入阅读记录:insertReadingRecord 函数接收一个 ReadingRecord 对象,然后在协程作用域内调用 repository.insertReadingRecord 方法将新的阅读记录插入数据库。

  3. 协程处理:使用 viewModelScope.launch 在 ViewModel 的作用域内启动协程,以便在后台线程上执行数据库操作。

app/src/main/java/com/example/BookRecord/ReadingRecordViewModel.kt

package com.example.BookRecord
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import org.threeten.bp.LocalDate

class ReadingRecordViewModel(application: Application) : AndroidViewModel(application) {
    // 获取对应的Repository实例
    private val repository: ReadingRecordRepository

    // LiveData保存最近7天和最近15天的每天阅读记录
    val readPagesLast7Days: LiveData<List<ReadingRecordDao.DailyReading>>
    val readPagesLast15Days: LiveData<List<ReadingRecordDao.DailyReading>>


    init {
        // 初始化repository和LiveData
        val appDatabase = AppDatabase.getDatabase(application)
        val readingRecordDao = appDatabase.ReadingRecordDao()
        repository = ReadingRecordRepository(readingRecordDao)

        val today = LocalDate.now()
        val eightDaysAgo = today.minusDays(6)  // 收集8天数据以计算7天的增量
        val sixteenDaysAgo = today.minusDays(14)  // 收集16天数据以计算15天的增量

        // 获取过去7天和过去15天的每天的阅读页数
        readPagesLast7Days = repository.getPagesReadPerDay(eightDaysAgo, today)
        readPagesLast15Days = repository.getPagesReadPerDay(sixteenDaysAgo, today)
    }

     //插入阅读记录的方法
    fun insertReadingRecord(readingRecord: ReadingRecord) {
        viewModelScope.launch {
            repository.insertReadingRecord(readingRecord)
        }
    }


}

ps

app的最终页面见和该文章同一专栏下的博文:安卓开发:BookRecord一款专为纸质书爱好者设计的阅读追踪应用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值