Ver Fonte

add(auth): add login/logout

zhaoyadi há 1 ano atrás
pai
commit
010e151479
65 ficheiros alterados com 1059 adições e 273 exclusões
  1. 1 0
      .idea/gradle.xml
  2. 1 0
      app/build.gradle.kts
  3. 2 1
      app/src/main/AndroidManifest.xml
  4. 75 1
      app/src/main/java/com/zaojiao/app/MainActivity.kt
  5. 2 6
      app/src/main/java/com/zaojiao/app/ui/App.kt
  6. 4 0
      core/auth/build.gradle.kts
  7. 6 0
      core/auth/src/main/aidl/com/zaojiao/app/core/auth/ILoginCallback.aidl
  8. 2 2
      core/auth/src/main/kotlin/com/zaojiao/app/core/auth/data/AuthRepository.kt
  9. 8 0
      core/auth/src/main/kotlin/com/zaojiao/app/core/auth/data/LocalAuthData.kt
  10. 48 5
      core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/LoginActivity.kt
  11. 4 0
      core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/LoginParcel.kt
  12. 2 0
      core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/LoginViewModel.kt
  13. 3 2
      core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/TokenActivity.kt
  14. 14 12
      core/auth/src/main/kotlin/com/zaojiao/app/core/auth/utils/AuthUtils.kt
  15. 10 0
      core/auth/src/main/kotlin/com/zaojiao/app/core/auth/utils/ITokenRequest.kt
  16. 2 2
      core/common/src/main/kotlin/com/zaojiao/app/core/common/state/DataState.kt
  17. 2 0
      core/http/src/main/kotlin/com/zaojiao/app/core/http/common/NetResult.kt
  18. 14 0
      core/navx/build.gradle.kts
  19. 4 0
      core/navx/src/main/AndroidManifest.xml
  20. 17 0
      core/navx/src/main/java/com/zaojiao/app/core/navx/LoginNavigation.kt
  21. 33 0
      core/navx/src/main/java/com/zaojiao/app/core/navx/NavXUtils.kt
  22. 0 43
      data/domain/src/main/kotlin/com/zaojiao/app/data/domain/AccountUseCase.kt
  23. 8 1
      data/local/src/main/kotlin/com/zaojiao/app/data/local/user/LocalUserData.kt
  24. 3 2
      data/local/src/main/proto/com.zaojiao.app.data.local/user_preferences.proto
  25. 1 0
      data/model/src/main/kotlin/com/zaojiao/app/data/model/UserGetV2Model.kt
  26. 8 0
      data/model/src/main/kotlin/com/zaojiao/app/data/model/interactive/InteractiveCountModel.kt
  27. 9 0
      data/model/src/main/kotlin/com/zaojiao/app/data/model/micropage/MicroPageConfigModel.kt
  28. 3 0
      data/model/src/main/kotlin/com/zaojiao/app/data/model/micropage/MicroPageJsonModel.kt
  29. 5 0
      data/model/src/main/kotlin/com/zaojiao/app/data/model/micropage/MicroPageTitleModel.kt
  30. 2 1
      data/model/src/main/kotlin/com/zaojiao/app/data/model/user/UserModel.kt
  31. 4 18
      data/remote/src/main/kotlin/com/zaojiao/app/data/remote/RemoteLoginData.kt
  32. 12 11
      data/remote/src/main/kotlin/com/zaojiao/app/data/remote/RemoteUserData.kt
  33. 27 0
      data/remote/src/main/kotlin/com/zaojiao/app/data/remote/api/HomeApi.kt
  34. 9 0
      data/remote/src/main/kotlin/com/zaojiao/app/data/remote/api/UserApi.kt
  35. 4 0
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/HomeRepository.kt
  36. 0 17
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/LoginRepository.kt
  37. 4 5
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/UserRepository.kt
  38. 5 5
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/di/RepoModule.kt
  39. 15 1
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/BabyRepositoryImpl.kt
  40. 9 0
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/HomeRepositoryImpl.kt
  41. 0 53
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/LoginRepositoryImpl.kt
  42. 35 16
      data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/UserRepositoryImpl.kt
  43. 13 1
      feat/baby/src/main/java/com/zaojiao/app/feat/baby/list/BabyListPage.kt
  44. 81 0
      feat/design/src/main/kotlin/com/zaojiao/app/feat/design/AppBar.kt
  45. 61 0
      feat/design/src/main/kotlin/com/zaojiao/app/feat/design/EmptyState.kt
  46. 30 18
      feat/design/src/main/kotlin/com/zaojiao/app/feat/design/StatePage.kt
  47. 1 0
      feat/home/build.gradle.kts
  48. 1 1
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCoursePage.kt
  49. 0 2
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/index/HomeIndexViewModel.kt
  50. 4 1
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalAccount.kt
  51. 4 1
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalContent.kt
  52. 9 5
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalEngage.kt
  53. 36 8
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalPage.kt
  54. 11 3
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalService.kt
  55. 4 1
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalTopBar.kt
  56. 8 1
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalUiState.kt
  57. 2 0
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalUserBar.kt
  58. 22 11
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalViewModel.kt
  59. 1 1
      feat/home/src/main/kotlin/com/zaojiao/app/feat/home/plan/HomePlanViewModel.kt
  60. 107 0
      feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsCommon.kt
  61. 132 0
      feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsIndexItem.kt
  62. 4 4
      feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsNavigation.kt
  63. 61 11
      feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsPage.kt
  64. 49 0
      feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsVideo.kt
  65. 1 0
      settings.gradle.kts

+ 1 - 0
.idea/gradle.xml

@@ -31,6 +31,7 @@
             <option value="$PROJECT_DIR$/core/common" />
             <option value="$PROJECT_DIR$/core/http" />
             <option value="$PROJECT_DIR$/core/nav" />
+            <option value="$PROJECT_DIR$/core/navx" />
             <option value="$PROJECT_DIR$/data" />
             <option value="$PROJECT_DIR$/data/domain" />
             <option value="$PROJECT_DIR$/data/local" />

+ 1 - 0
app/build.gradle.kts

