浏览代码

add(auth): add login/logout

zhaoyadi 1 年之前
父节点
当前提交
f46d861375
共有 39 个文件被更改,包括 510 次插入150 次删除
  1. 2 0
      .idea/gradle.xml
  2. 4 1
      app/build.gradle.kts
  3. 10 3
      app/src/main/AndroidManifest.xml
  4. 10 3
      app/src/main/java/com/zaojiao/app/MainActivity.kt
  5. 8 5
      app/src/main/java/com/zaojiao/app/ui/App.kt
  6. 1 1
      build.gradle.kts
  7. 12 4
      core/auth/src/main/AndroidManifest.xml
  8. 19 1
      core/auth/src/main/kotlin/com/zaojiao/app/core/auth/data/AuthRepository.kt
  9. 15 0
      core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/LoginActivity.kt
  10. 52 10
      core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/TokenActivity.kt
  11. 0 15
      core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/TokenViewModel.kt
  12. 34 0
      core/auth/src/main/kotlin/com/zaojiao/app/core/auth/utils/AuthUtils.kt
  13. 9 0
      core/http/src/main/kotlin/com/zaojiao/app/core/http/common/NetResult.kt
  14. 11 12
      data/domain/src/main/kotlin/com/zaojiao/app/data/domain/AccountUseCase.kt
  15. 3 5
      data/remote/src/main/kotlin/com/zaojiao/app/data/remote/RemoteBabyData.kt
  16. 1 0
      data/repo/build.gradle.kts
  17. 5 4
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/BabyRepositoryImpl.kt
  18. 3 0
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/UserRepositoryImpl.kt
  19. 22 0
      feat/baby/build.gradle.kts
  20. 4 0
      feat/baby/src/main/AndroidManifest.xml
  21. 17 0
      feat/baby/src/main/java/com/zaojiao/app/feat/baby/BabyNavigation.kt
  22. 8 0
      feat/baby/src/main/java/com/zaojiao/app/feat/baby/list/BabyListPage.kt
  23. 7 2
      feat/home/build.gradle.kts
  24. 3 0
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/index/HomeIndexPage.kt
  25. 28 6
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/index/HomeIndexTopBar.kt
  26. 37 39
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/index/HomeIndexViewModel.kt
  27. 8 2
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/index/state/HomeIndexUserUiState.kt
  28. 10 15
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/navigation/HomeNavigation.kt
  29. 5 1
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalPage.kt
  30. 7 1
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalService.kt
  31. 17 1
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalUiState.kt
  32. 28 16
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalViewModel.kt
  33. 1 3
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/plan/HomePlanPage.kt
  34. 22 0
      feat/settings/build.gradle.kts
  35. 4 0
      feat/settings/src/main/AndroidManifest.xml
  36. 16 0
      feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsNavigation.kt
  37. 45 0
      feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsPage.kt
  38. 20 0
      feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsViewModel.kt
  39. 2 0
      settings.gradle.kts

+ 2 - 0
.idea/gradle.xml

@@ -38,8 +38,10 @@
             <option value="$PROJECT_DIR$/data/remote" />
             <option value="$PROJECT_DIR$/data/repo" />
             <option value="$PROJECT_DIR$/feat" />
+            <option value="$PROJECT_DIR$/feat/baby" />
             <option value="$PROJECT_DIR$/feat/design" />
             <option value="$PROJECT_DIR$/feat/home" />
+            <option value="$PROJECT_DIR$/feat/settings" />
           </set>
         </option>
       </GradleProjectSettings>

+ 4 - 1
app/build.gradle.kts

