程序的入口main activity代码和一些定义类的代码

1. Main activity(程序的入口)

  1. Firebase Authentication:使用 FirebaseAuth 来处理用户认证。

  2. Google Sign-In:设置 Google Sign-In 客户端和处理登录结果的逻辑。

  3. ViewModels:

  • registerViewModel:通过 viewModels 委托获取 RegisterViewModel 的实例,用于注册和登录逻辑。
  • NoteViewModel、BookViewModel、ReadingRecordViewModel:这些 ViewModel 通过 viewModels 委托获取,分别用于管理笔记、书籍和阅读记录的数据。
  1. Navigation:使用 NavHostController 和 AppNavigation Composable 来管理应用的导航。

  2. Jetpack Compose:使用 Jetpack Compose 构建 UI,包括主题、导航和底部导航栏。

  3. Google Sign-In Setup:setupGoogleSignIn 方法配置 Google Sign-In,包括请求 ID 令牌和电子邮件。

  4. Handle Sign-In Result:handleSignInResult 方法处理 Google Sign-In 的结果,包括使用 Firebase Authentication 进行登录。

  5. ViewModel Factory:RegisterViewModelFactory 类用于创建 RegisterViewModel 的实例。

  6. Navigation Graph:在 AppNavigation Composable 中定义了导航图,包括不同的屏幕和它们的路径。

  7. Bottom Navigation Bar:BottomNavigationBar Composable 创建底部导航栏,允许用户在不同的屏幕之间切换。

  8. Authentication State Observation:observeUserAuthenticationState Composable 观察用户认证状态的变化。

  9. Composable Screens:定义了多个 Composable 函数,如 LoginScreen、RegisterScreen、HomeScreen、BookShelf、AnalyticsScreen、NotesScreen、EditNotesScreen 和 AddBookScreen,这些函数构建应用的不同屏幕。

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


class MainActivity : ComponentActivity() {
    private val firebaseAuth: FirebaseAuth = FirebaseAuth.getInstance()
    private lateinit var signInResultLauncher: ActivityResultLauncher<Intent>
    private lateinit var googleSignInClient: GoogleSignInClient
    private lateinit var navController: NavHostController  // 删除 'private var' 的声明,改为 lateinit
    // 添加一个属性来保存 RegisterViewModel 的引用
//    private val registerViewModel by viewModels<RegisterViewModel> {
//        RegisterViewModelFactory(UserRepository(getSharedPreferences("app_prefs", MODE_PRIVATE)))
//    }
    private val registerViewModel by viewModels<RegisterViewModel> {
        RegisterViewModelFactory(AppDatabase.getDatabase(application))
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        AndroidThreeTen.init(this)
        setupGoogleSignIn()  // 调用新的方法来初始化谷歌登录

        setContent {
            val notesViewModel: NoteViewModel by viewModels()
            val bookViewModel: BookViewModel by viewModels()
            val readingRecordViewModel:ReadingRecordViewModel by viewModels()
// 初始化 navController
            navController = rememberNavController()
            BookRecordTheme {
                CompositionLocalProvider(
                    LocalNotesViewModel provides notesViewModel,
                    LocalBooksViewModel provides bookViewModel,
                    LocalreadingRecordViewModel provides readingRecordViewModel,
                ) {
                    // 不再在这里声明 navController,直接传递上面初始化的 navController
                    AppNavigation(navController, registerViewModel, googleSignInClient, signInResultLauncher)
                }
            }
        }
    }