@@ -29,6 +29,7 @@ dependencies {
     implementation(project(":core:common"))
     implementation(project(":core:auth"))
     implementation(project(":core:nav"))
+    implementation(project(":core:navx"))
 
     implementation(project(":data:domain"))
 

+ 2 - 1
app/src/main/AndroidManifest.xml

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

+ 75 - 1
app/src/main/java/com/zaojiao/app/MainActivity.kt

@@ -1,7 +1,10 @@
 package com.zaojiao.app
 
+import android.content.Intent
+import android.os.BatteryManager
 import android.os.Build
 import android.os.Bundle
+import android.util.Log
 import android.view.WindowManager
 import androidx.activity.compose.setContent
 import androidx.compose.foundation.isSystemInDarkTheme
@@ -13,12 +16,24 @@ 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.data.AuthRepository
+import com.zaojiao.app.core.auth.model.TokenModel
+import com.zaojiao.app.core.auth.ui.LoginActivity
 import com.zaojiao.app.core.auth.utils.AuthUtils
+import com.zaojiao.app.core.auth.utils.ITokenRequest
 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 kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicInteger
 import javax.inject.Inject
 
 private val LightBackgroundTheme = BackgroundTheme(color = Color.White)
@@ -26,12 +41,31 @@ private val LightBackgroundTheme = BackgroundTheme(color = Color.White)
 private val DarkBackgroundTheme = BackgroundTheme(color = Color.Black)
 
 @AndroidEntryPoint
-class MainActivity : FragmentActivity() {
+class MainActivity : FragmentActivity(), ITokenRequest, CoroutineScope by MainScope() {
+    companion object {
+        private val tokenFlag = 99
+        private val requests = ConcurrentHashMap<Int, CompletableFuture<TokenModel?>>()
+        private val nextRequestCode = AtomicInteger(0)
+    }
+
     private lateinit var windowInsetsController: WindowInsetsControllerCompat
+
+    @Inject
+    public lateinit var authRepository: AuthRepository
+
     override fun onCreate(savedInstanceState: Bundle?) {
         AuthUtils.attach(this)
         super.onCreate(savedInstanceState)
         configureSystemBar()
+
+        getSystemService(BatteryManager::class.java)
+
+        launch {
+            authRepository.state.collect {
+                Log.e("MainActivity", "onCreate: $it")
+            }
+        }
+
         setContent {
             val systemUiController = rememberSystemUiController()
 
@@ -68,6 +102,46 @@ class MainActivity : FragmentActivity() {
 
     override fun onDestroy() {
         AuthUtils.detach()
+        this.cancel()
         super.onDestroy()
     }
+
+    override fun requestToken(): CompletableFuture<TokenModel?> {
+        val requestCode = nextRequestCode.getAndIncrement()
+        val tokenRequestCode = requestCode + tokenFlag shl 10
+        val future = CompletableFuture<TokenModel?>()
+        requests[tokenRequestCode] = future
+
+        val intent = Intent(this, LoginActivity::class.java)
+        startActivityForResult(intent, tokenRequestCode)
+        return future
+    }
+
+    override fun requestLogin() {
+        startActivity(Intent(this, LoginActivity::class.java))
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        super.onActivityResult(requestCode, resultCode, data)
+        if (requestCode shr 10 == tokenFlag) {
+            when (resultCode) {
+                RESULT_CANCELED -> {
+                    requests[requestCode]!!.complete(null)
+                }
+
+                RESULT_OK -> {
+                    requests[requestCode]!!.complete(
+                        TokenModel(
+                            accessToken = "1234",
+                            refreshToken = "5678",
+                        )
+                    )
+                }
+
+                else -> {
+                    requests[requestCode]!!.complete(null)
+                }
+            }
+        }
+    }
 }

+ 2 - 6
app/src/main/java/com/zaojiao/app/ui/App.kt

@@ -2,10 +2,9 @@ package com.zaojiao.app.ui
 
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-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.core.navx.loginRoute
 import com.zaojiao.app.feat.baby.babyRoute
 import com.zaojiao.app.feat.home.navigation.homePage
 import com.zaojiao.app.feat.home.navigation.homeRoute
@@ -26,9 +25,6 @@ fun App(
         homeRoute()
         babyRoute()
         settingRoute()
-
-        composable(route = "/login/sms") {
-            LoginPage()
-        }
+        loginRoute()
     }
 }

+ 4 - 0
core/auth/build.gradle.kts

@@ -9,6 +9,10 @@ plugins {
 
 android {
     namespace = "com.zaojiao.app.core.auth"
+
+    buildFeatures {
+        aidl = true
+    }
 }
 
 dependencies {

+ 6 - 0
core/auth/src/main/aidl/com/zaojiao/app/core/auth/ILoginCallback.aidl

@@ -0,0 +1,6 @@
+// ILoginCallback.aidl
+package com.zaojiao.app.core.auth;
+
+interface ILoginCallback {
+    void callback(int result);
+}

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

@@ -15,12 +15,12 @@ class AuthRepository constructor(
     private val remoteTokenData: RemoteAuthData,
 ) {
     val token: Flow<TokenModel?> = localAuthData.token.onEach {
-        AuthUtils.stateFlow.emit(if (it == null) AuthState.OUT else AuthState.IN)
+        AuthUtils._state = 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)
+        AuthUtils._state = if (it == null) AuthState.OUT else AuthState.IN
         if (it == null) AuthState.OUT else AuthState.IN
     }.distinctUntilChanged()
 

+ 8 - 0
core/auth/src/main/kotlin/com/zaojiao/app/core/auth/data/LocalAuthData.kt

@@ -6,15 +6,19 @@ import androidx.datastore.preferences.core.edit
 import androidx.datastore.preferences.core.stringPreferencesKey
 import androidx.datastore.preferences.preferencesDataStoreFile
 import com.zaojiao.app.core.auth.model.TokenModel
+import com.zaojiao.app.core.auth.utils.AuthState
 import com.zaojiao.app.core.common.remote.AppDispatchers
 import com.zaojiao.app.core.common.remote.Dispatcher
 import com.zaojiao.app.core.common.remote.di.ApplicationScope
 import dagger.hilt.android.qualifiers.ApplicationContext
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import javax.inject.Inject
+import javax.inject.Singleton
 
+@Singleton
 class LocalAuthData @Inject constructor(
     @ApplicationContext context: Context,
     @Dispatcher(AppDispatchers.IO) ioDispatcher: CoroutineDispatcher,
@@ -50,6 +54,10 @@ class LocalAuthData @Inject constructor(
         }
     }
 
+    val state = token.map {
+        if (it == null) AuthState.OUT else AuthState.IN
+    }.distinctUntilChanged()
+
     suspend fun updateToken(token: TokenModel) {
         tokenPreferences.edit {
             it[accessTokenKey] = token.accessToken

+ 48 - 5
core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/LoginActivity.kt

@@ -2,24 +2,57 @@ package com.zaojiao.app.core.auth.ui
 
 import android.os.Bundle
 import androidx.activity.ComponentActivity
+import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
 import androidx.activity.compose.setContent
+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.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
 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.runtime.CompositionLocalProvider
+import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import com.zaojiao.app.core.auth.model.TokenModel
+import dagger.hilt.android.AndroidEntryPoint
 
+private val LocalTokenSave = compositionLocalOf<((token: TokenModel?) -> Unit)?> { null }
+
+@AndroidEntryPoint
 class LoginActivity : ComponentActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+
         setContent {
-            MaterialTheme {
-                LoginPage()
+            val navController = rememberNavController()
+
+            CompositionLocalProvider(
+                LocalTokenSave provides {
+                    TokenActivity.start(this@LoginActivity, it?.accessToken, it?.refreshToken)
+                    finish()
+                }
+            ) {
+                MaterialTheme {
+                    NavHost(
+                        navController = navController,
+                        startDestination = "/",
+                    ) {
+                        composable("/") {
+                            LoginPage()
+                        }
+                    }
+                }
             }
         }
     }
@@ -31,17 +64,27 @@ fun LoginPage(
 ) {
     Column(
         modifier = Modifier
-            .fillMaxSize()
-            .statusBarsPadding(),
+            .statusBarsPadding()
+            .fillMaxSize(),
     ) {
         CloseButton()
 
+        val callback = LocalTokenSave.current
+
+        Box(
+            modifier = Modifier
+                .clickable {
+                    callback?.invoke(null)
+                }
+                .background(color = Color.Green)
+                .weight(1f)
+                .fillMaxWidth()
+        )
     }
 }
 
 @Composable
 fun CloseButton() {
-
     Icon(
         imageVector = Icons.Filled.Close,
         contentDescription = "关闭按钮",

+ 4 - 0
core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/LoginParcel.kt

@@ -0,0 +1,4 @@
+package com.zaojiao.app.core.auth.ui
+
+class LoginParcel {
+}

+ 2 - 0
core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/LoginViewModel.kt

@@ -2,8 +2,10 @@ package com.zaojiao.app.core.auth.ui
 
 import androidx.lifecycle.ViewModel
 import com.zaojiao.app.core.auth.data.AuthRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
 import javax.inject.Inject
 
+@HiltViewModel
 class LoginViewModel @Inject constructor(
     private val authRepository: AuthRepository,
 ) : ViewModel() {

+ 3 - 2
core/auth/src/main/kotlin/com/zaojiao/app/core/auth/ui/TokenActivity.kt

@@ -2,6 +2,7 @@ package com.zaojiao.app.core.auth.ui
 
 import android.content.Context
 import android.content.Intent
+import android.net.Uri
 import android.os.Bundle
 import androidx.appcompat.app.AppCompatActivity
 import com.zaojiao.app.core.auth.data.AuthRepository
@@ -19,8 +20,8 @@ class TokenActivity : AppCompatActivity() {
 
         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)
+            intent.data =
+                Uri.parse("token://www.luojigou.vip/app?access_token=$accessToken&&refreshToken=$refreshToken")
             context.startActivity(intent)
         }
     }

+ 14 - 12
core/auth/src/main/kotlin/com/zaojiao/app/core/auth/utils/AuthUtils.kt

@@ -1,34 +1,36 @@
 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
+    private var request: ITokenRequest? = null
 
-    internal val stateFlow: MutableStateFlow<AuthState?> = MutableStateFlow(null)
-    val state: Flow<AuthState> get() = stateFlow.filterNotNull().distinctUntilChanged()
+    internal var _state: AuthState = AuthState.OUT
+    val state: AuthState get() = _state
 
-    fun attach(activity: FragmentActivity) {
-        AuthUtils.activity = activity
+    fun attach(request: ITokenRequest) {
+        this.request = request
     }
 
     fun detach() {
-        activity = null
+        request = null
     }
 
-    fun navToLogin(): TokenModel? {
-        assert(activity != null) { "AuthUtils must called attach in MainActivity" }
-        val completableFuture = CompletableFuture<TokenModel?>()
+    @Deprecated("don't use")
+    fun navToLogin(): CompletableFuture<TokenModel?> {
+        assert(request != null) { "AuthUtils must called attach in MainActivity" }
+        return request!!.requestToken()
+    }
 
-        return completableFuture.get()
+    fun requestLogin() {
+        assert(request != null) { "AuthUtils must called attach in MainActivity" }
+        request!!.requestLogin()
     }
 }

+ 10 - 0
core/auth/src/main/kotlin/com/zaojiao/app/core/auth/utils/ITokenRequest.kt

@@ -0,0 +1,10 @@
+package com.zaojiao.app.core.auth.utils
+
+import com.zaojiao.app.core.auth.model.TokenModel
+import java.util.concurrent.CompletableFuture
+
+interface ITokenRequest {
+    fun requestToken(): CompletableFuture<TokenModel?>
+
+    fun requestLogin()
+}

+ 2 - 2
core/common/src/main/kotlin/com/zaojiao/app/core/common/state/DataState.kt

@@ -1,7 +1,7 @@
 package com.zaojiao.app.core.common.state
 
-sealed interface DataState<T> {
+sealed interface DataState<out T> {
     data class Success<T>(val data: T?) : DataState<T>
 
-    data class Failure<T>(val throwable: Throwable) : DataState<T>
+    data class Failure(val throwable: Throwable) : DataState<Nothing>
 }

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

@@ -1,5 +1,6 @@
 package com.zaojiao.app.core.http.common
 
+import android.util.Log
 import com.squareup.moshi.Json
 import com.zaojiao.app.core.common.state.DataState
 
@@ -35,6 +36,7 @@ suspend fun <T> dataStateCatch(action: suspend () -> NetResult<T>): DataState<T>
         val result = action.invoke()
         result.toDataState()
     } catch (e: Throwable) {
+        Log.e("dataStateCatch", "error", e)
         DataState.Failure(e)
     }
 }

+ 14 - 0
core/navx/build.gradle.kts

@@ -0,0 +1,14 @@
+plugins {
+    id("d.convention.library")
+    id("d.convention.compose")
+    id("d.convention.navigation")
+}
+
+android {
+    namespace = "com.zaojiao.app.core.navx"
+}
+
+dependencies {
+    implementation(project(":core:auth"))
+    implementation(project(":core:nav"))
+}

+ 4 - 0
core/navx/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
core/navx/src/main/java/com/zaojiao/app/core/navx/LoginNavigation.kt

@@ -0,0 +1,17 @@
+package com.zaojiao.app.core.navx
+
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavOptions
+import androidx.navigation.compose.composable
+import com.zaojiao.app.core.auth.ui.LoginPage
+import com.zaojiao.app.core.nav.LJGNavigator
+
+const val loginSmsPage = "/login/sms"
+
+fun LJGNavigator.toLoginSms(navOptions: NavOptions? = null) {
+    this.navigate(loginSmsPage)
+}
+
+fun NavGraphBuilder.loginRoute() {
+    composable(route = loginSmsPage) { LoginPage() }
+}

+ 33 - 0
core/navx/src/main/java/com/zaojiao/app/core/navx/NavXUtils.kt

@@ -0,0 +1,33 @@
+package com.zaojiao.app.core.navx
+
+import android.util.Log
+import com.zaojiao.app.core.auth.utils.AuthState
+import com.zaojiao.app.core.auth.utils.AuthUtils
+import com.zaojiao.app.core.nav.LJGNavigator
+import kotlin.concurrent.thread
+
+object NavXUtils {
+    fun navToRoute(
+        route: String,
+        needLogin: Boolean = true,
+    ) {
+        if (needLogin && AuthUtils.state == AuthState.OUT) {
+            AuthUtils.requestLogin()
+        } else {
+            AuthUtils.requestLogin()
+//            LJGNavigator.navigate(route)
+        }
+    }
+
+    fun callWithLogin(callback: () -> Unit) {
+        if (AuthUtils.state == AuthState.OUT) {
+            thread {
+                val result = AuthUtils.navToLogin().get()
+                Log.e("NavXUtils", "callWithLogin: $result")
+            }
+        } else {
+            callback()
+        }
+
+    }
+}

+ 0 - 43
data/domain/src/main/kotlin/com/zaojiao/app/data/domain/AccountUseCase.kt

@@ -1,43 +0,0 @@
-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.LoginState
-import com.zaojiao.app.data.repo.UserRepository
-import dagger.hilt.android.qualifiers.ApplicationContext
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.onEach
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class AccountUseCase @Inject constructor(
-    private val authRepository: AuthRepository,
-    private val userRepository: UserRepository,
-    private val babyRepository: BabyRepository,
-    @ApplicationContext private val context: Context,
-) {
-    val flow: Flow<LoginState> = flow {
-        authRepository.state.onEach {
-            when (it) {
-                AuthState.IN -> {
-                    userRepository.getUser()
-                    babyRepository.getBaby()
-                }
-
-                AuthState.OUT -> {
-                    userRepository.deleteUser()
-                    babyRepository.deleteAllBaby()
-
-                }
-            }
-        }
-    }
-
-    operator fun invoke() {
-
-    }
-}

+ 8 - 1
data/local/src/main/kotlin/com/zaojiao/app/data/local/user/LocalUserData.kt

@@ -4,7 +4,7 @@ import android.util.Log
 import androidx.datastore.core.DataStore
 import com.zaojiao.app.data.local.UserPreferences
 import com.zaojiao.app.data.local.copy
-import com.zaojiao.app.data.model.UserModel
+import com.zaojiao.app.data.model.user.UserModel
 import kotlinx.coroutines.flow.map
 import java.io.IOException
 import javax.inject.Inject
@@ -19,6 +19,7 @@ class LocalUserData @Inject constructor(
             gender = it.gender,
             avatarUrl = it.avatarUrl,
             phone = it.phone,
+            hobby = it.hobby,
             birthday = it.birthday,
             bindWx = it.bindWx,
             bindPhone = it.bindPhone,
@@ -44,6 +45,11 @@ class LocalUserData @Inject constructor(
                     } else {
                         clearBirthday()
                     }
+                    if (userModel.hobby != null) {
+                        hobby = userModel.hobby!!
+                    } else {
+                        clearHobby()
+                    }
                     bindWx = userModel.bindWx
                     bindPhone = userModel.bindPhone
                 }
@@ -82,6 +88,7 @@ class LocalUserData @Inject constructor(
                 clearGender()
                 clearAvatarUrl()
                 clearPhone()
+                clearHobby()
                 clearBirthday()
                 clearBindWx()
                 clearBindPhone()

+ 3 - 2
data/local/src/main/proto/com.zaojiao.app.data.local/user_preferences.proto

@@ -13,6 +13,7 @@ message UserPreferences {
   string avatarUrl = 5;
   optional string phone = 6;
   optional string birthday = 7;
-  uint32 bindWx = 8;
-  uint32 bindPhone = 9;
+  optional string hobby = 8;
+  uint32 bindWx = 9;
+  uint32 bindPhone = 10;
 }

+ 1 - 0
data/model/src/main/kotlin/com/zaojiao/app/data/model/UserGetV2Model.kt

@@ -1,6 +1,7 @@
 package com.zaojiao.app.data.model
 
 import com.squareup.moshi.Json
+import com.zaojiao.app.data.model.user.UserModel
 
 data class UserGetV2Model(
     @Json(name = "appUser")

+ 8 - 0
data/model/src/main/kotlin/com/zaojiao/app/data/model/interactive/InteractiveCountModel.kt

@@ -0,0 +1,8 @@
+package com.zaojiao.app.data.model.interactive
+
+data class InteractiveCountModel(
+    val followerCount: Int = 0,
+    val fansCount: Int = 0,
+    val likeCount: Int = 0,
+    val collectionCount: Int = 0,
+)

+ 9 - 0
data/model/src/main/kotlin/com/zaojiao/app/data/model/micropage/MicroPageConfigModel.kt

@@ -0,0 +1,9 @@
+package com.zaojiao.app.data.model.micropage
+
+data class MicroPageConfigModel(
+    val type: String,
+    val title: String,
+    val description: String,
+    val category: String,
+    val gmtStart: String,
+) : MicroPageJsonModel

+ 3 - 0
data/model/src/main/kotlin/com/zaojiao/app/data/model/micropage/MicroPageJsonModel.kt

@@ -0,0 +1,3 @@
+package com.zaojiao.app.data.model.micropage
+
+sealed interface MicroPageJsonModel

+ 5 - 0
data/model/src/main/kotlin/com/zaojiao/app/data/model/micropage/MicroPageTitleModel.kt

@@ -0,0 +1,5 @@
+package com.zaojiao.app.data.model.micropage
+
+data class MicroPageTitleModel(
+    val title: String,
+) : MicroPageJsonModel

+ 2 - 1
data/model/src/main/kotlin/com/zaojiao/app/data/model/UserModel.kt → data/model/src/main/kotlin/com/zaojiao/app/data/model/user/UserModel.kt

@@ -1,4 +1,4 @@
-package com.zaojiao.app.data.model
+package com.zaojiao.app.data.model.user
 
 data class UserModel(
     val id: String,
@@ -6,6 +6,7 @@ data class UserModel(
     val gender: Int,
     val avatarUrl: String,
     val phone: String?,
+    val hobby: String?,
     val birthday: String?,
     val bindWx: Int,
     val bindPhone: Int,

+ 4 - 18
data/remote/src/main/kotlin/com/zaojiao/app/data/remote/RemoteLoginData.kt

@@ -1,6 +1,7 @@
 package com.zaojiao.app.data.remote
 
 import com.zaojiao.app.core.common.state.DataState
+import com.zaojiao.app.core.http.common.dataStateCatch
 import com.zaojiao.app.data.model.TokenModel
 import com.zaojiao.app.data.remote.api.LoginApi
 import javax.inject.Inject
@@ -11,29 +12,14 @@ class RemoteLoginData @Inject constructor(
     private val loginApi: LoginApi,
 ) {
     suspend fun loginBySms(phone: String, code: String): DataState<TokenModel?> {
-        return try {
-            val result = loginApi.loginBySms(phone, code).data
-            DataState.Success(result)
-        } catch (throwable: Throwable) {
-            DataState.Failure(throwable)
-        }
+        return dataStateCatch { loginApi.loginBySms(phone, code) }
     }
 
     suspend fun loginByWechat(code: String): DataState<TokenModel?> {
-        return try {
-            val result = loginApi.loginByWechat(code).data
-            DataState.Success(result)
-        } catch (throwable: Throwable) {
-            DataState.Failure(throwable)
-        }
+        return dataStateCatch { loginApi.loginByWechat(code) }
     }
 
     suspend fun loginByOneClick(code: String): DataState<TokenModel?> {
-        return try {
-            val result = loginApi.loginByOneClick(code).data
-            DataState.Success(result)
-        } catch (throwable: Throwable) {
-            DataState.Failure(throwable)
-        }
+        return dataStateCatch { loginApi.loginByOneClick(code) }
     }
 }

+ 12 - 11
data/remote/src/main/kotlin/com/zaojiao/app/data/remote/RemoteUserData.kt

@@ -1,18 +1,11 @@
 package com.zaojiao.app.data.remote
 
-import com.zaojiao.app.core.common.remote.AppDispatchers
-import com.zaojiao.app.core.common.remote.Dispatcher
 import com.zaojiao.app.core.common.state.DataState
-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.UserGetV2Model
-import com.zaojiao.app.data.model.UserModel
+import com.zaojiao.app.data.model.interactive.InteractiveCountModel
+import com.zaojiao.app.data.model.user.UserModel
 import com.zaojiao.app.data.remote.api.UserApi
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.withContext
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -21,6 +14,14 @@ class RemoteUserData @Inject constructor(
     private val userApi: UserApi,
 ) {
     suspend fun getUser(): DataState<UserGetV2Model> {
-        return tryRequest { userApi.getUser().toDataState() }
+        return dataStateCatch { userApi.getUser() }
+    }
+
+    suspend fun getUserInfo(): DataState<UserModel> {
+        return dataStateCatch { userApi.getUserInfo() }
+    }
+
+    suspend fun getUserInactive(): DataState<InteractiveCountModel> {
+        return dataStateCatch { userApi.getUserInactive() }
     }
 }

+ 27 - 0
data/remote/src/main/kotlin/com/zaojiao/app/data/remote/api/HomeApi.kt

@@ -0,0 +1,27 @@
+package com.zaojiao.app.data.remote.api
+
+import com.zaojiao.app.core.http.common.NetHeaders
+import com.zaojiao.app.core.http.common.NetResult
+import retrofit2.http.GET
+import retrofit2.http.Headers
+import retrofit2.http.Query
+
+interface HomeApi {
+    @GET("/app/app/configPage/homePage/v2")
+    @Headers(NetHeaders.NO_TOKEN)
+    suspend fun getBanner1(
+        @Query(value = "type") type: String = "phone",
+    ): NetResult<String>
+
+    @GET("/app/app/configPage/homePage/v2")
+    @Headers(NetHeaders.NO_TOKEN)
+    suspend fun getHomeJson(
+        @Query(value = "type") type: String = "phone",
+    ): NetResult<String>
+
+    @GET("/app/app/configPage/gamePage/alert")
+    @Headers(NetHeaders.NO_TOKEN)
+    suspend fun getHomeDialog(
+        @Query(value = "type") type: String = "phone",
+    ): NetResult<String>
+}

+ 9 - 0
data/remote/src/main/kotlin/com/zaojiao/app/data/remote/api/UserApi.kt

@@ -2,6 +2,8 @@ package com.zaojiao.app.data.remote.api
 
 import com.zaojiao.app.data.model.UserGetV2Model
 import com.zaojiao.app.core.http.common.NetResult
+import com.zaojiao.app.data.model.interactive.InteractiveCountModel
+import com.zaojiao.app.data.model.user.UserModel
 import retrofit2.http.GET
 
 
@@ -9,4 +11,11 @@ interface UserApi {
 
     @GET("/app/user/getV2")
     suspend fun getUser(): NetResult<UserGetV2Model>
+
+    @GET("/app/user/info")
+    suspend fun getUserInfo(): NetResult<UserModel>
+
+
+    @GET("/app/user/attach/count")
+    suspend fun getUserInactive(): NetResult<InteractiveCountModel>
 }

+ 4 - 0
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/HomeRepository.kt

@@ -0,0 +1,4 @@
+package com.zaojiao.app.data.repo
+
+interface HomeRepository {
+}

+ 0 - 17
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/LoginRepository.kt

@@ -1,17 +0,0 @@
-package com.zaojiao.app.data.repo
-
-import kotlinx.coroutines.flow.Flow
-
-enum class LoginState { IN, OUT }
-
-interface LoginRepository {
-    val state: Flow<LoginState>
-
-    fun loginBySms(phone: String, code: String)
-
-    fun loginByWechat(code: String)
-
-    fun loginByOneClick(code: String)
-
-    fun logout()
-}

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

@@ -1,20 +1,19 @@
 package com.zaojiao.app.data.repo
 
-import com.zaojiao.app.data.model.UserModel
+import com.zaojiao.app.data.model.interactive.InteractiveCountModel
+import com.zaojiao.app.data.model.user.UserModel
 import kotlinx.coroutines.flow.Flow
 
 interface UserRepository {
     val user: Flow<UserModel?>
 
-    val fansCount: Flow<Int>
-
-    val followerCount: Flow<Int>
+    val interactiveCount: Flow<InteractiveCountModel>
 
     val phoneNumber: Flow<String?>
 
     val wechatState: Flow<Boolean>
 
-    suspend fun getUser()
+    suspend fun fetchData()
 
     suspend fun updateUser(userModel: UserModel)
 

+ 5 - 5
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/di/RepoModule.kt

@@ -1,10 +1,10 @@
 package com.zaojiao.app.data.repo.di
 
 import com.zaojiao.app.data.repo.BabyRepository
-import com.zaojiao.app.data.repo.LoginRepository
+import com.zaojiao.app.data.repo.HomeRepository
 import com.zaojiao.app.data.repo.UserRepository
 import com.zaojiao.app.data.repo.impl.BabyRepositoryImpl
-import com.zaojiao.app.data.repo.impl.LoginRepositoryImpl
+import com.zaojiao.app.data.repo.impl.HomeRepositoryImpl
 import com.zaojiao.app.data.repo.impl.UserRepositoryImpl
 import dagger.Binds
 import dagger.Module
@@ -29,7 +29,7 @@ interface RepoModule {
 
     @Binds
     @Singleton
-    fun bindLoginRepository(
-        loginRepositoryImpl: LoginRepositoryImpl,
-    ): LoginRepository
+    fun bindHomeRepository(
+        homeRepositoryImpl: HomeRepositoryImpl,
+    ): HomeRepository
 }

+ 15 - 1
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/BabyRepositoryImpl.kt

@@ -1,5 +1,7 @@
 package com.zaojiao.app.data.repo.impl
 
+import com.zaojiao.app.core.auth.data.LocalAuthData
+import com.zaojiao.app.core.auth.utils.AuthState
 import com.zaojiao.app.core.common.remote.di.ApplicationScope
 import com.zaojiao.app.core.common.state.DataState
 import com.zaojiao.app.data.local.baby.LocalBabyData
@@ -8,10 +10,11 @@ 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 kotlinx.coroutines.launch
 import javax.inject.Inject
 
 class BabyRepositoryImpl @Inject constructor(
+    private val localAuthData: LocalAuthData,
     private val localBabyData: LocalBabyData,
     private val remoteBabyData: RemoteBabyData,
     @ApplicationScope private val coroutineScope: CoroutineScope,
@@ -20,6 +23,17 @@ class BabyRepositoryImpl @Inject constructor(
 
     override val babyList: Flow<List<BabyModel>> get() = localBabyData.list
 
+    init {
+        coroutineScope.launch {
+            localAuthData.state.collect {
+                when (it) {
+                    AuthState.IN -> getBaby()
+                    AuthState.OUT -> deleteAllBaby()
+                }
+            }
+        }
+    }
+
     override suspend fun getBaby() {
         remoteBabyData.getBabyList().apply {
             when (this) {

+ 9 - 0
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/HomeRepositoryImpl.kt

@@ -0,0 +1,9 @@
+package com.zaojiao.app.data.repo.impl
+
+import com.zaojiao.app.data.repo.HomeRepository
+import javax.inject.Inject
+
+class HomeRepositoryImpl @Inject constructor(
+
+) : HomeRepository {
+}

+ 0 - 53
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/LoginRepositoryImpl.kt

@@ -1,53 +0,0 @@
-package com.zaojiao.app.data.repo.impl
-
-import com.zaojiao.app.core.common.remote.di.ApplicationScope
-import com.zaojiao.app.core.common.state.DataState
-import com.zaojiao.app.data.model.TokenModel
-import com.zaojiao.app.data.remote.RemoteLoginData
-import com.zaojiao.app.data.repo.LoginRepository
-import com.zaojiao.app.data.repo.LoginState
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class LoginRepositoryImpl @Inject constructor(
-    private val remoteLoginData: RemoteLoginData,
-    @ApplicationScope private val coroutineScope: CoroutineScope,
-) : LoginRepository {
-
-    override val state: Flow<LoginState> = flow {
-        emit(LoginState.IN)
-    }
-
-    override fun loginBySms(phone: String, code: String) {
-        coroutineScope.launch {
-            processTokenData(remoteLoginData.loginBySms(phone, code))
-        }
-    }
-
-    override fun loginByWechat(code: String) {
-        coroutineScope.launch {
-            processTokenData(remoteLoginData.loginByWechat(code))
-        }
-    }
-
-    override fun loginByOneClick(code: String) {
-        coroutineScope.launch {
-            processTokenData(remoteLoginData.loginByOneClick(code))
-        }
-    }
-
-    private suspend fun processTokenData(state: DataState<TokenModel?>) {
-
-    }
-
-    override fun logout() {
-        coroutineScope.launch {
-
-        }
-    }
-}

+ 35 - 16
data/repo/src/main/kotlin/com/zaojiao/app/data/repo/impl/UserRepositoryImpl.kt

@@ -1,32 +1,31 @@
 package com.zaojiao.app.data.repo.impl
 
-import com.zaojiao.app.core.auth.utils.AuthUtils
+import com.zaojiao.app.core.auth.data.LocalAuthData
+import com.zaojiao.app.core.auth.utils.AuthState
 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
-import com.zaojiao.app.data.model.UserModel
+import com.zaojiao.app.data.model.interactive.InteractiveCountModel
+import com.zaojiao.app.data.model.user.UserModel
 import com.zaojiao.app.data.remote.RemoteUserData
 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 kotlinx.coroutines.launch
 import javax.inject.Inject
 
 
 class UserRepositoryImpl @Inject constructor(
+    private val localAuthData: LocalAuthData,
     private val localUserData: LocalUserData,
     private val remoteUserData: RemoteUserData,
     @ApplicationScope private val coroutineScope: CoroutineScope,
 ) : UserRepository {
-    override val user: Flow<UserModel?> get() = localUserData.user
+    override val user: Flow<UserModel?> = localUserData.user
 
-    private val _fansCount = MutableStateFlow(0)
-    override val fansCount: Flow<Int> get() = _fansCount
-
-    private val _followerCount = MutableStateFlow(0)
-    override val followerCount: Flow<Int> get() = _followerCount
+    private val _interactiveCount = MutableStateFlow(InteractiveCountModel())
+    override val interactiveCount: Flow<InteractiveCountModel> get() = _interactiveCount
 
     private val _phoneNumber = MutableStateFlow<String?>(null)
     override val phoneNumber: Flow<String?> get() = _phoneNumber
@@ -34,19 +33,39 @@ class UserRepositoryImpl @Inject constructor(
     private val _wechatState = MutableStateFlow(false)
     override val wechatState: Flow<Boolean> get() = _wechatState
 
-    override suspend fun getUser() {
-        remoteUserData.getUser().apply {
+    init {
+        coroutineScope.launch {
+            localAuthData.state.collect {
+                when (it) {
+                    AuthState.IN -> fetchData()
+                    AuthState.OUT -> cleanData()
+                }
+            }
+        }
+    }
+
+    override suspend fun fetchData() {
+        remoteUserData.getUserInfo().apply {
             when (this) {
                 is DataState.Failure -> {}
                 is DataState.Success -> {
                     data?.apply {
-                        localUserData.updateUser(this.userModel)
-                        _fansCount.emit(this.fansCount)
-                        _followerCount.emit(this.followCount)
+                        localUserData.updateUser(this)
                     }
                 }
             }
         }
+
+        remoteUserData.getUserInactive().apply {
+            if (this is DataState.Success) {
+                data?.apply { _interactiveCount.emit(this) }
+            }
+        }
+    }
+
+    private suspend fun cleanData() {
+        deleteUser()
+        _interactiveCount.emit(InteractiveCountModel())
     }
 
     override suspend fun updateUser(userModel: UserModel) {
@@ -55,6 +74,6 @@ class UserRepositoryImpl @Inject constructor(
 
 
     override suspend fun deleteUser() {
-
+        localUserData.deleteUser()
     }
 }

+ 13 - 1
feat/baby/src/main/java/com/zaojiao/app/feat/baby/list/BabyListPage.kt

@@ -1,8 +1,20 @@
 package com.zaojiao.app.feat.baby.list
 
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
 
 @Composable
 fun BabyListPage() {
-
+    Box(
+        modifier = Modifier.fillMaxSize()
+    ) {
+        Text(
+            text = "宝宝列表",
+            style = MaterialTheme.typography.bodyMedium,
+        )
+    }
 }

+ 81 - 0
feat/design/src/main/kotlin/com/zaojiao/app/feat/design/AppBar.kt

@@ -0,0 +1,81 @@
+package com.zaojiao.app.feat.design
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Paint
+import androidx.compose.ui.graphics.PaintingStyle
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+
+@Composable
+fun AppBar(
+    title: String,
+    backColor: Color = Colors.FF333333,
+    primary: Boolean = true,
+) {
+    var modifier: Modifier = Modifier
+    modifier = modifier.background(color = Color.White)
+    if (primary) {
+        modifier = modifier.statusBarsPadding()
+    }
+    modifier = modifier.fillMaxWidth()
+    modifier = modifier.height(44.dp)
+
+
+    Box(
+        modifier = modifier,
+    ) {
+        NavBack(color = backColor)
+        Text(
+            text = title,
+            modifier = Modifier.align(Alignment.Center),
+            style = TextStyle(
+                color = Colors.FF333333,
+                fontSize = 18.sp,
+                lineHeight = 25.sp,
+                fontWeight = FontWeight.SemiBold,
+            )
+        )
+    }
+}
+
+@Composable
+private fun BoxScope.NavBack(color: Color) {
+    Canvas(
+        modifier = Modifier
+            .align(Alignment.CenterStart)
+            .size(44.dp, 44.dp),
+    ) {
+        drawIntoCanvas { canvas ->
+            val paint = Paint()
+            paint.color = color
+            paint.strokeWidth = 2.5.dp.toPx()
+            paint.style = PaintingStyle.Stroke
+            paint.strokeCap = StrokeCap.Square
+
+            val path = Path()
+            path.moveTo(25f.dp.toPx(), 13f.dp.toPx())
+            path.lineTo(16f.dp.toPx(), 22f.dp.toPx())
+            path.lineTo(25f.dp.toPx(), 31f.dp.toPx())
+
+            canvas.drawPath(path, paint)
+        }
+    }
+}

+ 61 - 0
feat/design/src/main/kotlin/com/zaojiao/app/feat/design/EmptyState.kt

@@ -0,0 +1,61 @@
+package com.zaojiao.app.feat.design
+
+import androidx.compose.runtime.Composable
+
+/**
+ *  空状态
+ *
+ *  ```kt
+ *     EmptyState.NoMessage()
+ *  ```
+ *
+ *  [NoMessage]: 无消息
+ *  [NoFollow]: 无关注
+ *  [NoSearchResult]: 无搜索结果
+ */
+object EmptyState {
+    @Composable
+    fun NoMessage() {
+
+    }
+
+    /**
+     *  无关注
+     */
+    @Composable
+    fun NoFollow() {
+
+    }
+
+    /**
+     *  无搜索结果
+     */
+    @Composable
+    fun NoSearchResult() {
+
+    }
+
+    /**
+     *  无活动
+     */
+    @Composable
+    fun NoActivity() {
+
+    }
+
+    /**
+     *  无学习
+     */
+    @Composable
+    fun NoLearning() {
+
+    }
+
+    /**
+     *  无评论
+     */
+    @Composable
+    fun NoComment() {
+
+    }
+}

+ 30 - 18
feat/design/src/main/kotlin/com/zaojiao/app/feat/design/StatePage.kt

@@ -1,7 +1,10 @@
 package com.zaojiao.app.feat.design
 
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
@@ -10,14 +13,12 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.unit.sp
 
-sealed interface UiState<T> {
-    class Loading<T> : UiState<T>
+sealed interface UiState<out T> {
+    object Loading : UiState<Nothing>
 
     data class Success<T>(val state: T) : UiState<T>
 
-    sealed interface Failure<T> : UiState<T> {
-        class NetworkError<T> : Failure<T>
-    }
+    data class Failure(val code: Int) : UiState<Nothing>
 }
 
 @Composable
@@ -43,20 +44,31 @@ fun <T> StatePage(uiState: UiState<T>, onSuccess: @Composable T.() -> Unit) {
         }
 
         is UiState.Failure -> {
-            when (uiState) {
-                is UiState.Failure.NetworkError -> {
-                    Box(
-                        modifier = Modifier.fillMaxSize(),
-                    ) {
-                        Text(
-                            text = "error state",
-                            modifier = Modifier.align(Alignment.Center),
-                            style = TextStyle(
-                                fontSize = 18.sp,
-                                color = Color.Red,
-                            )
+            Box(
+                modifier = Modifier.fillMaxSize(),
+            ) {
+                Column(
+                    modifier = Modifier
+                        .align(Alignment.Center)
+                        .wrapContentHeight()
+                        .fillMaxWidth(),
+                    horizontalAlignment = Alignment.CenterHorizontally,
+                ) {
+                    Text(
+                        text = "error state",
+                        style = TextStyle(
+                            fontSize = 18.sp,
+                            color = Color.Red,
                         )
-                    }
+                    )
+
+                    Text(
+                        text = "code: ${uiState.code}",
+                        style = TextStyle(
+                            fontSize = 18.sp,
+                            color = Color.Red,
+                        )
+                    )
                 }
             }
         }

+ 1 - 0
feat/home/build.gradle.kts

@@ -14,6 +14,7 @@ dependencies {
     implementation(project(":core:common"))
     implementation(project(":core:auth"))
     implementation(project(":core:nav"))
+    implementation(project(":core:navx"))
 
     implementation(project(":data:repo"))
     implementation(project(":data:model"))

+ 1 - 1
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/course/HomeCoursePage.kt

@@ -31,7 +31,7 @@ fun HomeCoursePage() {
     ) {
         val width = Screen.width()
 
-        StatePage(uiState = UiState.Loading<Any>()) {
+        StatePage(uiState = UiState.Loading) {
             LazyColumn(
                 modifier = Modifier.fillMaxWidth()
             ) {

+ 0 - 2
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/index/HomeIndexViewModel.kt

@@ -23,8 +23,6 @@ class HomeIndexViewModel @Inject constructor(
         combine(authRepository.state, babyRepository.current) { auth, baby ->
             when (auth) {
                 AuthState.IN -> {
-                    babyRepository.getBaby()
-
                     if (baby != null) {
                         HomeIndexBabyUiState(
                             name = baby.name,

+ 4 - 1
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalAccount.kt

@@ -31,7 +31,10 @@ import com.zaojiao.app.feat.design.shadow
 
 
 @Composable
-fun HomePersonalAccount() {
+fun HomePersonalAccount(
+    navToPageWithLogin: (route: String) -> Unit,
+    navToPageWithoutLogin: (route: String) -> Unit,
+) {
     Box(
         modifier = Modifier
             .padding(

+ 4 - 1
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalContent.kt

@@ -30,7 +30,10 @@ import com.zaojiao.app.feat.home.R
 import com.zaojiao.app.feat.design.shadow
 
 @Composable
-fun HomePersonalContent() {
+fun HomePersonalContent(
+    navToPageWithLogin: (route: String) -> Unit,
+    navToPageWithoutLogin: (route: String) -> Unit,
+) {
     Box(
         modifier = Modifier
             .padding(

+ 9 - 5
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalEngage.kt

@@ -19,7 +19,11 @@ import com.zaojiao.app.feat.design.Spacer
 
 
 @Composable
-fun HomePersonalEngage() {
+fun HomePersonalEngage(
+    interactiveUiState: HomePersonalInteractiveUiState,
+    navToPageWithLogin: (route: String) -> Unit,
+    navToPageWithoutLogin: (route: String) -> Unit,
+) {
     Row(
         modifier = Modifier
             .padding(bottom = 6.dp)
@@ -27,10 +31,10 @@ fun HomePersonalEngage() {
             .fillMaxWidth(),
         horizontalArrangement = Arrangement.SpaceAround,
     ) {
-        HomePersonalEngageItem(num = 20, title = "关注")
-        HomePersonalEngageItem(num = 200, title = "粉丝")
-        HomePersonalEngageItem(num = 2999, title = "获赞")
-        HomePersonalEngageItem(num = 2848180, title = "收藏")
+        HomePersonalEngageItem(num = interactiveUiState.followerCount, title = "关注")
+        HomePersonalEngageItem(num = interactiveUiState.fansCount, title = "粉丝")
+        HomePersonalEngageItem(num = interactiveUiState.likeCount, title = "获赞")
+        HomePersonalEngageItem(num = interactiveUiState.collectionCount, title = "收藏")
     }
 }
 

+ 36 - 8
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalPage.kt

@@ -17,16 +17,24 @@ import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
 import androidx.hilt.navigation.compose.hiltViewModel
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.zaojiao.app.core.navx.NavXUtils
 
 @Composable
 internal fun HomePersonalRoute(
     viewModel: HomePersonalViewModel = hiltViewModel()
 ) {
     val userUiState by viewModel.userUiState.collectAsStateWithLifecycle()
+    val interactiveUiState by viewModel.interactiveUiState.collectAsStateWithLifecycle()
 
     HomePersonalPage(
         userUiState = userUiState,
-        navToSettings = viewModel::navToSettings,
+        interactiveUiState = interactiveUiState,
+        navToPageWithLogin = {
+            NavXUtils.navToRoute(it, needLogin = true)
+        },
+        navToPageWithoutLogin = {
+            NavXUtils.navToRoute(it, needLogin = false)
+        },
     )
 }
 
@@ -36,7 +44,9 @@ internal fun HomePersonalRoute(
 @Composable
 fun HomePersonalPage(
     userUiState: HomePersonalUserUiState,
-    navToSettings: () -> Unit,
+    interactiveUiState: HomePersonalInteractiveUiState,
+    navToPageWithLogin: (route: String) -> Unit,
+    navToPageWithoutLogin: (route: String) -> Unit,
 ) {
     Box(
         modifier = Modifier
@@ -53,28 +63,46 @@ fun HomePersonalPage(
             state = state
         ) {
             item {
-                HomePersonalTopBar()
+                HomePersonalTopBar(
+                    navToPageWithLogin = navToPageWithLogin,
+                    navToPageWithoutLogin = navToPageWithoutLogin,
+                )
             }
 
             item {
-                HomePersonalUserBar(userUiState)
+                HomePersonalUserBar(
+                    userUiState = userUiState,
+                    navToPageWithLogin = navToPageWithLogin,
+                    navToPageWithoutLogin = navToPageWithoutLogin,
+                )
             }
 
             item {
-                HomePersonalEngage()
+                HomePersonalEngage(
+                    interactiveUiState = interactiveUiState,
+                    navToPageWithLogin = navToPageWithLogin,
+                    navToPageWithoutLogin = navToPageWithoutLogin,
+                )
             }
 
             item {
-                HomePersonalContent()
+                HomePersonalContent(
+                    navToPageWithLogin = navToPageWithLogin,
+                    navToPageWithoutLogin = navToPageWithoutLogin,
+                )
             }
 
             item {
-                HomePersonalAccount()
+                HomePersonalAccount(
+                    navToPageWithLogin = navToPageWithLogin,
+                    navToPageWithoutLogin = navToPageWithoutLogin,
+                )
             }
 
             item {
                 HomePersonalService(
-                    navToSettings = navToSettings,
+                    navToPageWithLogin = navToPageWithLogin,
+                    navToPageWithoutLogin = navToPageWithoutLogin,
                 )
             }
 

+ 11 - 3
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalService.kt

@@ -27,12 +27,15 @@ import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
-import com.zaojiao.app.feat.home.R
+import com.zaojiao.app.feat.baby.babyList
 import com.zaojiao.app.feat.design.shadow
+import com.zaojiao.app.feat.home.R
+import com.zaojiao.app.feat.settings.settingsIndex
 
 @Composable
 fun HomePersonalService(
-    navToSettings: () -> Unit,
+    navToPageWithLogin: (route: String) -> Unit,
+    navToPageWithoutLogin: (route: String) -> Unit,
 ) {
     Box(
         modifier = Modifier
@@ -84,6 +87,9 @@ fun HomePersonalService(
                 HomePersonalServiceItem(
                     iconRes = R.mipmap.personal_baby,
                     description = "宝宝档案",
+                    onClick = {
+                        navToPageWithLogin.invoke(babyList)
+                    },
                 )
                 HomePersonalServiceItem(
                     iconRes = R.mipmap.personal_address,
@@ -116,7 +122,9 @@ fun HomePersonalService(
                 HomePersonalServiceItem(
                     iconRes = R.mipmap.personal_setting,
                     description = "设置",
-                    onClick = navToSettings,
+                    onClick = {
+                        navToPageWithoutLogin.invoke(settingsIndex)
+                    },
                 )
                 HomePersonalServiceBlank()
             }

+ 4 - 1
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalTopBar.kt

@@ -28,7 +28,10 @@ import com.zaojiao.app.feat.design.Images
 import com.zaojiao.app.feat.design.Spacer
 
 @Composable
-fun HomePersonalTopBar() {
+fun HomePersonalTopBar(
+    navToPageWithLogin: (route: String) -> Unit,
+    navToPageWithoutLogin: (route: String) -> Unit,
+) {
     Row(
         modifier = Modifier
             .statusBarsPadding()

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

@@ -19,4 +19,11 @@ data class HomePersonalUserUiState(
             avatar = "",
         )
     }
-}
+}
+
+data class HomePersonalInteractiveUiState(
+    val followerCount: Int = 0,
+    val fansCount: Int = 0,
+    val likeCount: Int = 0,
+    val collectionCount: Int = 0,
+)

+ 2 - 0
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalUserBar.kt

@@ -25,6 +25,8 @@ import com.zaojiao.app.feat.design.Spacer
 @Composable
 fun HomePersonalUserBar(
     userUiState: HomePersonalUserUiState,
+    navToPageWithLogin: (route: String) -> Unit,
+    navToPageWithoutLogin: (route: String) -> Unit,
 ) {
     Row(
         modifier = Modifier

+ 22 - 11
feat/home/src/main/kotlin/com/zaojiao/app/feat/home/personal/HomePersonalViewModel.kt

@@ -4,29 +4,24 @@ 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.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
+    private val userRepository: UserRepository,
 ) : ViewModel() {
 
     val userUiState: StateFlow<HomePersonalUserUiState> =
         combine(authRepository.state, userRepository.user) { auth, user ->
             when (auth) {
                 AuthState.IN -> {
-                    userRepository.getUser()
-
                     HomePersonalUserUiState(
                         name = user?.nickName ?: "逻辑狗",
                         avatar = user?.avatarUrl,
@@ -43,9 +38,25 @@ class HomePersonalViewModel @Inject constructor(
             initialValue = HomePersonalUserUiState.None,
         )
 
-    fun navToSettings() {
-        viewModelScope.launch {
-            LJGNavigator.toSettings()
-        }
-    }
+    val interactiveUiState: StateFlow<HomePersonalInteractiveUiState> =
+        combine(authRepository.state, userRepository.interactiveCount) { auth, interactive ->
+            when (auth) {
+                AuthState.IN -> {
+                    HomePersonalInteractiveUiState(
+                        followerCount = interactive.followerCount,
+                        fansCount = interactive.fansCount,
+                        likeCount = interactive.likeCount,
+                        collectionCount = interactive.collectionCount,
+                    )
+                }
+
+                AuthState.OUT -> {
+                    HomePersonalInteractiveUiState()
+                }
+            }
+        }.stateIn(
+            scope = viewModelScope,
+            started = SharingStarted.WhileSubscribed(5_000),
+            initialValue = HomePersonalInteractiveUiState(),
+        )
 }

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

@@ -26,6 +26,6 @@ class HomePlanViewModel(
     }.stateIn(
         scope = viewModelScope,
         started = SharingStarted.WhileSubscribed(5_000),
-        initialValue = UiState.Loading(),
+        initialValue = UiState.Loading,
     )
 }

+ 107 - 0
feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsCommon.kt

@@ -0,0 +1,107 @@
+package com.zaojiao.app.feat.settings
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.zaojiao.app.feat.design.Colors
+
+@Composable
+fun SettingsCommon() {
+    Column(
+        modifier = Modifier
+            .padding(horizontal = 12.dp)
+            .clip(shape = RoundedCornerShape(10.dp))
+            .background(color = Color.White)
+            .wrapContentHeight()
+            .fillMaxWidth(),
+    ) {
+        SettingsIndexInfoItem(
+            title = "微信绑定",
+            onClick = {},
+            isFirst = true,
+        ) {
+            Text(
+                text = "已绑定",
+                style = TextStyle(
+                    color = Colors.FF666666,
+                    fontSize = 12.sp,
+                    lineHeight = 17.sp,
+                    fontWeight = FontWeight.Normal,
+                )
+            )
+        }
+
+        SettingsIndexInfoItem(
+            title = "手机号码",
+            onClick = {},
+        ) {
+            Text(
+                text = "1999999999",
+                style = TextStyle(
+                    color = Colors.FF666666,
+                    fontSize = 12.sp,
+                    lineHeight = 17.sp,
+                    fontWeight = FontWeight.Normal,
+                )
+            )
+        }
+
+        SettingsIndexInfoItem(
+            title = "清除缓存",
+            onClick = {},
+        ) {
+            Text(
+                text = "200.4M",
+                style = TextStyle(
+                    color = Colors.FF666666,
+                    fontSize = 12.sp,
+                    lineHeight = 17.sp,
+                    fontWeight = FontWeight.Normal,
+                )
+            )
+        }
+
+        SettingsIndexForwardItem(
+            title = "用户协议",
+            onClick = {},
+        )
+
+        SettingsIndexForwardItem(
+            title = "隐私协议",
+            onClick = {},
+        )
+
+        SettingsIndexForwardItem(
+            title = "意见反馈",
+            onClick = {},
+        )
+
+        SettingsIndexForwardItem(
+            title = "去评价",
+            onClick = {},
+        )
+
+        SettingsIndexForwardItem(
+            title = "关于我们",
+            onClick = {},
+        )
+
+        SettingsIndexForwardItem(
+            title = "注销账号",
+            onClick = {},
+            isLast = true,
+        )
+    }
+}

+ 132 - 0
feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsIndexItem.kt

@@ -0,0 +1,132 @@
+package com.zaojiao.app.feat.settings
+
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.zaojiao.app.feat.design.Colors
+import com.zaojiao.app.feat.design.Expanded
+import com.zaojiao.app.feat.design.Icons
+import com.zaojiao.app.feat.design.Spacer
+
+@Composable
+fun SettingsIndexSelectorItem(
+    title: String,
+    value: String,
+    onClick: () -> Unit,
+    isFirst: Boolean = false,
+    isLast: Boolean = false,
+) {
+    val top = if (isFirst) 16.dp else 7.dp
+    val bottom = if (isLast) 7.dp else 16.dp
+
+    Row(
+        modifier = Modifier
+            .padding(start = 12.dp, end = 12.dp, top = top, bottom = bottom)
+            .wrapContentHeight()
+            .fillMaxWidth(),
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        Text(
+            text = title,
+            style = TextStyle(
+                color = Colors.FF333333,
+                fontSize = 15.sp,
+                lineHeight = 21.sp,
+                fontWeight = FontWeight.Medium,
+            )
+        )
+        Expanded()
+        Text(
+            text = value,
+            style = TextStyle(
+                color = Colors.FF666666,
+                fontSize = 12.sp,
+                lineHeight = 17.sp,
+                fontWeight = FontWeight.Normal,
+            )
+        )
+        Spacer(width = 6.dp)
+        Icons.Forward(
+            size = Size(width = 6f, height = 12f),
+            color = Colors.from("#CBCBCB"),
+            width = 1.5.dp,
+        )
+    }
+}
+
+@Composable
+fun SettingsIndexForwardItem(
+    title: String,
+    onClick: () -> Unit = TODO(),
+    isFirst: Boolean = false,
+    isLast: Boolean = false,
+) {
+    val top = if (isFirst) 16.dp else 14.dp
+    val bottom = if (isLast) 16.dp else 14.dp
+
+    Row(
+        modifier = Modifier
+            .padding(start = 12.dp, end = 12.dp, top = top, bottom = bottom)
+            .wrapContentHeight()
+            .fillMaxWidth(),
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        Text(
+            text = title,
+            style = TextStyle(
+                color = Colors.FF333333,
+                fontSize = 15.sp,
+                lineHeight = 21.sp,
+                fontWeight = FontWeight.Medium,
+            )
+        )
+        Expanded()
+        Icons.Forward(
+            size = Size(width = 6f, height = 12f),
+            color = Colors.from("#CBCBCB"),
+            width = 1.5.dp,
+        )
+    }
+}
+
+@Composable
+fun SettingsIndexInfoItem(
+    title: String,
+    onClick: () -> Unit = TODO(),
+    isFirst: Boolean = false,
+    isLast: Boolean = false,
+    value: @Composable () -> Unit,
+) {
+    val top = if (isFirst) 16.dp else 14.dp
+    val bottom = if (isLast) 16.dp else 14.dp
+
+    Row(
+        modifier = Modifier
+            .padding(start = 12.dp, end = 12.dp, top = top, bottom = bottom)
+            .wrapContentHeight()
+            .fillMaxWidth(),
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        Text(
+            text = title,
+            style = TextStyle(
+                color = Colors.FF333333,
+                fontSize = 15.sp,
+                lineHeight = 21.sp,
+                fontWeight = FontWeight.Medium,
+            )
+        )
+        Expanded()
+        value.invoke()
+    }
+}

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

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

+ 61 - 11
feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsPage.kt

@@ -1,16 +1,30 @@
 package com.zaojiao.app.feat.settings
 
+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.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.shape.RoundedCornerShape
 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.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
 import androidx.hilt.navigation.compose.hiltViewModel
+import com.zaojiao.app.feat.design.AppBar
+import com.zaojiao.app.feat.design.Colors
 
 @Composable
 internal fun SettingsRoute(
@@ -25,20 +39,56 @@ internal fun SettingsRoute(
 fun SettingsPage(
     logout: () -> Unit,
 ) {
-    Column(
+    LazyColumn(
         modifier = Modifier
-            .statusBarsPadding()
+            .background(color = Color(0xFFF9F7F8))
+            .fillMaxHeight()
             .fillMaxWidth()
     ) {
-        Button(
-            onClick = logout,
-            modifier = Modifier
-                .fillMaxWidth()
-                .height(60.dp)
-        ) {
+        item {
+            AppBar(title = "设置")
+        }
+
+        item {
+            Box(modifier = Modifier.height(11.dp))
+        }
+
+        item {
+            SettingsVideo()
+        }
+
+        item {
+            Box(modifier = Modifier.height(14.dp))
+        }
+
+        item {
+            SettingsCommon()
+        }
+
+        item {
+            Box(modifier = Modifier.height(14.dp))
+        }
+
+        item {
             Text(
-                text = "退出",
-                modifier = Modifier.align(Alignment.CenterVertically)
+                text = "退出登录",
+                modifier = Modifier
+                    .navigationBarsPadding()
+                    .padding(horizontal = 16.dp)
+                    .clickable { logout() }
+                    .background(
+                        color = Color.White,
+                        shape = RoundedCornerShape(10.dp),
+                    )
+                    .padding(vertical = 12.dp)
+                    .fillMaxWidth(),
+                style = TextStyle(
+                    color = Colors.FF333333,
+                    fontSize = 16.sp,
+                    lineHeight = 22.sp,
+                    fontWeight = FontWeight.Medium,
+                ),
+                textAlign = TextAlign.Center,
             )
         }
     }

+ 49 - 0
feat/settings/src/main/java/com/zaojiao/app/feat/settings/SettingsVideo.kt

@@ -0,0 +1,49 @@
+package com.zaojiao.app.feat.settings
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.zaojiao.app.feat.design.Colors
+
+@Composable
+fun SettingsVideo() {
+    Column(
+        modifier = Modifier
+            .padding(horizontal = 12.dp)
+            .clip(shape = RoundedCornerShape(10.dp))
+            .background(color = Color.White)
+            .wrapContentHeight()
+            .fillMaxWidth(),
+    ) {
+        Text(
+            text = "视频护眼设置",
+            modifier = Modifier
+                .padding(top = 16.dp, bottom = 7.dp, start = 12.dp, end = 12.dp)
+                .fillMaxWidth(),
+            style = TextStyle(
+                color = Colors.FF999999,
+                fontSize = 13.sp,
+                lineHeight = 18.sp,
+                fontWeight = FontWeight.Normal,
+            )
+        )
+
+        SettingsIndexSelectorItem(
+            title = "每次观看时长",
+            value = "不限制",
+            onClick = {},
+        )
+    }
+}

+ 1 - 0
settings.gradle.kts

@@ -24,6 +24,7 @@ include(":core:common")
 include(":core:http")
 include(":core:auth")
 include(":core:nav")
+include(":core:navx")
 
 include(":data:model")
 include(":data:local")