refactor: 更新Android网络和配置

- 更新ApiService以支持认证
- 更新WebSocketClient以支持认证连接
- 更新WebSocketMessage模型
- 更新AppConfig配置
- 更新MainActivity以支持认证导航
- 更新AppNavigation添加认证相关路由
- 更新VoiceRecorder功能
This commit is contained in:
徐在坤
2026-01-18 15:58:03 +08:00
parent b75205e53d
commit 8582e88ec2
7 changed files with 86 additions and 32 deletions

View File

@@ -6,9 +6,25 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.clickable 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.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.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -16,7 +32,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController 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.AppNavigation
import com.huaga.life_echo.navigation.Screen import com.huaga.life_echo.navigation.Screen
import com.huaga.life_echo.ui.icons.AppIcons import com.huaga.life_echo.ui.icons.AppIcons
@@ -37,6 +51,9 @@ import com.huaga.life_echo.ui.theme.LightPurple
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// 初始化TokenManager
TokenManager.initialize(this)
// 启用边缘到边缘显示 // 启用边缘到边缘显示
enableEdgeToEdge() enableEdgeToEdge()
// 设置系统栏透明 // 设置系统栏透明
@@ -168,19 +185,3 @@ enum class AppDestinations(
MEMOIR("回忆录", AppIcons.Memoir), MEMOIR("回忆录", AppIcons.Memoir),
PROFILE("我的", AppIcons.Profile), 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")
}
}

View File

@@ -8,4 +8,3 @@ object AppConfig {
// 生产环境应该从配置文件或环境变量读取 // 生产环境应该从配置文件或环境变量读取
// const val BASE_URL = BuildConfig.API_BASE_URL // const val BASE_URL = BuildConfig.API_BASE_URL
} }

View File

@@ -1,6 +1,5 @@
package com.huaga.life_echo.feature.voice package com.huaga.life_echo.feature.voice
import android.Manifest
import android.content.Context import android.content.Context
import android.media.MediaRecorder import android.media.MediaRecorder
import android.os.Build import android.os.Build
@@ -8,8 +7,7 @@ import androidx.annotation.RequiresApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import java.io.File import java.io.File
import java.io.FileOutputStream import java.util.UUID
import java.util.*
class VoiceRecorder(private val context: Context) { class VoiceRecorder(private val context: Context) {
private var mediaRecorder: MediaRecorder? = null private var mediaRecorder: MediaRecorder? = null

View File

@@ -9,9 +9,11 @@ import androidx.navigation.navArgument
import com.huaga.life_echo.ui.screens.ConversationListScreen import com.huaga.life_echo.ui.screens.ConversationListScreen
import com.huaga.life_echo.ui.screens.CreateMemoryScreen import com.huaga.life_echo.ui.screens.CreateMemoryScreen
import com.huaga.life_echo.ui.screens.ExportDataScreen 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.MyMemoirScreen
import com.huaga.life_echo.ui.screens.MyOrdersScreen import com.huaga.life_echo.ui.screens.MyOrdersScreen
import com.huaga.life_echo.ui.screens.ProfileScreen import com.huaga.life_echo.ui.screens.ProfileScreen
import com.huaga.life_echo.ui.screens.RegisterScreen
import com.huaga.life_echo.ui.screens.UpgradePlanScreen import com.huaga.life_echo.ui.screens.UpgradePlanScreen
sealed class Screen(val route: String) { sealed class Screen(val route: String) {
@@ -21,6 +23,8 @@ sealed class Screen(val route: String) {
} }
object MyMemoir : Screen("my_memoir") object MyMemoir : Screen("my_memoir")
object Profile : Screen("profile") object Profile : Screen("profile")
object Login : Screen("login")
object Register : Screen("register")
object UpgradePlan : Screen("upgrade_plan") object UpgradePlan : Screen("upgrade_plan")
object MyOrders : Screen("my_orders") object MyOrders : Screen("my_orders")
object ExportData : Screen("export_data") object ExportData : Screen("export_data")
@@ -64,6 +68,26 @@ fun AppNavigation(navController: NavHostController) {
composable(Screen.ExportData.route) { composable(Screen.ExportData.route) {
ExportDataScreen(navController = navController) 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()
}
)
}
} }
} }