    private fun setupGoogleSignIn() {
        val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
            .requestIdToken(getString(R.string.default_web_client_id))
            .requestEmail()
            .build()
        googleSignInClient = GoogleSignIn.getClient(this, gso)
        signInResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            Log.d("GoogleSignIn", "Activity Result received")
            if (result.resultCode == Activity.RESULT_OK) {
                val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
                handleSignInResult(task)
            } else {
                Log.d("GoogleSignIn", "Sign in failed or cancelled")
            }
        }

    }

    private fun handleSignInResult(task: Task<GoogleSignInAccount>) {
        try {
            val account = task.getResult(ApiException::class.java)
            val idToken = account.idToken ?: throw Exception("Google ID Token is null")
            val credential = GoogleAuthProvider.getCredential(idToken, null)
            firebaseAuth.signInWithCredential(credential).addOnCompleteListener { authTask ->
                if (authTask.isSuccessful) {
                    val firebaseUser = authTask.result?.user ?: throw Exception("Firebase user is null")
                    val userId = firebaseUser.uid
                    registerViewModel.checkAndInsertUser(userId).observe(this, Observer { userInserted ->
                        if (userInserted || registerViewModel.userAlreadyExists) {
                            navController.navigate("Book")  // 如果用户新插入或已存在,都导航至Book页面
                        } else {
                            // 处理用户插入失败情况
                            Log.d("SignIn", "User insertion failed due to an error.")
                            Toast.makeText(this, "An error occurred during user insertion", Toast.LENGTH_LONG).show()
                        }
                    })
                } else {
                    Log.e("SignIn", "Firebase Sign-In failed: ${authTask.exception?.localizedMessage}")
                    Toast.makeText(this, "Firebase Google Sign-In failed: ${authTask.exception?.localizedMessage}", Toast.LENGTH_LONG).show()
                }
            }
        } catch (e: ApiException) {
            Log.e("SignIn", "Google Sign-In failed code=${e.statusCode}")
            Toast.makeText(this, "Google Sign-In failed: ${e.localizedMessage}", Toast.LENGTH_LONG).show()
        }
    }

    class RegisterViewModelFactory(private val appDatabase: AppDatabase) : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            if (modelClass.isAssignableFrom(RegisterViewModel::class.java)) {
                return RegisterViewModel(UserRepository(appDatabase)) as T
            }
            throw IllegalArgumentException("Unknown ViewModel class")
        }
    }

}
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun AppNavigation(
    navController: NavHostController,  // 更改类型为 NavHostController
    //activity: MainActivity,
    registerViewModel: RegisterViewModel,
    googleSignInClient: GoogleSignInClient,
    signInResultLauncher: ActivityResultLauncher<Intent>
) {
    val currentUser = observeUserAuthenticationState().value
    val startDestination = if (currentUser != null) "Book" else "LoginScreen"

    //val navController = rememberNavController()
    // 根据当前的导航目的地决定是否显示底部导航栏
    val shouldShowBottomBar = navController.currentBackStackEntryAsState().value?.destination?.route !in listOf("notesScreen","EditNotesScreen","LoginScreen","AddBooks","Register")
    Scaffold(
        bottomBar = { if (shouldShowBottomBar) {
            BottomNavigationBar(navController) }
        }
    ) { innerPadding ->
        // 利用提供的 innerPadding 参数调整内容的内边距
        NavHost(
            navController = navController,
            startDestination = startDestination,
            //startDestination = "LoginScreen",
            modifier = Modifier.padding(innerPadding) // 应用内边距
        ) {
            composable("LoginScreen") {
                //LoginScreen(navController, modifier = Modifier.fillMaxSize())
                LoginScreen(navController,signInResultLauncher,googleSignInClient, modifier = Modifier.fillMaxSize(), registerViewModel)

            }
            composable("Register") {
                // 获取一个与当前 Composable 生命周期相关联的 ViewModel 实例
                //val registerViewModel: RegisterViewModel = viewModel()

                // 调用 RegisterScreen 并将 viewModel 传递进去
                RegisterScreen(navController, registerViewModel, modifier = Modifier.fillMaxSize())
            }
            composable("Book") { HomeScreen(googleSignInClient,navController,modifier = Modifier.fillMaxSize())}
            composable("Bookshelf"){BookShelf(navController,modifier = Modifier.fillMaxSize()) }
            composable("Analysis"){AnalyticsScreen(navController,modifier = Modifier.fillMaxSize())}
            composable("notesScreen/{bookId}") { backStackEntry ->
                // Extract the bookId parameter from the backStackEntry
                val bookId = backStackEntry.arguments?.getString("bookId")?.toIntOrNull()
                if (bookId == null) {
                    // 无法解析bookId,根据你的应用逻辑处理这种情况
                    // 比如返回上一屏或显示一个错误消息
                } else {
                    NotesScreen(navController, bookId, modifier = Modifier.fillMaxSize())
                }
            }
            composable("EditNotesScreen/{bookId}") { backStackEntry ->
                // Extract the bookId parameter from the backStackEntry
                val bookId = backStackEntry.arguments?.getString("bookId")?.toIntOrNull()
                if (bookId == null) {
                    // 无法解析bookId,根据你的应用逻辑处理这种情况
                    // 比如返回上一屏或显示一个错误消息
                } else {
                    EditNotesScreen(navController, bookId, modifier = Modifier.fillMaxSize())
                }
            }
            composable("AddBooks"){ AddBookScreen(navController) }
        }
    }
}

