diff --git a/app-android/app/src/main/AndroidManifest.xml b/app-android/app/src/main/AndroidManifest.xml index f63d579..3e44214 100644 --- a/app-android/app/src/main/AndroidManifest.xml +++ b/app-android/app/src/main/AndroidManifest.xml @@ -18,7 +18,8 @@ android:name=".MainActivity" android:exported="true" android:label="@string/app_name" - android:theme="@style/Theme.Lifeecho" > + android:theme="@style/Theme.Lifeecho" + android:windowSoftInputMode="adjustResize" > 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 7c79b9a..a9ea4fc 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 @@ -5,7 +5,11 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -21,6 +25,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBars import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.draw.scale import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -29,6 +34,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -60,9 +66,9 @@ class MainActivity : ComponentActivity() { // 设置系统栏透明 WindowCompat.setDecorFitsSystemWindows(window, false) - // 设置状态栏和导航栏完全透明 - window.statusBarColor = android.graphics.Color.TRANSPARENT - window.navigationBarColor = android.graphics.Color.TRANSPARENT + // 设置状态栏和导航栏完全透明(使用 WindowInsetsController 在 LaunchedEffect 中处理) + // 注意:在 API 30+ 中,直接设置 statusBarColor 和 navigationBarColor 已弃用 + // 我们通过 WindowInsetsController 来管理系统栏外观 setContent { val darkMode = com.huaga.life_echo.ui.settings.AppSettings.rememberDarkMode() @@ -184,9 +190,30 @@ fun BottomNavItem( val iconColor = if (isSelected) LightPurple else MaterialTheme.colorScheme.onSurfaceVariant val textColor = if (isSelected) LightPurple else MaterialTheme.colorScheme.onSurfaceVariant + // 选中状态的缩放动画 + val scale by animateFloatAsState( + targetValue = if (isSelected) 1.1f else 1f, + animationSpec = tween(durationMillis = 200, easing = androidx.compose.animation.core.FastOutSlowInEasing), + label = "nav_item_scale" + ) + + // 点击时的缩放动画 + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + val pressScale by animateFloatAsState( + targetValue = if (isPressed) 0.9f else 1f, + animationSpec = tween(durationMillis = 100), + label = "nav_item_press_scale" + ) + Column( modifier = Modifier - .clickable { onClick() } + .clickable( + interactionSource = interactionSource, + indication = null, // 移除默认的点击波纹效果,使用自定义动画 + onClick = onClick + ) + .scale(scale * pressScale) // 应用缩放动画 .padding(horizontal = 16.dp, vertical = 4.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center 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 c68e696..b493e7e 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 @@ -1,9 +1,11 @@ package com.huaga.life_echo.config object AppConfig { - // API 基础 URL(可以从 BuildConfig 或环境变量读取) - const val BASE_URL = "https://lifecho.worldsplats.com" // Android 模拟器使用 10.0.2.2 访问 localhost - const val WS_BASE_URL = "https://lifecho.worldsplats.com" + // API 基础 URL - 内网地址,用于同一局域网下的物理机测试 + // 当前主机IP: 192.168.10.9,端口: 8000 + // 如果IP地址变化,请修改此处 + const val BASE_URL = "http://192.168.10.9:8000" + const val WS_BASE_URL = "ws://192.168.10.9:8000" // 生产环境应该从配置文件或环境变量读取 // const val BASE_URL = BuildConfig.API_BASE_URL diff --git a/app-android/app/src/main/java/com/huaga/life_echo/data/database/Chapter.kt b/app-android/app/src/main/java/com/huaga/life_echo/data/database/Chapter.kt index f29ef6c..a6f0963 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/data/database/Chapter.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/data/database/Chapter.kt @@ -12,6 +12,7 @@ data class Chapter( val status: String, // draft, partial, completed val updatedAt: Long, val category: String, - val pageCount: Int? = null + val pageCount: Int? = null, + val isNew: Boolean = false ) diff --git a/app-android/app/src/main/java/com/huaga/life_echo/data/mock/MockDataProvider.kt b/app-android/app/src/main/java/com/huaga/life_echo/data/mock/MockDataProvider.kt index aacf5b5..b239702 100644 --- a/app-android/app/src/main/java/com/huaga/life_echo/data/mock/MockDataProvider.kt +++ b/app-android/app/src/main/java/com/huaga/life_echo/data/mock/MockDataProvider.kt @@ -1,7 +1,19 @@ package com.huaga.life_echo.data.mock -import com.huaga.life_echo.network.models.* -import kotlinx.serialization.Serializable +import android.os.Build +import androidx.annotation.RequiresApi +import com.huaga.life_echo.network.models.BookDto +import com.huaga.life_echo.network.models.ChapterContentDto +import com.huaga.life_echo.network.models.ChapterDto +import com.huaga.life_echo.network.models.ConversationDetailDto +import com.huaga.life_echo.network.models.ConversationListItemDto +import com.huaga.life_echo.network.models.FAQDto +import com.huaga.life_echo.network.models.MessageDto +import com.huaga.life_echo.network.models.OrderDto +import com.huaga.life_echo.network.models.PlanBenefitDto +import com.huaga.life_echo.network.models.PlanDto +import com.huaga.life_echo.network.models.QuotaCheckDto +import com.huaga.life_echo.network.models.UserProfileDto /** * Mock数据提供者 @@ -89,47 +101,57 @@ object MockDataProvider { } // 获取Mock章节列表 + @RequiresApi(Build.VERSION_CODES.O) fun getMockChapters(): List { + val now = java.time.Instant.now().toString() return listOf( ChapterDto( id = "chapter_001", title = "童年与家庭", content = "我出生在一个普通的农村家庭,父母都是勤劳朴实的农民。\n\n父亲是村里的木匠,手艺精湛,邻里乡亲都愿意找他帮忙。", - orderIndex = 1, + order_index = 1, status = "completed", category = "childhood", - pageCount = 3, - updatedAt = System.currentTimeMillis() - 86400000 + images = emptyList(), + updated_at = now, + is_new = false, + source_segments = emptyList() ), ChapterDto( id = "chapter_002", title = "上学的日子", content = "上学的日子总是充满欢声笑语,虽然条件艰苦,但学习的快乐让我忘记了生活的艰辛。", - orderIndex = 2, + order_index = 2, status = "partial", category = "education", - pageCount = 2, - updatedAt = System.currentTimeMillis() - 43200000 + images = emptyList(), + updated_at = now, + is_new = false, + source_segments = emptyList() ), ChapterDto( id = "chapter_003", title = "工作与事业", content = "工作是我人生中重要的转折点,让我学会了承担责任,也让我明白了生活的意义。", - orderIndex = 3, + order_index = 3, status = "pending", category = "career", - pageCount = null, - updatedAt = System.currentTimeMillis() + images = emptyList(), + updated_at = now, + is_new = true, + source_segments = emptyList() ), ChapterDto( id = "chapter_004", title = "爱情与婚姻", content = "爱情是人生中最美好的经历之一,它让我懂得了什么是真正的幸福。", - orderIndex = 4, + order_index = 4, status = "pending", category = "family", - pageCount = null, - updatedAt = System.currentTimeMillis() + images = emptyList(), + updated_at = now, + is_new = true, + source_segments = emptyList() ) ) } @@ -138,17 +160,17 @@ object MockDataProvider { fun getMockBookInfo(): BookDto { return BookDto( id = "book_001", - userId = "user_001", title = "这一生", - subtitle = "我的回忆录", - totalPages = 5, - totalWords = 5000, - updatedAt = System.currentTimeMillis(), - lastUpdatedAt = System.currentTimeMillis() - 120000 // 2分钟前 + total_pages = 5, + total_words = 5000, + cover_image_url = null, + has_update = true, + last_update_chapter_id = "chapter_003" ) } // 获取Mock章节内容 + @RequiresApi(Build.VERSION_CODES.O) fun getMockChapterContent(id: String): ChapterContentDto { val chapters = getMockChapters() val chapter = chapters.find { it.id == id } ?: chapters[0] @@ -157,11 +179,17 @@ object MockDataProvider { id = chapter.id, title = chapter.title, content = chapter.content, - orderIndex = chapter.orderIndex, + orderIndex = chapter.order_index, status = chapter.status, category = chapter.category, - pageCount = chapter.pageCount, - updatedAt = chapter.updatedAt ?: System.currentTimeMillis(), + pageCount = null, + updatedAt = chapter.updated_at?.let { + try { + java.time.Instant.parse(it).toEpochMilli() + } catch (e: Exception) { + System.currentTimeMillis() + } + } ?: System.currentTimeMillis(), quotes = if (chapter.category == "childhood") { listOf("\"日子虽然清苦, 但那时候的快乐是最纯粹的。\"") } else {