View File

@@ -1,5 +1,7 @@
package com.huaga.life_echo.network 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.*
import io.ktor.client.call.* import io.ktor.client.call.*
import io.ktor.client.engine.android.* import io.ktor.client.engine.android.*
@@ -30,7 +32,10 @@ data class BookDto(
val totalWords: Int val totalWords: Int
) )
class ApiService { class ApiService(
tokenManager: TokenManager? = null,
authService: AuthService? = null
) {
private val client = HttpClient(Android) { private val client = HttpClient(Android) {
install(ContentNegotiation) { install(ContentNegotiation) {
json(Json { json(Json {
@@ -40,15 +45,24 @@ class ApiService {
install(Logging) { install(Logging) {
level = LogLevel.INFO level = LogLevel.INFO
} }
// 如果提供了tokenManager和authService安装认证拦截器
if (tokenManager != null && authService != null) {
install(AuthInterceptorPlugin) {
this.tokenManager = tokenManager
this.authService = authService
}
}
} }
companion object { companion object {
private const val BASE_URL = com.huaga.life_echo.config.AppConfig.BASE_URL private const val BASE_URL = com.huaga.life_echo.config.AppConfig.BASE_URL
} }
suspend fun getChapters(): List<ChapterDto> { suspend fun getChapters(userId: String = "default_user"): List<ChapterDto> {
return client.get("$BASE_URL/api/chapters") { return client.get("$BASE_URL/api/chapters") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
parameter("user_id", userId)
}.body() }.body()
} }
@@ -58,10 +72,10 @@ class ApiService {
}.body() }.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") { return client.post("$BASE_URL/api/books/export-pdf") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(mapOf("book_id" to bookId)) setBody(mapOf("book_id" to bookId, "user_id" to userId))
}.body() }.body()
} }
} }

View File

@@ -32,6 +32,7 @@ class WebSocketClient {
private var reconnectJob: Job? = null private var reconnectJob: Job? = null
private var isConnected = false private var isConnected = false
private var currentConversationId: String? = null private var currentConversationId: String? = null
private var currentToken: String? = null
companion object { companion object {
private const val BASE_URL = com.huaga.life_echo.config.AppConfig.WS_BASE_URL private const val BASE_URL = com.huaga.life_echo.config.AppConfig.WS_BASE_URL
@@ -41,9 +42,15 @@ class WebSocketClient {
suspend fun connect( suspend fun connect(
conversationId: String, conversationId: String,
token: String? = null,
onMessage: (WebSocketMessage) -> Unit 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 { try {
session = client.webSocketSession { session = client.webSocketSession {
@@ -53,6 +60,7 @@ class WebSocketClient {
} }
currentConversationId = conversationId currentConversationId = conversationId
currentToken = token
isConnected = true 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) { suspend fun sendEndConversation(conversationId: String) {
sendMessage(WebSocketMessage( sendMessage(WebSocketMessage(
type = MessageType.end_conversation, type = MessageType.end_conversation,
@@ -139,8 +155,9 @@ class WebSocketClient {
try { try {
val conversationId = currentConversationId val conversationId = currentConversationId
val token = currentToken
if (conversationId != null) { if (conversationId != null) {
connect(conversationId, onMessage) connect(conversationId, token, onMessage)
} }
} catch (_: Exception) { } catch (_: Exception) {
reconnectWithBackoff(onMessage, attempt + 1) reconnectWithBackoff(onMessage, attempt + 1)

View File

@@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable
enum class MessageType { enum class MessageType {
connect, connect,
audio_chunk, audio_chunk,
text, // 文本消息
transcript, transcript,
agent_response, agent_response,
tts_audio, tts_audio,