@Composable
fun BottomNavigationBar(navController: NavController) {
    BottomNavigation(
        backgroundColor = Color(0xFFCCC2DC) // 设置导航栏颜色
    ) {
        val items = listOf(
            "Book" to Icons.Default.Book,
            "Bookshelf" to Icons.Default.LibraryBooks,
            "Analysis" to Icons.Default.BarChart,
        )
        items.forEach { (screen, icon) ->
            BottomNavigationItem(
                icon = { Icon(icon, contentDescription = screen) },
                label = { Text(screen) },
                selected = navController.currentDestination?.route == screen,
                onClick = {
                    navController.navigate(screen) {
                        // 清理导航栈,避免导航栈过深
                        popUpTo(navController.graph.startDestinationId)
                        launchSingleTop = true
                    }
                }
            )
        }
    }
}

@Composable
fun observeUserAuthenticationState(): State<FirebaseUser?> {
    val auth = FirebaseAuth.getInstance()
    val currentUser = remember { mutableStateOf(auth.currentUser) }

    DisposableEffect(Unit) {
        val listener = FirebaseAuth.AuthStateListener { auth ->
            currentUser.value = auth.currentUser
        }
        auth.addAuthStateListener(listener)
        onDispose {
            auth.removeAuthStateListener(listener)
        }
    }

    return currentUser
}

2. Class

这些实体类使用了 Room Persistence Library 来创建和操作数据库。以下是每个实体类的详细说明:

  1. User 实体:
  • 代表用户表。
  • 包含一个字段 uid,它是用户的主键。
  1. Book 实体:
  • 代表书籍表。
  • 包含字段 id(主键,自动生成)、userId(外键,关联到 User 表的 uid 字段)、title(书名)、image(书籍图片链接)、author(作者)、pages(页数)、status(阅读状态)、readpage(已读页码)、press(出版社)和 startTime(开始阅读日期)。
  • 使用 ForeignKey 注解来定义与 User 实体的关系,如果用户被删除,相应的书籍也会被删除(级联删除)。
  1. Note 实体:
  • 代表笔记表。
  • 包含字段 id(主键,自动生成)、content(笔记内容)和 bookId(外键,关联到 Book 表的 id 字段)。
  • 使用 ForeignKey 注解来定义与 Book 实体的关系,如果书籍被删除,相应的笔记也会被删除(级联删除)。
  1. ReadingRecord 实体:
  • 代表阅读记录表。
  • 包含字段 id(主键,自动生成)、userId(外键,关联到 User 表的 uid 字段)、date(阅读日期)和 readPages(当天阅读的页数)。
  • 使用 ForeignKey 注解来定义与 User 实体的关系,如果用户被删除,相应的阅读记录也会被删除(级联删除)。

这些实体类是应用程序数据持久化的基础,它们允许应用程序存储和管理用户、书籍、笔记和阅读记录等信息。通过定义这些实体类,Room 库可以自动生成数据库访问代码,简化数据库操作。

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

package com.example.BookRecord

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import org.threeten.bp.LocalDate


@Entity(tableName = "users")
data class User(
    @PrimaryKey val uid: String

)
@Entity(
    tableName = "books",
    foreignKeys = [
        ForeignKey(
            entity = User::class,
            parentColumns = ["uid"],
            childColumns = ["userId"],
            onDelete = ForeignKey.CASCADE
        )
    ]
)

data class Book(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    var userId: String, // 添加这个字段来连接User
    var title: String,
    var image: String,
    var author: String,
    var pages: String,
    var status: BookStatus,
    var readpage: String,
    var press: String,
    var startTime: LocalDate
)


@Entity(
    foreignKeys = [
        ForeignKey(
            entity = Book::class,
            parentColumns = ["id"], // Book类的ID字段
            childColumns = ["bookId"], // Note类的bookId字段,作为外键
            onDelete = ForeignKey.CASCADE // 如果Book被删除,则相应的Note也被删除
        )
    ]
)

data class Note(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    var content: String,
    val bookId: Int // 引用Book的ID
)

//创建一个实体来记录每日的阅读页数
@Entity(tableName = "reading_records",
    foreignKeys = [
        ForeignKey(
            entity = User::class,
            parentColumns = ["uid"],
            childColumns = ["userId"],
            onDelete = ForeignKey.CASCADE
        )
    ])
data class ReadingRecord(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val userId: String, // 引用User的UID
    val date: LocalDate, // 记录阅读的日期
    val readPages: Int // 当天读的页数
)

3. CompositionLocal

它们分别用于在 Composable 函数之间共享 NoteViewModel、BookViewModel 和 ReadingRecordViewModel 的实例。Composition Locals 是一种在 Compose 中进行属性传递的方式,类似于在 Android 视图中使用 Context。

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

package com.example.BookRecord

