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