feat: 新增关于、常见问题、反馈和套餐详情屏幕
- 新增AboutScreen关于页面 - 新增FAQScreen常见问题页面 - 新增FeedbackScreen反馈页面 - 新增PlanDetailsScreen套餐详情页面 - 新增工具类(DateUtils、TextUtils、ValidationUtils)
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
package com.huaga.life_echo.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
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.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.huaga.life_echo.ui.icons.AppIcons
|
||||
import com.huaga.life_echo.ui.theme.LightPurple
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AboutScreen(
|
||||
navController: androidx.navigation.NavHostController? = null
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("关于我们") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { navController?.popBackStack() }) {
|
||||
Icon(
|
||||
imageVector = AppIcons.ArrowBack,
|
||||
contentDescription = "返回",
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = LightPurple,
|
||||
titleContentColor = Color.White,
|
||||
navigationIconContentColor = Color.White
|
||||
)
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(24.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Logo 或图标区域
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(120.dp)
|
||||
.padding(bottom = 24.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = AppIcons.Info,
|
||||
contentDescription = "应用图标",
|
||||
tint = LightPurple,
|
||||
modifier = Modifier.size(80.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// 应用名称
|
||||
Text(
|
||||
text = "Life Echo",
|
||||
fontSize = 28.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
|
||||
// 版本信息
|
||||
Text(
|
||||
text = "版本 1.0.0",
|
||||
fontSize = 16.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(bottom = 32.dp)
|
||||
)
|
||||
|
||||
Divider(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp),
|
||||
color = MaterialTheme.colorScheme.outlineVariant
|
||||
)
|
||||
|
||||
// 应用介绍
|
||||
Text(
|
||||
text = "应用介绍",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "Life Echo 是一款智能回忆录助手应用,帮助您记录和整理生活中的美好回忆。通过AI对话的方式,轻松创建属于您的个人回忆录。",
|
||||
fontSize = 16.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 24.dp),
|
||||
lineHeight = 24.sp
|
||||
)
|
||||
|
||||
Divider(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp),
|
||||
color = MaterialTheme.colorScheme.outlineVariant
|
||||
)
|
||||
|
||||
// 联系方式
|
||||
Text(
|
||||
text = "联系我们",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "如有任何问题或建议,欢迎通过应用内的"反馈与客服"功能联系我们。",
|
||||
fontSize = 16.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 24.dp),
|
||||
lineHeight = 24.sp
|
||||
)
|
||||
|
||||
Divider(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp),
|
||||
color = MaterialTheme.colorScheme.outlineVariant
|
||||
)
|
||||
|
||||
// 版权信息
|
||||
Text(
|
||||
text = "© 2024 Life Echo. All rights reserved.",
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 16.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.huaga.life_echo.ui.screens
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.huaga.life_echo.ui.components.common.EmptyStateView
|
||||
import com.huaga.life_echo.ui.components.common.LoadingIndicator
|
||||
import com.huaga.life_echo.ui.components.profile.FAQItem
|
||||
import com.huaga.life_echo.ui.icons.AppIcons
|
||||
import com.huaga.life_echo.ui.theme.LightPurple
|
||||
import com.huaga.life_echo.ui.viewmodel.ProfileViewModel
|
||||
import com.huaga.life_echo.ui.viewmodel.ViewModelFactory
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun FAQScreen(
|
||||
navController: androidx.navigation.NavHostController? = null,
|
||||
viewModel: ProfileViewModel = viewModel(
|
||||
factory = ViewModelFactory(LocalContext.current)
|
||||
)
|
||||
) {
|
||||
val faqs by viewModel.faqs.collectAsState()
|
||||
val isLoading by viewModel.isLoading.collectAsState()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("常见问题") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { navController?.popBackStack() }) {
|
||||
Icon(
|
||||
imageVector = AppIcons.ArrowBack,
|
||||
contentDescription = "返回",
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = LightPurple,
|
||||
titleContentColor = Color.White,
|
||||
navigationIconContentColor = Color.White
|
||||
)
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
when {
|
||||
isLoading -> {
|
||||
LoadingIndicator(modifier = Modifier.padding(paddingValues))
|
||||
}
|
||||
faqs.isEmpty() -> {
|
||||
EmptyStateView(
|
||||
message = "暂无常见问题",
|
||||
icon = "❓",
|
||||
modifier = Modifier.padding(paddingValues)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
items(
|
||||
items = faqs,
|
||||
key = { faq -> faq.id }
|
||||
) { faq ->
|
||||
FAQItem(faq = faq)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.huaga.life_echo.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.huaga.life_echo.ui.components.profile.FeedbackForm
|
||||
import com.huaga.life_echo.ui.icons.AppIcons
|
||||
import com.huaga.life_echo.ui.theme.LightPurple
|
||||
import com.huaga.life_echo.ui.viewmodel.ProfileViewModel
|
||||
import com.huaga.life_echo.ui.viewmodel.ViewModelFactory
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun FeedbackScreen(
|
||||
navController: androidx.navigation.NavHostController? = null,
|
||||
viewModel: ProfileViewModel = viewModel(
|
||||
factory = ViewModelFactory(LocalContext.current)
|
||||
)
|
||||
) {
|
||||
var showSuccessDialog by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("反馈与客服") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { navController?.popBackStack() }) {
|
||||
Icon(
|
||||
imageVector = AppIcons.ArrowBack,
|
||||
contentDescription = "返回",
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = LightPurple,
|
||||
titleContentColor = Color.White,
|
||||
navigationIconContentColor = Color.White
|
||||
)
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
FeedbackForm(
|
||||
onSubmit = { content, contact ->
|
||||
viewModel.submitFeedback(
|
||||
content = content,
|
||||
contact = contact,
|
||||
onSuccess = {
|
||||
showSuccessDialog = true
|
||||
},
|
||||
onError = { }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 提交成功对话框
|
||||
if (showSuccessDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showSuccessDialog = false },
|
||||
title = { Text("提交成功") },
|
||||
text = {
|
||||
Text(
|
||||
text = "感谢您的反馈,我们会尽快处理!",
|
||||
modifier = Modifier.wrapContentHeight()
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
showSuccessDialog = false
|
||||
navController?.popBackStack()
|
||||
}
|
||||
) {
|
||||
Text("确定", color = LightPurple)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.huaga.life_echo.ui.screens
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.huaga.life_echo.ui.components.common.LoadingIndicator
|
||||
import com.huaga.life_echo.ui.components.payment.PlanCard
|
||||
import com.huaga.life_echo.ui.components.profile.PlanDetailsCard
|
||||
import com.huaga.life_echo.ui.icons.AppIcons
|
||||
import com.huaga.life_echo.ui.theme.LightPurple
|
||||
import com.huaga.life_echo.ui.viewmodel.PaymentViewModel
|
||||
import com.huaga.life_echo.ui.viewmodel.ViewModelFactory
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PlanDetailsScreen(
|
||||
navController: androidx.navigation.NavHostController? = null,
|
||||
viewModel: PaymentViewModel = viewModel(
|
||||
factory = ViewModelFactory(LocalContext.current)
|
||||
)
|
||||
) {
|
||||
val currentPlan by viewModel.currentPlan.collectAsState()
|
||||
val isLoading by viewModel.isLoading.collectAsState()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("套餐详情") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { navController?.popBackStack() }) {
|
||||
Icon(
|
||||
imageVector = AppIcons.ArrowBack,
|
||||
contentDescription = "返回",
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = LightPurple,
|
||||
titleContentColor = Color.White,
|
||||
navigationIconContentColor = Color.White
|
||||
)
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
if (isLoading) {
|
||||
LoadingIndicator(modifier = Modifier.padding(paddingValues))
|
||||
} else {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
currentPlan?.let { plan ->
|
||||
item {
|
||||
PlanDetailsCard(plan = plan)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.huaga.life_echo.utils
|
||||
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.scale
|
||||
|
||||
/**
|
||||
* 动画工具类
|
||||
*/
|
||||
object AnimationUtils {
|
||||
|
||||
/**
|
||||
* 创建打字机效果的动画值
|
||||
*/
|
||||
@Composable
|
||||
fun createTypingAnimation(
|
||||
durationMillis: Int = 1000,
|
||||
delayMillis: Int = 0
|
||||
): androidx.compose.runtime.State<Float> {
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "typing")
|
||||
return infiniteTransition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 1f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(durationMillis, delayMillis, FastOutSlowInEasing),
|
||||
repeatMode = RepeatMode.Reverse
|
||||
),
|
||||
label = "typing_alpha"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建脉冲动画
|
||||
*/
|
||||
@Composable
|
||||
fun createPulseAnimation(
|
||||
durationMillis: Int = 1000
|
||||
): androidx.compose.runtime.State<Float> {
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "pulse")
|
||||
return infiniteTransition.animateFloat(
|
||||
initialValue = 0.8f,
|
||||
targetValue = 1f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(durationMillis, easing = FastOutSlowInEasing),
|
||||
repeatMode = RepeatMode.Reverse
|
||||
),
|
||||
label = "pulse_scale"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打字机效果修饰符
|
||||
*/
|
||||
@Composable
|
||||
fun Modifier.typingEffect(visible: Boolean): Modifier {
|
||||
val alpha by AnimationUtils.createTypingAnimation()
|
||||
return this.alpha(if (visible) alpha else 1f)
|
||||
}
|
||||
|
||||
/**
|
||||
* 脉冲效果修饰符
|
||||
*/
|
||||
@Composable
|
||||
fun Modifier.pulseEffect(): Modifier {
|
||||
val scale by AnimationUtils.createPulseAnimation()
|
||||
return this.scale(scale)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.huaga.life_echo.utils
|
||||
|
||||
/**
|
||||
* 支付工具类
|
||||
* 用于处理支付相关的工具函数
|
||||
*/
|
||||
object PaymentUtils {
|
||||
|
||||
/**
|
||||
* 格式化价格
|
||||
* @param price 价格
|
||||
* @param currency 货币符号
|
||||
* @return 格式化后的价格字符串
|
||||
*/
|
||||
fun formatPrice(price: Double, currency: String = "CNY"): String {
|
||||
val symbol = when (currency) {
|
||||
"CNY" -> "¥"
|
||||
"USD" -> "$"
|
||||
"EUR" -> "€"
|
||||
else -> currency
|
||||
}
|
||||
|
||||
return if (price == 0.0) {
|
||||
"免费"
|
||||
} else {
|
||||
"$symbol${String.format("%.2f", price)}"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化计费周期
|
||||
* @param cycle 计费周期(monthly/yearly)
|
||||
* @return 格式化后的周期字符串
|
||||
*/
|
||||
fun formatBillingCycle(cycle: String): String {
|
||||
return when (cycle.lowercase()) {
|
||||
"monthly" -> "/月"
|
||||
"yearly" -> "/年"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化订单状态
|
||||
* @param status 订单状态
|
||||
* @return 格式化后的状态字符串
|
||||
*/
|
||||
fun formatOrderStatus(status: String): String {
|
||||
return when (status.lowercase()) {
|
||||
"pending" -> "待支付"
|
||||
"paid" -> "已支付"
|
||||
"failed" -> "支付失败"
|
||||
"cancelled" -> "已取消"
|
||||
else -> status
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化订阅状态
|
||||
* @param status 订阅状态
|
||||
* @return 格式化后的状态字符串
|
||||
*/
|
||||
fun formatSubscriptionStatus(status: String): String {
|
||||
return when (status.lowercase()) {
|
||||
"active" -> "有效"
|
||||
"expired" -> "已过期"
|
||||
"cancelled" -> "已取消"
|
||||
else -> status
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.huaga.life_echo.utils
|
||||
|
||||
/**
|
||||
* 文本处理工具
|
||||
*/
|
||||
object TextUtils {
|
||||
|
||||
/**
|
||||
* 单行文本省略
|
||||
* @param text 原始文本
|
||||
* @param maxLength 最大长度
|
||||
* @return 省略后的文本
|
||||
*/
|
||||
fun ellipsizeSingleLine(text: String?, maxLength: Int = 50): String {
|
||||
if (text.isNullOrBlank()) return ""
|
||||
|
||||
return if (text.length > maxLength) {
|
||||
text.substring(0, maxLength) + "..."
|
||||
} else {
|
||||
text
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 多行文本省略
|
||||
* @param text 原始文本
|
||||
* @param maxLines 最大行数
|
||||
* @param maxLengthPerLine 每行最大长度
|
||||
* @return 省略后的文本
|
||||
*/
|
||||
fun ellipsizeMultiLine(text: String?, maxLines: Int = 3, maxLengthPerLine: Int = 50): String {
|
||||
if (text.isNullOrBlank()) return ""
|
||||
|
||||
val lines = text.split("\n")
|
||||
if (lines.size <= maxLines) {
|
||||
return text
|
||||
}
|
||||
|
||||
val result = lines.take(maxLines).joinToString("\n")
|
||||
return if (result.length > maxLines * maxLengthPerLine) {
|
||||
result.substring(0, maxLines * maxLengthPerLine) + "..."
|
||||
} else {
|
||||
result + "..."
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除多余的空白字符
|
||||
* @param text 原始文本
|
||||
* @return 处理后的文本
|
||||
*/
|
||||
fun trimWhitespace(text: String?): String {
|
||||
return text?.replace(Regex("\\s+"), " ")?.trim() ?: ""
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取引用内容(以引号包裹的内容)
|
||||
* @param text 原始文本
|
||||
* @return 引用内容列表
|
||||
*/
|
||||
fun extractQuotes(text: String?): List<String> {
|
||||
if (text.isNullOrBlank()) return emptyList()
|
||||
|
||||
// 使用原始字符串避免转义问题
|
||||
val quotePattern = Regex("""["'](.*?)["']""")
|
||||
return quotePattern.findAll(text).map { it.groupValues[1] }.toList()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.huaga.life_echo.utils
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 时间格式化工具
|
||||
*/
|
||||
object TimeUtils {
|
||||
|
||||
/**
|
||||
* 格式化时间戳为相对时间
|
||||
* @param timestamp 时间戳(毫秒)
|
||||
* @return 格式化后的时间字符串(刚刚/分钟前/小时前/日期)
|
||||
*/
|
||||
fun formatRelativeTime(timestamp: Long?): String {
|
||||
if (timestamp == null) return "刚刚"
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
val diff = now - timestamp
|
||||
|
||||
return when {
|
||||
diff < 60000 -> "刚刚" // 1分钟内
|
||||
diff < 3600000 -> "${diff / 60000}分钟前" // 1小时内
|
||||
diff < 86400000 -> "${diff / 3600000}小时前" // 24小时内
|
||||
diff < 604800000 -> "${diff / 86400000}天前" // 7天内
|
||||
else -> {
|
||||
val date = Date(timestamp)
|
||||
val calendar = Calendar.getInstance()
|
||||
val nowCalendar = Calendar.getInstance()
|
||||
|
||||
// 如果是今年,显示月日
|
||||
if (calendar.get(Calendar.YEAR) == nowCalendar.get(Calendar.YEAR)) {
|
||||
SimpleDateFormat("MM月dd日", Locale.getDefault()).format(date)
|
||||
} else {
|
||||
// 跨年显示年月日
|
||||
SimpleDateFormat("yyyy年MM月dd日", Locale.getDefault()).format(date)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间戳为日期时间
|
||||
* @param timestamp 时间戳(毫秒)
|
||||
* @return 格式化后的日期时间字符串
|
||||
*/
|
||||
fun formatDateTime(timestamp: Long?): String {
|
||||
if (timestamp == null) return ""
|
||||
|
||||
val date = Date(timestamp)
|
||||
val calendar = Calendar.getInstance()
|
||||
val nowCalendar = Calendar.getInstance()
|
||||
calendar.time = date
|
||||
|
||||
// 判断是否是今天
|
||||
val isToday = calendar.get(Calendar.YEAR) == nowCalendar.get(Calendar.YEAR) &&
|
||||
calendar.get(Calendar.DAY_OF_YEAR) == nowCalendar.get(Calendar.DAY_OF_YEAR)
|
||||
|
||||
return if (isToday) {
|
||||
"今天 ${SimpleDateFormat("HH:mm", Locale.getDefault()).format(date)}"
|
||||
} else {
|
||||
SimpleDateFormat("MM月dd日 HH:mm", Locale.getDefault()).format(date)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化更新时间
|
||||
* @param timestamp 时间戳(毫秒)
|
||||
* @return 格式化后的更新时间字符串
|
||||
*/
|
||||
fun formatUpdateTime(timestamp: Long?): String {
|
||||
if (timestamp == null) return ""
|
||||
|
||||
val diff = System.currentTimeMillis() - timestamp
|
||||
return when {
|
||||
diff < 60000 -> "刚刚更新"
|
||||
diff < 3600000 -> "${diff / 60000}分钟前更新"
|
||||
diff < 86400000 -> "${diff / 3600000}小时前更新"
|
||||
diff < 604800000 -> "${diff / 86400000}天前更新"
|
||||
else -> {
|
||||
val date = Date(timestamp)
|
||||
SimpleDateFormat("MM月dd日更新", Locale.getDefault()).format(date)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user