import androidx.compose.runtime.staticCompositionLocalOf

// 提供默认的 ViewModel 实例或错误提示
val LocalNotesViewModel = staticCompositionLocalOf<NoteViewModel> {
    error("No NotesViewModel provided")
}

// 提供默认的 ViewModel 实例或错误提示
val LocalBooksViewModel = staticCompositionLocalOf<BookViewModel> {
    error("No NotesViewModel provided")
}


val LocalreadingRecordViewModel = staticCompositionLocalOf<ReadingRecordViewModel> {
    error("No NotesViewModel provided")
}

4. TypeConverter

定义了两个 Room 数据库类型转换器类,用于在数据库列的类型和 Java/Kotlin 对象的类型之间进行转换。Room 数据库使用这些转换器来处理那些不能直接映射到 SQLite 数据类型的复杂对象。

  1. BookStatusConverter 类:
  • 这个类包含了两个方法,用于转换 BookStatus 枚举和数据库中的 String 类型。
  • toBookStatus 方法将数据库中的 String 类型转换为 BookStatus 枚举。它使用 enumValueOf 函数来查找与给定字符串名称对应的枚举值。
  • fromBookStatus 方法将 BookStatus 枚举转换为数据库中的 String 类型。它返回枚举的名称,该名称将被存储在数据库中。
  1. DataConverters 类:
  • 这个类包含了两个方法,用于转换 LocalDate 对象和数据库中的 String 类型。
  • fromLocalDate 方法将 LocalDate 对象转换为 String。它调用 LocalDate 的 toString 方法来获取 ISO 格式的日期字符串。
  • toLocalDate 方法将 String 转换为 LocalDate 对象。它使用 LocalDate.parse 方法来解析 ISO 格式的日期字符串,并返回 LocalDate 对象。

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

package com.example.BookRecord

import androidx.room.TypeConverter
import org.threeten.bp.LocalDate


class BookStatusConverter {
    @TypeConverter
    fun toBookStatus(value: String) = enumValueOf<BookStatus>(value)

    @TypeConverter
    fun fromBookStatus(status: BookStatus) = status.name
}




class DataConverters {
    @TypeConverter
    fun fromLocalDate(value: LocalDate?): String? {
        return value?.toString()
    }

    @TypeConverter
    fun toLocalDate(value: String?): LocalDate? {
        return value?.let { LocalDate.parse(it) }
    }
}

ps

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

### 关于鸿蒙OS 应用入口页面定义 在鸿蒙操作系统(HarmonyOS)中,应用程序入口页面通常通过AbilitySlice来定义。下面是一个简单的例子展示如何创建一个基本的应用程序并设置其入口页面。 #### 创建项目结构 首先,在IDE中新建一个HarmonyOS工程,并确保选择了Java作为编程语言。之后按照默认向导完成项目的建立过程[^1]。 #### 定义配置文件 编辑`config.json`文件中的`module`节点下的`abilities`部分,指定应用启动时加载的第一个Ability及其对应的Main Ability Slice: ```json { "app": { ... }, "module": [ { "abilities":[ { "name":".MainAbility", "label":"entry_label", "icon":"$media:icon", "description":"main ability description", "launchType":"singleton", "skills":[...], "pages":[".MainAbilitySlice"] } ] } } ``` #### 编写主Activity (MainAbility.java) ```java package com.example.helloworld; import ohos.aafwk.ability.Ability; import ohos.aafwk.content.Intent; public class MainAbility extends Ability { @Override public void onStart(Intent intent){ super.onStart(intent); // 设置该Ability显示的内容为MainAbilitySlice实例 this.setUIContent(new MainAbilitySlice()); } } ``` #### 实现首页逻辑 (MainAbilitySlice.java) ```java package com.example.helloworld.slice; import ohos.aafwk.ability.AbilitySlice; import ohos.aafwk.content.Intent; import ohos.agp.components.Text; public class MainAbilitySlice extends AbilitySlice { private Text text; @Override protected void onStart(Intent intent) { super.onStart(intent); // 加载布局资源ID R.layout.xxxx 对应的是XML描述界面组件的位置 super.setUIContent(ResourceTable.Layout_ability_main); // 获取界面上的文字控件对象 text = (Text)this.findComponentById(ResourceTable.Id_text_hello_world); // 给文字控件赋值 if(text != null){ text.setText("Hello Harmony OS!"); } } } ``` 上述代码展示了如何构建一个最基础版本的鸿蒙操作系统的应用,其中包含了入口页面的设计方式。开发者可以根据实际需求进一步扩展此模板以满足更复杂的功能要求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值