diff --git a/app-android/app/src/main/java/com/huaga/life_echo/MainActivity.kt b/app-android/app/src/main/java/com/huaga/life_echo/MainActivity.kt index 9a9dd2f..290e68d 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/MainActivity.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/MainActivity.kt @@ -6,9 +6,25 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.* +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -16,7 +32,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview @@ -24,10 +39,9 @@ import androidx.compose.ui.tooling.preview.PreviewScreenSizes import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.view.WindowCompat -import androidx.core.view.WindowInsetsControllerCompat import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController -import android.view.WindowManager +import com.huaga.life_echo.data.auth.TokenManager import com.huaga.life_echo.navigation.AppNavigation import com.huaga.life_echo.navigation.Screen import com.huaga.life_echo.ui.icons.AppIcons @@ -37,6 +51,9 @@ import com.huaga.life_echo.ui.theme.LightPurple class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + // 初始化TokenManager + TokenManager.initialize(this) // 启用边缘到边缘显示 enableEdgeToEdge() // 设置系统栏透明 @@ -167,20 +184,4 @@ enum class AppDestinations( CHAT("聊天", AppIcons.Chat), MEMOIR("回忆录", AppIcons.Memoir), PROFILE("我的", AppIcons.Profile), -} - -@Composable -fun Greeting(name: String, modifier: Modifier = Modifier) { - Text( - text = "Hello $name!", - modifier = modifier - ) -} - -@Preview(showBackground = true) -@Composable -fun GreetingPreview() { - LifeechoTheme { - Greeting("Android") - } } \ No newline at end of file diff --git a/app-android/app/src/main/java/com/huaga/life_echo/config/AppConfig.kt b/app-android/app/src/main/java/com/huaga/life_echo/config/AppConfig.kt index 3873b31..09bfff9 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/config/AppConfig.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/config/AppConfig.kt @@ -7,5 +7,4 @@ object AppConfig { // 生产环境应该从配置文件或环境变量读取 // const val BASE_URL = BuildConfig.API_BASE_URL -} - +} \ No newline at end of file diff --git a/app-android/app/src/main/java/com/huaga/life_echo/feature/voice/VoiceRecorder.kt b/app-android/app/src/main/java/com/huaga/life_echo/feature/voice/VoiceRecorder.kt index cd65534..9b91211 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/feature/voice/VoiceRecorder.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/feature/voice/VoiceRecorder.kt @@ -1,6 +1,5 @@ package com.huaga.life_echo.feature.voice -import android.Manifest import android.content.Context import android.media.MediaRecorder import android.os.Build @@ -8,8 +7,7 @@ import androidx.annotation.RequiresApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import java.io.File -import java.io.FileOutputStream -import java.util.* +import java.util.UUID class VoiceRecorder(private val context: Context) { private var mediaRecorder: MediaRecorder? = null diff --git a/app-android/app/src/main/java/com/huaga/life_echo/navigation/AppNavigation.kt b/app-android/app/src/main/java/com/huaga/life_echo/navigation/AppNavigation.kt index 4c2faa1..65cbf82 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/navigation/AppNavigation.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/navigation/AppNavigation.kt @@ -9,9 +9,11 @@ import androidx.navigation.navArgument import com.huaga.life_echo.ui.screens.ConversationListScreen import com.huaga.life_echo.ui.screens.CreateMemoryScreen import com.huaga.life_echo.ui.screens.ExportDataScreen +import com.huaga.life_echo.ui.screens.LoginScreen import com.huaga.life_echo.ui.screens.MyMemoirScreen import com.huaga.life_echo.ui.screens.MyOrdersScreen import com.huaga.life_echo.ui.screens.ProfileScreen +import com.huaga.life_echo.ui.screens.RegisterScreen import com.huaga.life_echo.ui.screens.UpgradePlanScreen sealed class Screen(val route: String) { @@ -21,6 +23,8 @@ sealed class Screen(val route: String) { } object MyMemoir : Screen("my_memoir") object Profile : Screen("profile") + object Login : Screen("login") + object Register : Screen("register") object UpgradePlan : Screen("upgrade_plan") object MyOrders : Screen("my_orders") object ExportData : Screen("export_data") @@ -64,6 +68,26 @@ fun AppNavigation(navController: NavHostController) { composable(Screen.ExportData.route) { ExportDataScreen(navController = navController) } + composable(Screen.Login.route) { + LoginScreen( + onLoginSuccess = { + navController.popBackStack() + }, + onNavigateToRegister = { + navController.navigate(Screen.Register.route) + } + ) + } + composable(Screen.Register.route) { + RegisterScreen( + onRegisterSuccess = { + navController.popBackStack() + }, + onNavigateBack = { + navController.popBackStack() + } + ) + } } } diff --git a/app-android/app/src/main/java/com/huaga/life_echo/network/ApiService.kt b/app-android/app/src/main/java/com/huaga/life_echo/network/ApiService.kt index e4bb58d..b224a88 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/network/ApiService.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/network/ApiService.kt @@ -1,5 +1,7 @@ package com.huaga.life_echo.network +import com.huaga.life_echo.data.auth.TokenManager +import com.huaga.life_echo.network.interceptors.AuthInterceptorPlugin import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.engine.android.* @@ -30,7 +32,10 @@ data class BookDto( val totalWords: Int ) -class ApiService { +class ApiService( + tokenManager: TokenManager? = null, + authService: AuthService? = null +) { private val client = HttpClient(Android) { install(ContentNegotiation) { json(Json { @@ -40,15 +45,24 @@ class ApiService { install(Logging) { level = LogLevel.INFO } + + // 如果提供了tokenManager和authService,安装认证拦截器 + if (tokenManager != null && authService != null) { + install(AuthInterceptorPlugin) { + this.tokenManager = tokenManager + this.authService = authService + } + } } companion object { private const val BASE_URL = com.huaga.life_echo.config.AppConfig.BASE_URL } - suspend fun getChapters(): List { + suspend fun getChapters(userId: String = "default_user"): List { return client.get("$BASE_URL/api/chapters") { contentType(ContentType.Application.Json) + parameter("user_id", userId) }.body() } @@ -58,10 +72,10 @@ class ApiService { }.body() } - suspend fun exportPdf(bookId: String): ByteArray { + suspend fun exportPdf(bookId: String, userId: String = "default_user"): ByteArray { return client.post("$BASE_URL/api/books/export-pdf") { contentType(ContentType.Application.Json) - setBody(mapOf("book_id" to bookId)) + setBody(mapOf("book_id" to bookId, "user_id" to userId)) }.body() } } diff --git a/app-android/app/src/main/java/com/huaga/life_echo/network/WebSocketClient.kt b/app-android/app/src/main/java/com/huaga/life_echo/network/WebSocketClient.kt index 54a294d..bb54453 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/network/WebSocketClient.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/network/WebSocketClient.kt @@ -32,6 +32,7 @@ class WebSocketClient { private var reconnectJob: Job? = null private var isConnected = false private var currentConversationId: String? = null + private var currentToken: String? = null companion object { private const val BASE_URL = com.huaga.life_echo.config.AppConfig.WS_BASE_URL @@ -41,9 +42,15 @@ class WebSocketClient { suspend fun connect( conversationId: String, + token: String? = null, onMessage: (WebSocketMessage) -> Unit ) { - val url = "$BASE_URL/ws/conversation/$conversationId" + val baseUrl = "$BASE_URL/ws/conversation/$conversationId" + val url = if (token != null) { + "$baseUrl?token=$token" + } else { + baseUrl + } try { session = client.webSocketSession { @@ -53,6 +60,7 @@ class WebSocketClient { } currentConversationId = conversationId + currentToken = token isConnected = true // 启动消息接收协程 @@ -112,6 +120,14 @@ class WebSocketClient { )) } + suspend fun sendTextMessage(text: String, conversationId: String) { + sendMessage(WebSocketMessage( + type = MessageType.text, + conversation_id = conversationId, + data = mapOf("text" to text) + )) + } + suspend fun sendEndConversation(conversationId: String) { sendMessage(WebSocketMessage( type = MessageType.end_conversation, @@ -139,8 +155,9 @@ class WebSocketClient { try { val conversationId = currentConversationId + val token = currentToken if (conversationId != null) { - connect(conversationId, onMessage) + connect(conversationId, token, onMessage) } } catch (_: Exception) { reconnectWithBackoff(onMessage, attempt + 1) diff --git a/app-android/app/src/main/java/com/huaga/life_echo/network/WebSocketMessage.kt b/app-android/app/src/main/java/com/huaga/life_echo/network/WebSocketMessage.kt index 760eae5..cc08c18 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/network/WebSocketMessage.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/network/WebSocketMessage.kt @@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable enum class MessageType { connect, audio_chunk, + text, // 文本消息 transcript, agent_response, tts_audio,