@@ -30,10 +30,13 @@ dependencies {
     implementation(project(":core:auth"))
     implementation(project(":core:nav"))
 
+    implementation(project(":data:domain"))
+
     implementation(project(":feat:design"))
     implementation(project(":feat:home"))
+    implementation(project(":feat:baby"))
+    implementation(project(":feat:settings"))
 
-    implementation(project(":data:domain"))
 
     implementation("androidx.core:core-splashscreen:1.0.1")
     implementation("androidx.startup:startup-runtime:1.1.1")

+ 10 - 3
app/src/main/AndroidManifest.xml

@@ -1,14 +1,15 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
 
     <application
         android:name=".LJGApplication"
         android:allowBackup="false"
         android:dataExtractionRules="@xml/data_extraction_rules"
+        android:enableOnBackInvokedCallback="true"
         android:fullBackupContent="@xml/backup_rules"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
@@ -18,6 +19,7 @@
         <activity
             android:name="com.zaojiao.app.MainActivity"
             android:exported="true"
+            android:launchMode="singleInstance"
             android:theme="@style/Theme.Luojigou.LightBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -25,6 +27,11 @@
             </intent-filter>
 
             <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+
                 <data
                     android:host="www.luojigou.vip"
                     android:scheme="https" />

+ 10 - 3
app/src/main/java/com/zaojiao/app/MainActivity.kt

@@ -1,10 +1,8 @@
 package com.zaojiao.app
 
-import android.content.Intent
 import android.os.Build
 import android.os.Bundle
 import android.view.WindowManager
-import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.material3.MaterialTheme
@@ -13,21 +11,25 @@ import androidx.compose.ui.graphics.Color
 import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.WindowInsetsControllerCompat
+import androidx.fragment.app.FragmentActivity
 import com.google.accompanist.systemuicontroller.rememberSystemUiController
+import com.zaojiao.app.core.auth.utils.AuthUtils
 import com.zaojiao.app.feat.design.BackgroundTheme
 import com.zaojiao.app.feat.design.LocalBackgroundTheme
 import com.zaojiao.app.feat.design.LocalOnFinishDispatcher
 import com.zaojiao.app.ui.App
 import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
 
 private val LightBackgroundTheme = BackgroundTheme(color = Color.White)
 
 private val DarkBackgroundTheme = BackgroundTheme(color = Color.Black)
 
 @AndroidEntryPoint
-class MainActivity : ComponentActivity() {
+class MainActivity : FragmentActivity() {
     private lateinit var windowInsetsController: WindowInsetsControllerCompat
     override fun onCreate(savedInstanceState: Bundle?) {
+        AuthUtils.attach(this)
         super.onCreate(savedInstanceState)
         configureSystemBar()
         setContent {
@@ -63,4 +65,9 @@ class MainActivity : ComponentActivity() {
             show(WindowInsetsCompat.Type.systemBars())
         }
     }
+
+    override fun onDestroy() {
+        AuthUtils.detach()
+        super.onDestroy()
+    }
 }

+ 8 - 5
app/src/main/java/com/zaojiao/app/ui/App.kt

@@ -6,7 +6,10 @@ import androidx.navigation.compose.composable
 import com.zaojiao.app.core.auth.ui.LoginPage
 import com.zaojiao.app.core.nav.LJGNavHost
 import com.zaojiao.app.core.nav.LJGNavigator
-import com.zaojiao.app.feat.home.HomePage
+import com.zaojiao.app.feat.baby.babyRoute
+import com.zaojiao.app.feat.home.navigation.homePage
+import com.zaojiao.app.feat.home.navigation.homeRoute
+import com.zaojiao.app.feat.settings.settingRoute
 
 @Composable
 fun App(
@@ -18,11 +21,11 @@ fun App(
 
     LJGNavHost(
         navHostController = appState.navController,
-        startDestination = "/"
+        startDestination = homePage,
     ) {
-        composable(route = "/") {
-            HomePage()
-        }
+        homeRoute()
+        babyRoute()
+        settingRoute()
 
         composable(route = "/login/sms") {
             LoginPage()

+ 1 - 1
build.gradle.kts

@@ -19,6 +19,7 @@ buildscript {
 }
 
 plugins {
+    alias(libs.plugins.ksp).apply(false)
     id("com.android.application").version("8.0.1").apply(false)
     id("com.android.library").version("8.0.1").apply(false)
     id("org.jetbrains.kotlin.android").version("1.8.21").apply(false)
@@ -26,7 +27,6 @@ plugins {
     id("com.google.dagger.hilt.android").version("2.44.2").apply(false)
     id("com.google.protobuf").version("0.9.3").apply(false)
     id("org.jetbrains.kotlin.plugin.serialization").version("1.8.21").apply(false)
-    alias(libs.plugins.ksp).apply(false)
 }
 
 subprojects {

+ 12 - 4
core/auth/src/main/AndroidManifest.xml

@@ -2,16 +2,24 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
 
     <application>
-        <activity android:name=".LoginActivity" />
-
         <activity
             android:name=".ui.TokenActivity"
-            android:exported="true">
+            android:exported="true"
+            android:launchMode="singleInstance">
+
             <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+
+                <category android:name="android.intent.category.BROWSABLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+
                 <data
-                    android:host="/com.zaojiao.app"
+                    android:host="www.luojigou.vip"
+                    android:path="/app"
                     android:scheme="token" />
             </intent-filter>
         </activity>
+
+        <activity android:name=".ui.LoginActivity" />
     </application>
 </manifest>

+ 19 - 1
core/auth/src/main/kotlin/com/zaojiao/app/core/auth/data/AuthRepository.kt

@@ -1,14 +1,32 @@
 package com.zaojiao.app.core.auth.data
 
+import android.util.Log
 import com.zaojiao.app.core.auth.model.TokenModel
+import com.zaojiao.app.core.auth.utils.AuthState
+import com.zaojiao.app.core.auth.utils.AuthUtils
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 
 class AuthRepository constructor(
     private val localAuthData: LocalAuthData,
     private val remoteTokenData: RemoteAuthData,
 ) {
-    val token: Flow<TokenModel?> = localAuthData.token
+    val token: Flow<TokenModel?> = localAuthData.token.onEach {
+        AuthUtils.stateFlow.emit(if (it == null) AuthState.OUT else AuthState.IN)
+    }
+
+    val state: Flow<AuthState> = localAuthData.token.map {
+        Log.e("AuthRepository", "state: ${if (it == null) "out" else "in"}")
+        AuthUtils.stateFlow.emit(if (it == null) AuthState.OUT else AuthState.IN)
+        if (it == null) AuthState.OUT else AuthState.IN
+    }.distinctUntilChanged()
+
+    suspend fun checkHasLogin(): Boolean {
+        return token.first() != null
+    }
 
     suspend fun updateToken(token: TokenModel) {
         localAuthData.updateToken(token)

+ 15 - 0
core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/LoginPage.kt → core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/LoginActivity.kt

@@ -1,15 +1,30 @@
 package com.zaojiao.app.core.auth.ui
 
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.statusBarsPadding
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Close
 import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.hilt.navigation.compose.hiltViewModel
 
+class LoginActivity : ComponentActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContent {
+            MaterialTheme {
+                LoginPage()
+            }
+        }
+    }
+}
+
 @Composable
 fun LoginPage(
     loginViewModel: LoginViewModel = hiltViewModel(),

+ 52 - 10
core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/TokenActivity.kt

@@ -1,31 +1,73 @@
 package com.zaojiao.app.core.auth.ui
 
-import android.app.Activity
+import android.content.Context
+import android.content.Intent
 import android.os.Bundle
 import androidx.appcompat.app.AppCompatActivity
+import com.zaojiao.app.core.auth.data.AuthRepository
 import com.zaojiao.app.core.auth.model.TokenModel
 import dagger.hilt.android.AndroidEntryPoint
 import kotlinx.coroutines.runBlocking
 import javax.inject.Inject
+import kotlin.concurrent.thread
 
 @AndroidEntryPoint
 class TokenActivity : AppCompatActivity() {
+    companion object {
+        const val KEY_ACCESS = "access_token"
+        const val KEY_REFRESH = "refresh_token"
+
+        fun start(context: Context, accessToken: String?, refreshToken: String?) {
+            val intent = Intent(context, TokenActivity::class.java)
+            intent.putExtra(KEY_ACCESS, accessToken)
+            intent.putExtra(KEY_REFRESH, refreshToken)
+            context.startActivity(intent)
+        }
+    }
 
     @Inject
-    public lateinit var tokenViewModel: TokenViewModel
+    public lateinit var authRepository: AuthRepository
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+        parseIntent(intent)
+    }
 
-        val tokenModel = TokenModel(
-            accessToken = "",
-            refreshToken = "",
-        )
+    override fun onNewIntent(newIntent: Intent?) {
+        super.onNewIntent(intent)
+        parseIntent(newIntent)
+    }
 
-        runBlocking {
-            tokenViewModel.saveToken(tokenModel)
-        }
+    private fun parseIntent(parsedIntent: Intent?) {
+        thread {
+            var accessToken: String? = null
+            var refreshToken: String? = null
+
+            parsedIntent?.data?.apply {
+                accessToken = this.getQueryParameter(KEY_ACCESS)
+                refreshToken = this.getQueryParameter(KEY_REFRESH)
+            }
 
-        finish()
+            if (accessToken == null || refreshToken == null) {
+                runBlocking {
+                    authRepository.clearToken()
+                }
+            } else {
+                runBlocking {
+                    authRepository.updateToken(
+                        TokenModel(
+                            accessToken = accessToken!!,
+                            refreshToken = refreshToken!!,
+                        )
+                    )
+                }
+            }
+
+            val intent = Intent()
+            intent.setClassName(packageName, "com.zaojiao.app.MainActivity")
+            intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
+            startActivity(intent)
+            finish()
+        }
     }
 }

+ 0 - 15
core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/TokenViewModel.kt

@@ -1,15 +0,0 @@
-package com.zaojiao.app.core.auth.ui
-
-import androidx.lifecycle.ViewModel
-import com.zaojiao.app.core.auth.data.AuthRepository
-import com.zaojiao.app.core.auth.model.TokenModel
-import javax.inject.Inject
-
-class TokenViewModel @Inject constructor(
-    private val authRepository: AuthRepository,
-) : ViewModel() {
-
-    suspend fun saveToken(tokenModel: TokenModel) {
-        authRepository.updateToken(tokenModel)
-    }
-}

+ 34 - 0
core/auth/src/main/kotlin/com/zaojiao/app/core/auth/utils/AuthUtils.kt

@@ -0,0 +1,34 @@
+package com.zaojiao.app.core.auth.utils
+
+import androidx.fragment.app.FragmentActivity
+import com.zaojiao.app.core.auth.model.TokenModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import java.util.concurrent.CompletableFuture
+
+enum class AuthState { IN, OUT }
+
+object AuthUtils {
+    private var activity: FragmentActivity? = null
+
+    internal val stateFlow: MutableStateFlow<AuthState?> = MutableStateFlow(null)
+    val state: Flow<AuthState> get() = stateFlow.filterNotNull().distinctUntilChanged()
+
+    fun attach(activity: FragmentActivity) {
+        AuthUtils.activity = activity
+    }
+
+    fun detach() {
+        activity = null
+    }
+
+    fun navToLogin(): TokenModel? {
+        assert(activity != null) { "AuthUtils must called attach in MainActivity" }
+        val completableFuture = CompletableFuture<TokenModel?>()
+
+        return completableFuture.get()
+    }
+}

+ 9 - 0
core/http/src/main/kotlin/com/zaojiao/app/core/http/common/NetResult.kt

@@ -28,4 +28,13 @@ fun <T> NetResult<T>.toDataState(): DataState<T> {
     } else {
         DataState.Failure(RuntimeException(this.message))
     }
+}
+
+suspend fun <T> dataStateCatch(action: suspend () -> NetResult<T>): DataState<T> {
+    return try {
+        val result = action.invoke()
+        result.toDataState()
+    } catch (e: Throwable) {
+        DataState.Failure(e)
+    }
 }

+ 11 - 12
data/domain/src/main/kotlin/com/zaojiao/app/data/domain/AccountUseCase.kt

@@ -1,8 +1,9 @@
 package com.zaojiao.app.data.domain
 
 import android.content.Context
+import com.zaojiao.app.core.auth.data.AuthRepository
+import com.zaojiao.app.core.auth.utils.AuthState
 import com.zaojiao.app.data.repo.BabyRepository
-import com.zaojiao.app.data.repo.LoginRepository
 import com.zaojiao.app.data.repo.LoginState
 import com.zaojiao.app.data.repo.UserRepository
 import dagger.hilt.android.qualifiers.ApplicationContext
@@ -14,26 +15,24 @@ import javax.inject.Singleton
 
 @Singleton
 class AccountUseCase @Inject constructor(
-    private val loginRepository: LoginRepository,
+    private val authRepository: AuthRepository,
     private val userRepository: UserRepository,
     private val babyRepository: BabyRepository,
     @ApplicationContext private val context: Context,
 ) {
-    val appState: Flow<LoginState> = flow {
-        emit(LoginState.OUT)
-
-        loginRepository.state.onEach {
-            try {
-                if (it == LoginState.IN) {
+    val flow: Flow<LoginState> = flow {
+        authRepository.state.onEach {
+            when (it) {
+                AuthState.IN -> {
                     userRepository.getUser()
                     babyRepository.getBaby()
-                } else {
+                }
+
+                AuthState.OUT -> {
                     userRepository.deleteUser()
                     babyRepository.deleteAllBaby()
+
                 }
-                emit(LoginState.IN)
-            } catch (throwable: Throwable) {
-                emit(LoginState.OUT)
             }
         }
     }

+ 3 - 5
data/remote/src/main/kotlin/com/zaojiao/app/data/remote/RemoteBabyData.kt

@@ -1,9 +1,7 @@
 package com.zaojiao.app.data.remote
 
 import com.zaojiao.app.core.common.state.DataState
-import com.zaojiao.app.core.http.common.isSuccess
-import com.zaojiao.app.core.http.common.toDataState
-import com.zaojiao.app.core.http.common.tryRequest
+import com.zaojiao.app.core.http.common.dataStateCatch
 import com.zaojiao.app.data.model.BabyModel
 import com.zaojiao.app.data.model.BabyRelationModel
 import com.zaojiao.app.data.remote.api.BabyApi
@@ -15,10 +13,10 @@ class RemoteBabyData @Inject constructor(
     private val babyApi: BabyApi,
 ) {
     suspend fun getBabyList(): DataState<List<BabyModel>> {
-        return tryRequest { babyApi.getBabyList().toDataState() }
+        return dataStateCatch { babyApi.getBabyList() }
     }
 
     suspend fun getBabyRelation(): DataState<List<BabyRelationModel>> {
-        return tryRequest { babyApi.getBabyRelation().toDataState() }
+        return dataStateCatch { babyApi.getBabyRelation() }
     }
 }

+ 1 - 0
data/repo/build.gradle.kts

@@ -9,6 +9,7 @@ android {
 
 dependencies {
     implementation(project(":core:common"))
+    implementation(project(":core:auth"))
 
     implementation(project(":data:remote"))
     implementation(project(":data:local"))

+ 5 - 4
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/BabyRepositoryImpl.kt

@@ -8,6 +8,7 @@ import com.zaojiao.app.data.remote.RemoteBabyData
 import com.zaojiao.app.data.repo.BabyRepository
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
 import javax.inject.Inject
 
 class BabyRepositoryImpl @Inject constructor(
@@ -15,15 +16,15 @@ class BabyRepositoryImpl @Inject constructor(
     private val remoteBabyData: RemoteBabyData,
     @ApplicationScope private val coroutineScope: CoroutineScope,
 ) : BabyRepository {
-
     override val current: Flow<BabyModel?> get() = localBabyData.current
 
     override val babyList: Flow<List<BabyModel>> get() = localBabyData.list
+
     override suspend fun getBaby() {
         remoteBabyData.getBabyList().apply {
             when (this) {
                 is DataState.Failure -> {
-
+                    localBabyData.updateList(emptyList())
                 }
 
                 is DataState.Success -> {
@@ -34,10 +35,10 @@ class BabyRepositoryImpl @Inject constructor(
     }
 
     override suspend fun deleteBaby() {
-        TODO("Not yet implemented")
+
     }
 
     override suspend fun deleteAllBaby() {
-        TODO("Not yet implemented")
+        localBabyData.updateList(emptyList())
     }
 }

+ 3 - 0
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/UserRepositoryImpl.kt

@@ -1,5 +1,6 @@
 package com.zaojiao.app.data.repo.impl
 
+import com.zaojiao.app.core.auth.utils.AuthUtils
 import com.zaojiao.app.core.common.remote.di.ApplicationScope
 import com.zaojiao.app.core.common.state.DataState
 import com.zaojiao.app.data.local.user.LocalUserData
@@ -9,6 +10,8 @@ import com.zaojiao.app.data.repo.UserRepository
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
 import javax.inject.Inject
 
 

+ 22 - 0
feat/baby/build.gradle.kts

@@ -0,0 +1,22 @@
+plugins {
+    id("d.convention.library")
+    id("d.convention.compose")
+    id("d.convention.hilt")
+    id("d.convention.lifecycle")
+    id("d.convention.navigation")
+}
+
+android {
+    namespace = "com.zaojiao.app.feat.baby"
+}
+
+dependencies {
+    implementation(project(":core:common"))
+    implementation(project(":core:auth"))
+    implementation(project(":core:nav"))
+
+    implementation(project(":feat:design"))
+
+    implementation(project(":data:repo"))
+    implementation(project(":data:model"))
+}

+ 4 - 0
feat/baby/src/main/AndroidManifest.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+</manifest>

+ 17 - 0
feat/baby/src/main/java/com/zaojiao/app/feat/baby/BabyNavigation.kt

@@ -0,0 +1,17 @@
+package com.zaojiao.app.feat.baby
+
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptions
+import androidx.navigation.compose.composable
+import com.zaojiao.app.core.nav.LJGNavigator
+import com.zaojiao.app.feat.baby.list.BabyListPage
+
+const val babyList = "/baby/list"
+
+fun LJGNavigator.toBabyIndex(navOptions: NavOptions? = null) {
+    this.navigate(babyList)
+}
+
+fun NavGraphBuilder.babyRoute() {
+    composable(route = babyList) { BabyListPage() }
+}

+ 8 - 0
feat/baby/src/main/java/com/zaojiao/app/feat/baby/list/BabyListPage.kt

@@ -0,0 +1,8 @@
+package com.zaojiao.app.feat.baby.list
+
+import androidx.compose.runtime.Composable
+
+@Composable
+fun BabyListPage() {
+
+}

+ 7 - 2
feat/home/build.gradle.kts

@@ -12,9 +12,14 @@ android {
 
 dependencies {
     implementation(project(":core:common"))
-
-    implementation(project(":feat:design"))
+    implementation(project(":core:auth"))
+    implementation(project(":core:nav"))
 
     implementation(project(":data:repo"))
     implementation(project(":data:model"))
+    implementation(project(":data:domain"))
+
+    implementation(project(":feat:design"))
+    implementation(project(":feat:baby"))
+    implementation(project(":feat:settings"))
 }

+ 3 - 0
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/index/HomeIndexPage.kt

@@ -27,6 +27,7 @@ internal fun HomeIndexRoute(
 
     HomeIndexPage(
         babyUiState = babyUiState,
+        onBabyInfoClick = { viewModel.navToUserPage() }
     )
 }
 
@@ -34,6 +35,7 @@ internal fun HomeIndexRoute(
 @Composable
 fun HomeIndexPage(
     babyUiState: HomeIndexBabyUiState,
+    onBabyInfoClick: () -> Unit,
 ) {
     LazyColumn(
         modifier = Modifier
@@ -44,6 +46,7 @@ fun HomeIndexPage(
         stickyHeader {
             HomeIndexTopBar(
                 babyUiState = babyUiState,
+                onBabyInfoClick = onBabyInfoClick,
             )
         }
 

+ 28 - 6
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/index/HomeIndexTopBar.kt

@@ -2,6 +2,7 @@ package com.zaojiao.app.feat.home.index
 
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
@@ -39,6 +40,7 @@ import com.zaojiao.app.feat.home.index.state.HomeIndexBabyUiState
 @Composable
 fun HomeIndexTopBar(
     babyUiState: HomeIndexBabyUiState,
+    onBabyInfoClick: () -> Unit,
 ) {
     Row(
         modifier = Modifier
@@ -51,6 +53,7 @@ fun HomeIndexTopBar(
     ) {
         HomeIndexTopBarBabyInfo(
             babyUiState = babyUiState,
+            onBabyInfoClick = onBabyInfoClick,
         )
         HomeIndexTopBarSearchBar()
         HomeIndexTopBarSignupButton()
@@ -60,28 +63,35 @@ fun HomeIndexTopBar(
 @Composable
 fun HomeIndexTopBarBabyInfo(
     babyUiState: HomeIndexBabyUiState,
+    onBabyInfoClick: () -> Unit,
 ) {
     Row(
         modifier = Modifier
             .padding(start = 16.dp)
+            .clip(
+                shape = RoundedCornerShape(100.dp),
+            )
             .background(
                 color = Color(0x260B57C7),
                 shape = RoundedCornerShape(100.dp)
             )
             .height(44.dp)
-            .wrapContentWidth(),
+            .wrapContentWidth()
+            .clickable { onBabyInfoClick() },
         verticalAlignment = Alignment.CenterVertically,
     ) {
         when (babyUiState) {
-            HomeIndexBabyUiState.None -> {
+            HomeIndexBabyUiState.Login -> {
                 Text(
                     text = "立即登录",
                     modifier = Modifier
                         .width(93.dp)
+                        .padding(vertical = 12.dp)
+                        .align(Alignment.CenterVertically)
                         .fillMaxHeight(),
                     style = TextStyle(
                         color = Color(0xFF0B57C7),
-                        fontSize = 13.sp,
+                        fontSize = 15.sp,
                         lineHeight = 18.sp,
                         fontWeight = FontWeight.Medium,
                         textAlign = TextAlign.Center,
@@ -89,15 +99,27 @@ fun HomeIndexTopBarBabyInfo(
                 )
             }
 
+            HomeIndexBabyUiState.None -> {
+                Box(
+                    modifier = Modifier
+                        .width(93.dp)
+                        .padding(vertical = 12.dp)
+                        .align(Alignment.CenterVertically)
+                        .fillMaxHeight(),
+                )
+            }
+
             HomeIndexBabyUiState.Add -> {
                 Text(
-                    text = "立即登录",
+                    text = "去添加",
                     modifier = Modifier
-                        .weight(1f)
+                        .width(93.dp)
+                        .padding(vertical = 12.dp)
+                        .align(Alignment.CenterVertically)
                         .fillMaxHeight(),
                     style = TextStyle(
                         color = Color(0xFF0B57C7),
-                        fontSize = 13.sp,
+                        fontSize = 15.sp,
                         lineHeight = 18.sp,
                         fontWeight = FontWeight.Medium,
                         textAlign = TextAlign.Center,

+ 37 - 39
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/index/HomeIndexViewModel.kt

@@ -2,12 +2,11 @@ package com.zaojiao.app.feat.home.index
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
+import com.zaojiao.app.core.auth.data.AuthRepository
+import com.zaojiao.app.core.auth.utils.AuthState
 import com.zaojiao.app.data.repo.BabyRepository
-import com.zaojiao.app.data.repo.LoginRepository
-import com.zaojiao.app.data.repo.UserRepository
 import com.zaojiao.app.feat.home.index.state.HomeIndexBabyUiState
 import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.async
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -17,52 +16,51 @@ import javax.inject.Inject
 
 @HiltViewModel
 class HomeIndexViewModel @Inject constructor(
-    private val loginRepository: LoginRepository,
-    private val userRepository: UserRepository,
+    private val authRepository: AuthRepository,
     private val babyRepository: BabyRepository,
 ) : ViewModel() {
     val babyUiState: StateFlow<HomeIndexBabyUiState> =
-        loginRepository.state.combine(babyRepository.current) { login, baby ->
-//            when (login) {
-//                LoginState.IN -> {
-//                    babyRepository.getBaby()
-//
-//                    if (baby != null) {
-//                        HomeIndexBabyUiState(
-//                            name = baby.name,
-//                            avatar = baby.img,
-//                            age = baby.age.substringBefore("-"),
-//                        )
-//                    } else {
-//                        HomeIndexBabyUiState.Add
-//                    }
-//                }
-//
-//                LoginState.OUT -> {
-//                    HomeIndexBabyUiState.None
-//                }
-//            }
-            viewModelScope.launch {
-                async {
-                    userRepository.getUser()
-                }
-                async {
+        combine(authRepository.state, babyRepository.current) { auth, baby ->
+            when (auth) {
+                AuthState.IN -> {
                     babyRepository.getBaby()
+
+                    if (baby != null) {
+                        HomeIndexBabyUiState(
+                            name = baby.name,
+                            avatar = baby.img,
+                            age = baby.age.substringBefore("-"),
+                        )
+                    } else {
+                        HomeIndexBabyUiState.Add
+                    }
                 }
-            }
 
-            if (baby != null) {
-                HomeIndexBabyUiState(
-                    name = baby.name,
-                    avatar = baby.img,
-                    age = baby.age.substringBefore("-"),
-                )
-            } else {
-                HomeIndexBabyUiState.Add
+                AuthState.OUT -> {
+                    HomeIndexBabyUiState.Login
+                }
             }
         }.stateIn(
             scope = viewModelScope,
             started = SharingStarted.WhileSubscribed(5_000),
             initialValue = HomeIndexBabyUiState.None,
         )
+
+    fun navToUserPage() = viewModelScope.launch {
+//        val isLogin = authRepository.checkHasLogin()
+//
+//        if (isLogin) {
+//            LJGNavigator.toBabyIndex()
+//        } else {
+//            thread {
+//                AuthUtils.navToLogin().apply {
+//                    if (this != null) {
+//                        LJGNavigator.toBabyIndex()
+//                    } else {
+//
+//                    }
+//                }
+//            }
+//        }
+    }
 }

+ 8 - 2
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/index/state/HomeIndexUserUiState.kt

@@ -7,13 +7,19 @@ data class HomeIndexBabyUiState(
 ) {
     companion object {
         val None = HomeIndexBabyUiState(
-            name = "",
+            name = "1",
             avatar = null,
             age = "0",
         )
 
         val Add = HomeIndexBabyUiState(
-            name = "",
+            name = "2",
+            avatar = null,
+            age = "0",
+        )
+
+        val Login = HomeIndexBabyUiState(
+            name = "3",
             avatar = null,
             age = "0",
         )

+ 10 - 15
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/navigation/HomeNavigation.kt

@@ -1,31 +1,26 @@
 package com.zaojiao.app.feat.home.navigation
 
-
-import androidx.navigation.NavController
 import androidx.navigation.NavGraphBuilder
 import androidx.navigation.NavOptions
 import androidx.navigation.compose.composable
+import com.zaojiao.app.core.nav.LJGNavigator
+import com.zaojiao.app.feat.home.HomePage
 import com.zaojiao.app.feat.home.course.HomeCourseRoute
 import com.zaojiao.app.feat.home.index.HomeIndexRoute
 import com.zaojiao.app.feat.home.personal.HomePersonalRoute
 
-const val homeIndex = "home/index"
-const val homeCourse = "home/course"
-const val homePersonal = "home/personal"
-
-fun NavController.navigateToHomeIndex(navOptions: NavOptions? = null) {
-    this.navigate(homeIndex, navOptions)
-}
-
-fun NavController.navigateToHomeCourse(navOptions: NavOptions? = null) {
-    this.navigate(homeCourse, navOptions)
-}
+const val homePage = "/home"
+const val homeIndex = "/home/index"
+const val homeCourse = "/home/course"
+const val homePersonal = "/home/personal"
 
-fun NavController.navigateToHomePersonal(navOptions: NavOptions? = null) {
-    this.navigate(homePersonal, navOptions)
+fun LJGNavigator.toHomePage(navOptions: NavOptions? = null) {
+    this.navigate(homePage)
 }
 
 fun NavGraphBuilder.homeRoute() {
+    composable(route = homePage) { HomePage() }
+
     composable(route = homeIndex) { HomeIndexRoute() }
 
     composable(route = homeCourse) { HomeCourseRoute() }

+ 5 - 1
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalPage.kt

@@ -26,6 +26,7 @@ internal fun HomePersonalRoute(
 
     HomePersonalPage(
         userUiState = userUiState,
+        navToSettings = viewModel::navToSettings,
     )
 }
 
@@ -35,6 +36,7 @@ internal fun HomePersonalRoute(
 @Composable
 fun HomePersonalPage(
     userUiState: HomePersonalUserUiState,
+    navToSettings: () -> Unit,
 ) {
     Box(
         modifier = Modifier
@@ -71,7 +73,9 @@ fun HomePersonalPage(
             }
 
             item {
-                HomePersonalService()
+                HomePersonalService(
+                    navToSettings = navToSettings,
+                )
             }
 
             item {

+ 7 - 1
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalService.kt

@@ -3,6 +3,7 @@ package com.zaojiao.app.feat.home.personal
 import androidx.annotation.DrawableRes
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -30,7 +31,9 @@ import com.zaojiao.app.feat.home.R
 import com.zaojiao.app.feat.design.shadow
 
 @Composable
-fun HomePersonalService() {
+fun HomePersonalService(
+    navToSettings: () -> Unit,
+) {
     Box(
         modifier = Modifier
             .padding(
@@ -113,6 +116,7 @@ fun HomePersonalService() {
                 HomePersonalServiceItem(
                     iconRes = R.mipmap.personal_setting,
                     description = "设置",
+                    onClick = navToSettings,
                 )
                 HomePersonalServiceBlank()
             }
@@ -125,9 +129,11 @@ fun HomePersonalService() {
 fun RowScope.HomePersonalServiceItem(
     @DrawableRes iconRes: Int,
     description: String,
+    onClick: (() -> Unit)? = null,
 ) {
     Column(
         modifier = Modifier
+            .clickable { onClick?.invoke() }
             .weight(1f)
             .fillMaxHeight()
             .widthIn(max = 64.dp),

+ 17 - 1
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalUiState.kt

@@ -3,4 +3,20 @@ package com.zaojiao.app.feat.home.personal
 data class HomePersonalUserUiState(
     val name: String,
     val avatar: String?,
-)
+    val followCount: Int = 0,
+    val followerCount: Int = 0,
+    val likeCount: Int = 0,
+    val favoriteCount: Int = 0,
+){
+    companion object {
+        val None = HomePersonalUserUiState(
+            name = "",
+            avatar = null,
+        )
+
+        val Login = HomePersonalUserUiState(
+            name = "登录",
+            avatar = "",
+        )
+    }
+}

+ 28 - 16
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalViewModel.kt

@@ -2,38 +2,50 @@ package com.zaojiao.app.feat.home.personal
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
+import com.zaojiao.app.core.auth.data.AuthRepository
+import com.zaojiao.app.core.auth.utils.AuthState
+import com.zaojiao.app.core.nav.LJGNavigator
 import com.zaojiao.app.data.repo.UserRepository
+import com.zaojiao.app.feat.settings.toSettings
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 import javax.inject.Inject
 
 @HiltViewModel
 class HomePersonalViewModel @Inject constructor(
+    private val authRepository: AuthRepository,
     private val userRepository: UserRepository
 ) : ViewModel() {
 
-    init {
-        viewModelScope.launch {
-            userRepository.getUser()
-        }
-    }
+    val userUiState: StateFlow<HomePersonalUserUiState> =
+        combine(authRepository.state, userRepository.user) { auth, user ->
+            when (auth) {
+                AuthState.IN -> {
+                    userRepository.getUser()
+
+                    HomePersonalUserUiState(
+                        name = user?.nickName ?: "逻辑狗",
+                        avatar = user?.avatarUrl,
+                    )
+                }
 
-    val userUiState: StateFlow<HomePersonalUserUiState> = userRepository.user
-        .map {
-            HomePersonalUserUiState(
-                name = it?.nickName ?: "逻辑狗",
-                avatar = it?.avatarUrl,
-            )
+                AuthState.OUT -> {
+                    HomePersonalUserUiState.Login
+                }
+            }
         }.stateIn(
             scope = viewModelScope,
             started = SharingStarted.WhileSubscribed(5_000),
-            initialValue = HomePersonalUserUiState(
-                name = "逻辑狗",
-                avatar = null,
-            )
+            initialValue = HomePersonalUserUiState.None,
         )
+
+    fun navToSettings() {
+        viewModelScope.launch {
+            LJGNavigator.toSettings()
+        }
+    }
 }

+ 1 - 3
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/plan/HomePlanPage.kt

@@ -25,9 +25,7 @@ internal fun HomePlanRoute(
 }
 
 @Composable
-fun HomePlanPage(
-    uiState: UiState<HomePlanState>,
-) {
+fun HomePlanPage(uiState: UiState<HomePlanState>) {
     StatePage(uiState = uiState) {
         Column(
             modifier = Modifier

+ 22 - 0
feat/settings/build.gradle.kts

@@ -0,0 +1,22 @@
+plugins {
+    id("d.convention.library")
+    id("d.convention.compose")
+    id("d.convention.hilt")
+    id("d.convention.lifecycle")
+    id("d.convention.navigation")
+}
+
+android {
+    namespace = "com.zaojiao.app.feat.settings"
+}
+
+dependencies {
+    implementation(project(":core:common"))
+    implementation(project(":core:auth"))
+    implementation(project(":core:nav"))
+
+    implementation(project(":feat:design"))
+
+    implementation(project(":data:repo"))
+    implementation(project(":data:model"))
+}

+ 4 - 0
feat/settings/src/main/AndroidManifest.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+</manifest>

+ 16 - 0
feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsNavigation.kt

@@ -0,0 +1,16 @@
+package com.zaojiao.app.feat.settings
+
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptions
+import androidx.navigation.compose.composable
+import com.zaojiao.app.core.nav.LJGNavigator
+
+const val settings = "/settings/index"
+
+fun LJGNavigator.toSettings(navOptions: NavOptions? = null) {
+    this.navigate(settings)
+}
+
+fun NavGraphBuilder.settingRoute() {
+    composable(route = settings) { SettingsRoute() }
+}

+ 45 - 0
feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsPage.kt

@@ -0,0 +1,45 @@
+package com.zaojiao.app.feat.settings
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+
+@Composable
+internal fun SettingsRoute(
+    viewModel: SettingsViewModel = hiltViewModel()
+) {
+    SettingsPage(
+        logout = viewModel::logout
+    )
+}
+
+@Composable
+fun SettingsPage(
+    logout: () -> Unit,
+) {
+    Column(
+        modifier = Modifier
+            .statusBarsPadding()
+            .fillMaxWidth()
+    ) {
+        Button(
+            onClick = logout,
+            modifier = Modifier
+                .fillMaxWidth()
+                .height(60.dp)
+        ) {
+            Text(
+                text = "退出",
+                modifier = Modifier.align(Alignment.CenterVertically)
+            )
+        }
+    }
+}

+ 20 - 0
feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsViewModel.kt

@@ -0,0 +1,20 @@
+package com.zaojiao.app.feat.settings
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.zaojiao.app.core.auth.data.AuthRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class SettingsViewModel @Inject constructor(
+    private val authRepository: AuthRepository,
+) : ViewModel() {
+
+    fun logout() {
+        viewModelScope.launch {
+            authRepository.clearToken()
+        }
+    }
+}

+ 2 - 0
settings.gradle.kts

@@ -33,3 +33,5 @@ include(":data:domain")
 
 include(":feat:design")
 include(":feat:home")
+include(":feat:baby")
+include(":feat:settings")