This commit is contained in:
Hsy
2025-10-15 10:15:55 +08:00
parent ede72fdedc
commit 6e8529caad
48 changed files with 880 additions and 427 deletions

View File

@@ -41,8 +41,5 @@ enum class PointEvent(val eventName: String, val eventCode: Int) {
AdShownBanner("ad_shown_banner", 9001),
AdClickReward("ad_click_reward", 9002);
fun toMap(): Map<String, Any> = mapOf(
"eventName" to eventName,
"eventCode" to eventCode,
)
fun toMap(): Map<String, Any> = mapOf("eventName" to eventName, "eventCode" to eventCode)
}

View File

@@ -7,8 +7,8 @@ package com.taskttl.core.network
*/
object ApiConfig {
/** 基本地址 */
const val BASE_URL = "http://10.0.0.5:8888/api/v1"
// const val BASE_URL = "https://api.tikttl.com/api/v1"
// const val BASE_URL = "http://10.0.0.5:8888/api/v1"
const val BASE_URL = "https://api.taskttl.com/api/v1"
/** 反馈地址 */
const val FEEDBACK_URL = "$BASE_URL/feedback"

View File

@@ -0,0 +1,131 @@
package com.taskttl.core.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import taskttl.composeapp.generated.resources.Res
import taskttl.composeapp.generated.resources.loading
@Composable
fun LoadingOverlay(
visible: Boolean,
messageRes: StringResource = Res.string.loading,
) {
AnimatedVisibility(
visible = visible,
enter = fadeIn(animationSpec = tween(300)),
exit = fadeOut(animationSpec = tween(300))
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.4f))
.blur(8.dp)
// 拦截所有点击事件,防止点击到底层
.clickable(
indication = null, // 无点击动画
interactionSource = remember { MutableInteractionSource() }
) { },
contentAlignment = Alignment.Center
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
RotatingGradientRing(
size = 56.dp,
stroke = 6.dp,
gradientColors = listOf(
Color(0xFF4285F4),
Color(0xFF73A7F9),
Color(0xFF4285F4)
)
)
Spacer(Modifier.height(16.dp))
Text(
text = stringResource(messageRes),
color = MaterialTheme.colorScheme.onSurface,
fontSize = 15.sp
)
}
// CircularProgressIndicator(
// modifier = Modifier.size(48.dp),
// strokeWidth = 4.dp,
// color = MaterialTheme.colorScheme.primary
// )
}
}
}
@Composable
private fun RotatingGradientRing(
size: Dp,
stroke: Dp,
gradientColors: List<Color>,
) {
val rotation by rememberInfiniteTransition(label = "").animateFloat(
initialValue = 0f,
targetValue = 360f,
animationSpec = infiniteRepeatable(
animation = tween(1200, easing = LinearEasing)
), label = ""
)
Canvas(
modifier = Modifier
.size(size)
.graphicsLayer(rotationZ = rotation)
) {
drawCircle(
brush = Brush.sweepGradient(gradientColors),
style = Stroke(width = stroke.toPx(), cap = StrokeCap.Round)
)
}
}
@Preview
@Composable
private fun PreviewTaskTTLLoading() {
var show by remember { mutableStateOf(true) }
LaunchedEffect(Unit) {
delay(3000)
show = false
}
LoadingOverlay(visible = show)
}

View File

@@ -1,30 +0,0 @@
package com.taskttl.core.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@Composable
fun LoadingScreen() {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0x88000000))
// 拦截所有点击事件,防止点击到底层
.clickable(
indication = null, // 无点击动画
interactionSource = remember { MutableInteractionSource() }
) { },
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}

View File

@@ -0,0 +1,22 @@
package com.taskttl.core.utils
/**
* 外部应用程序启动器
* @author DevTTL
* @date 2025/03/08
* @constructor 创建[ExternalAppLauncher]
*/
expect object ExternalAppLauncher {
/**
* 打开应用程序评级
*/
suspend fun openAppRating()
/**
* 打开网址
* @param [url] 网址
*/
suspend fun openUrl(url: String)
}

View File

@@ -0,0 +1,20 @@
package com.taskttl.core.utils
/**
* 本地化
* @author DevTTL
* @date 2025/10/14
*/
expect object Localization {
/**
* 应用语言
* @param [iso]
*/
fun applyLanguage(iso: String)
/**
* 获取设备当前语言
* @return 设备语言代码,例如"zh"、"en"等
*/
fun getDeviceLanguage(): String
}

View File

@@ -1,8 +1,5 @@
package com.taskttl.core.utils
import io.ktor.client.plugins.logging.Logger
enum class LogLevel { DEBUG, INFO, WARN, ERROR }
/**
* 日志工具类
@@ -14,4 +11,6 @@ expect object LogUtils {
fun i(tag: String, message: String)
fun w(tag: String, message: String)
fun e(tag: String, message: String, throwable: Throwable? = null)
}
}
// enum class LogLevel { DEBUG, INFO, WARN, ERROR }

View File

@@ -8,6 +8,7 @@ import com.taskttl.data.viewmodel.CategoryViewModel
import com.taskttl.data.viewmodel.CountdownViewModel
import com.taskttl.data.viewmodel.FeedbackViewModel
import com.taskttl.data.viewmodel.OnboardingViewModel
import com.taskttl.data.viewmodel.SettingsViewModel
import com.taskttl.data.viewmodel.SplashViewModel
import com.taskttl.data.viewmodel.TaskViewModel
import org.koin.core.KoinApplication
@@ -44,5 +45,6 @@ val viewModelModule = module {
viewModelOf(::TaskViewModel)
viewModelOf(::CategoryViewModel)
viewModelOf(::CountdownViewModel)
viewModelOf(::SettingsViewModel)
viewModelOf(::FeedbackViewModel)
}

View File

@@ -3,18 +3,28 @@ package com.taskttl.data.repository.impl
import com.taskttl.data.local.dao.CategoryDao
import com.taskttl.data.local.dao.CountdownDao
import com.taskttl.data.local.dao.TaskDao
import com.taskttl.data.mapper.CategoryMapper
import com.taskttl.data.local.model.Category
import com.taskttl.data.local.model.CategoryColor
import com.taskttl.data.local.model.CategoryIcon
import com.taskttl.data.local.model.CategoryStatistics
import com.taskttl.data.local.model.CategoryType
import com.taskttl.data.mapper.CategoryMapper
import com.taskttl.data.repository.CategoryRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import org.jetbrains.compose.resources.getString
import taskttl.composeapp.generated.resources.Res
import taskttl.composeapp.generated.resources.category_birthday
import taskttl.composeapp.generated.resources.category_book
import taskttl.composeapp.generated.resources.category_briefcase
import taskttl.composeapp.generated.resources.category_exam
import taskttl.composeapp.generated.resources.category_festival
import taskttl.composeapp.generated.resources.category_goal
import taskttl.composeapp.generated.resources.category_heart
import taskttl.composeapp.generated.resources.category_home
import kotlin.time.Clock
import kotlin.time.ExperimentalTime
import kotlin.uuid.ExperimentalUuidApi
@@ -123,7 +133,7 @@ class CategoryRepositoryImpl(
val defaultTaskCategories = listOf(
Category(
id = Uuid.random().toString(),
name = "工作",
name = getString(Res.string.category_briefcase),
color = CategoryColor.BLUE,
icon = CategoryIcon.BRIEFCASE,
type = CategoryType.TASK,
@@ -132,7 +142,7 @@ class CategoryRepositoryImpl(
),
Category(
id = Uuid.random().toString(),
name = "生活",
name = getString(Res.string.category_home),
color = CategoryColor.GREEN,
icon = CategoryIcon.HOME,
type = CategoryType.TASK,
@@ -141,7 +151,7 @@ class CategoryRepositoryImpl(
),
Category(
id = Uuid.random().toString(),
name = "健康",
name = getString(Res.string.category_heart),
color = CategoryColor.ORANGE,
icon = CategoryIcon.HEART,
type = CategoryType.TASK,
@@ -150,7 +160,7 @@ class CategoryRepositoryImpl(
),
Category(
id = Uuid.random().toString(),
name = "学习",
name = getString(Res.string.category_book),
color = CategoryColor.PURPLE,
icon = CategoryIcon.BOOK,
type = CategoryType.TASK,
@@ -163,7 +173,7 @@ class CategoryRepositoryImpl(
val defaultCountdownCategories = listOf(
Category(
id = Uuid.random().toString(),
name = "生日",
name = getString(Res.string.category_birthday),
color = CategoryColor.PINK,
icon = CategoryIcon.BIRTHDAY,
type = CategoryType.COUNTDOWN,
@@ -172,7 +182,7 @@ class CategoryRepositoryImpl(
),
Category(
id = Uuid.random().toString(),
name = "节日",
name = getString(Res.string.category_festival),
color = CategoryColor.CYAN,
icon = CategoryIcon.FESTIVAL,
type = CategoryType.COUNTDOWN,
@@ -181,7 +191,7 @@ class CategoryRepositoryImpl(
),
Category(
id = Uuid.random().toString(),
name = "目标",
name = getString(Res.string.category_goal),
color = CategoryColor.YELLOW,
icon = CategoryIcon.GOAL,
type = CategoryType.COUNTDOWN,
@@ -190,7 +200,7 @@ class CategoryRepositoryImpl(
),
Category(
id = Uuid.random().toString(),
name = "考试",
name = getString(Res.string.category_exam),
color = CategoryColor.GREEN,
icon = CategoryIcon.EXAM,
type = CategoryType.COUNTDOWN,

View File

@@ -0,0 +1,63 @@
package com.taskttl.data.state
/**
* 设置状态
* @author DevTTL
* @date 2025/10/14
* @constructor 创建[SettingsState]
* @param [isLoading] 正在加载
* @param [error] 错误
*/
data class SettingsState(
val isLoading: Boolean = false,
val error: String? = null,
)
/**
* 设置意图
* @author DevTTL
* @date 2025/10/14
* @constructor 创建[SettingsIntent]
*/
sealed class SettingsIntent {
/**
* 打开应用评分
* @author DevTTL
* @date 2025/10/14
*/
object OpenAppRating: SettingsIntent()
/**
* 打开网址
* @author DevTTL
* @date 2025/10/14
* @constructor 创建[OpenUrl]
* @param [url] 网址
*/
class OpenUrl(val url:String): SettingsIntent()
}
/**
* 设置效果
* @author DevTTL
* @date 2025/10/14
* @constructor 创建[SettingsEffect]
*/
sealed class SettingsEffect {
/**
* 导航返回
* @author admin
* @date 2025/10/12
*/
object NavigateBack : SettingsEffect()
/**
* 显示消息
* @author admin
* @date 2025/10/12
* @constructor 创建[ShowMessage]
* @param [message] 消息
*/
data class ShowMessage(val message: String) : SettingsEffect()
}

View File

@@ -0,0 +1,58 @@
package com.taskttl.data.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.taskttl.core.utils.ExternalAppLauncher
import com.taskttl.data.state.SettingsEffect
import com.taskttl.data.state.SettingsIntent
import com.taskttl.data.state.SettingsState
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
/**
* 反馈视图模型
* @author admin
* @date 2025/10/12
* @constructor 创建[SettingsViewModel]
*/
class SettingsViewModel() : ViewModel() {
private val _state = MutableStateFlow(SettingsState())
val state: StateFlow<SettingsState> = _state.asStateFlow()
private val _effects = MutableSharedFlow<SettingsEffect>()
val effects: SharedFlow<SettingsEffect> = _effects.asSharedFlow()
fun handleIntent(intent: SettingsIntent) {
when (intent) {
is SettingsIntent.OpenAppRating -> openAppRating()
is SettingsIntent.OpenUrl -> openUrl(intent.url)
}
}
private fun openAppRating() {
viewModelScope.launch {
ExternalAppLauncher.openAppRating()
}
}
private fun openUrl(url:String) {
viewModelScope.launch {
ExternalAppLauncher.openUrl(url)
}
}
/**
* 清除错误
*/
private fun clearError() {
_state.value = _state.value.copy(error = null)
}
}

View File

@@ -17,9 +17,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
@@ -48,7 +45,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.taskttl.core.ui.LoadingScreen
import com.taskttl.core.ui.LoadingOverlay
import com.taskttl.core.utils.ToastUtils
import com.taskttl.data.local.model.Category
import com.taskttl.data.local.model.CategoryColor
@@ -56,7 +53,6 @@ import com.taskttl.data.local.model.CategoryIcon
import com.taskttl.data.local.model.CategoryType
import com.taskttl.data.state.CategoryEffect
import com.taskttl.data.state.CategoryIntent
import com.taskttl.data.state.TaskEffect
import com.taskttl.data.viewmodel.CategoryViewModel
import com.taskttl.ui.components.AppHeader
import kotlinx.datetime.TimeZone
@@ -89,7 +85,7 @@ import kotlin.uuid.Uuid
fun CategoryEditScreen(
categoryId: String? = null,
onNavigateBack: () -> Unit,
viewModel: CategoryViewModel = koinViewModel()
viewModel: CategoryViewModel = koinViewModel(),
) {
LaunchedEffect(Unit) {
viewModel.effects.collect { effect ->
@@ -173,6 +169,7 @@ fun CategoryEditScreen(
LazyColumn(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
@@ -257,7 +254,7 @@ fun CategoryEditScreen(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.fillMaxWidth()
) {
CategoryIcon.entries.forEach {item ->
CategoryIcon.entries.forEach { item ->
IconOption(
item = item,
selected = icon == item,
@@ -283,7 +280,7 @@ fun CategoryEditScreen(
}
}
}
if (state.isLoading) LoadingScreen()
LoadingOverlay(state.isLoading)
}
}
@@ -296,7 +293,7 @@ private fun CategoryTypeOption(
icon: ImageVector,
text: String,
selected: Boolean,
onClick: () -> Unit
onClick: () -> Unit,
) {
Card(
modifier = modifier
@@ -367,7 +364,7 @@ private fun ColorOption(
colorLong: CategoryColor,
selected: Boolean,
onClick: () -> Unit,
modifier: Modifier
modifier: Modifier,
) {
Box(
modifier = modifier

View File

@@ -56,7 +56,8 @@ import androidx.navigation.NavHostController
import com.taskttl.core.routes.Routes.Main
import com.taskttl.core.ui.ActionButtonListItem
import com.taskttl.core.ui.ErrorDialog
import com.taskttl.core.ui.LoadingScreen
import com.taskttl.core.ui.LoadingOverlay
import com.taskttl.core.utils.ToastUtils
import com.taskttl.data.local.model.Category
import com.taskttl.data.local.model.CategoryType
@@ -127,7 +128,7 @@ fun CategoryScreen(
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.background(MaterialTheme.colorScheme.background)
.padding(16.dp)
) {
@@ -210,7 +211,7 @@ fun CategoryScreen(
contentDescription = stringResource(Res.string.title_add_category)
)
}
if (state.isLoading) LoadingScreen()
LoadingOverlay(state.isLoading)
}
}

View File

@@ -20,6 +20,7 @@ import androidx.compose.material.icons.filled.CalendarToday
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -33,7 +34,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.taskttl.core.ui.Chip
import com.taskttl.core.ui.LoadingScreen
import com.taskttl.core.ui.LoadingOverlay
import com.taskttl.core.utils.DateUtils
import com.taskttl.data.state.CountdownEffect
import com.taskttl.data.viewmodel.CountdownViewModel
@@ -104,7 +106,7 @@ fun CountdownDetailScreen(
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.background(MaterialTheme.colorScheme.background)
.padding(16.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
@@ -234,7 +236,7 @@ fun CountdownDetailScreen(
}
}
}
if (state.isLoading) LoadingScreen()
LoadingOverlay(state.isLoading)
}
}

View File

@@ -1,5 +1,6 @@
package com.taskttl.presentation.countdown
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -40,7 +41,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.taskttl.core.ui.LoadingScreen
import com.taskttl.core.ui.LoadingOverlay
import com.taskttl.core.utils.ToastUtils
import com.taskttl.data.local.model.Category
import com.taskttl.data.local.model.Countdown
@@ -163,6 +165,7 @@ fun CountdownEditScreen(
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.verticalScroll(rememberScrollState())
.padding(16.dp),
) {
@@ -283,6 +286,6 @@ fun CountdownEditScreen(
}
}
}
if (state.isLoading) LoadingScreen()
LoadingOverlay(state.isLoading)
}
}

View File

@@ -58,12 +58,10 @@ import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import com.taskttl.core.routes.Routes
import com.taskttl.core.ui.ErrorDialog
import com.taskttl.core.ui.LoadingScreen
import com.taskttl.core.ui.LoadingOverlay
import com.taskttl.core.utils.DateUtils
import com.taskttl.core.utils.ToastUtils
import com.taskttl.data.local.model.Countdown
import com.taskttl.data.state.CategoryEffect
import com.taskttl.data.state.CategoryIntent
import com.taskttl.data.state.CountdownEffect
import com.taskttl.data.state.CountdownIntent
import com.taskttl.data.viewmodel.CountdownViewModel
@@ -75,19 +73,18 @@ import org.koin.compose.viewmodel.koinViewModel
import taskttl.composeapp.generated.resources.Res
import taskttl.composeapp.generated.resources.delete
import taskttl.composeapp.generated.resources.desc_add_countdown
import taskttl.composeapp.generated.resources.edit
import taskttl.composeapp.generated.resources.label_countdown_list
import taskttl.composeapp.generated.resources.label_days
import taskttl.composeapp.generated.resources.edit
import taskttl.composeapp.generated.resources.text_add_countdown_tip
import taskttl.composeapp.generated.resources.text_no_countdowns
import taskttl.composeapp.generated.resources.title_countdown
import taskttl.composeapp.generated.resources.title_edit_countdown
@Composable
@Preview
fun CountdownScreen(
navController: NavHostController,
viewModel: CountdownViewModel = koinViewModel()
viewModel: CountdownViewModel = koinViewModel(),
) {
val state by viewModel.state.collectAsState()
@@ -97,6 +94,7 @@ fun CountdownScreen(
is CountdownEffect.ShowMessage -> {
ToastUtils.show(effect.message)
}
is CountdownEffect.NavigateBack -> {
navController.popBackStack()
}
@@ -125,7 +123,7 @@ fun CountdownScreen(
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.background(MaterialTheme.colorScheme.background)
.padding(16.dp)
) {
// 分类筛选
@@ -220,7 +218,7 @@ fun CountdownScreen(
contentDescription = stringResource(Res.string.desc_add_countdown)
)
}
if (state.isLoading) LoadingScreen()
LoadingOverlay(state.isLoading)
}
}
@@ -229,7 +227,7 @@ fun CountdownCard(
countdown: Countdown,
onEdit: () -> Unit = {},
onDelete: () -> Unit = {},
onCardClick: () -> Unit = {}
onCardClick: () -> Unit = {},
) {
val countdownTime = DateUtils.calculateCountdownTime(countdown.targetDate)
countdown.category
@@ -422,7 +420,7 @@ fun IconBut(onClick: () -> Unit = {}, icon: ImageVector) {
@Composable
fun ReminderDialog(
onDismiss: () -> Unit,
onSave: (String) -> Unit
onSave: (String) -> Unit,
) {
var isEnabled by remember { mutableStateOf(true) }
var timeBefore by remember { mutableStateOf("1天前") }
@@ -490,7 +488,7 @@ fun MoreActionsDialog(
onDismiss: () -> Unit,
onEdit: () -> Unit,
onShare: () -> Unit,
onDelete: () -> Unit
onDelete: () -> Unit,
) {
AlertDialog(
onDismissRequest = { onDismiss() },
@@ -521,7 +519,7 @@ private fun ActionItem(
text: String,
icon: ImageVector,
danger: Boolean = false,
onClick: () -> Unit
onClick: () -> Unit,
) {
Row(
modifier = Modifier

View File

@@ -1,6 +1,7 @@
package com.taskttl.presentation.settings
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -31,15 +32,20 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.taskttl.data.state.SettingsIntent
import com.taskttl.data.viewmodel.SettingsViewModel
import com.taskttl.ui.components.AppHeader
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.stringResource
import org.koin.compose.viewmodel.koinViewModel
import taskttl.composeapp.generated.resources.Res
import taskttl.composeapp.generated.resources.all_rights_reserved
import taskttl.composeapp.generated.resources.app_intro_content
import taskttl.composeapp.generated.resources.app_intro_title
import taskttl.composeapp.generated.resources.app_name
import taskttl.composeapp.generated.resources.app_name_description
import taskttl.composeapp.generated.resources.app_version_code
import taskttl.composeapp.generated.resources.app_version_name
import taskttl.composeapp.generated.resources.build_version
import taskttl.composeapp.generated.resources.contact_us
import taskttl.composeapp.generated.resources.copyright_year
@@ -47,6 +53,7 @@ import taskttl.composeapp.generated.resources.developer_text
import taskttl.composeapp.generated.resources.devttl_team
import taskttl.composeapp.generated.resources.email
import taskttl.composeapp.generated.resources.email_text
import taskttl.composeapp.generated.resources.setting_privacy_email_uri
import taskttl.composeapp.generated.resources.tech_stack
import taskttl.composeapp.generated.resources.tech_stack_compose
import taskttl.composeapp.generated.resources.tech_stack_kmp
@@ -62,7 +69,8 @@ import taskttl.composeapp.generated.resources.web_url
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AboutScreen(
onNavigateBack: () -> Unit
onNavigateBack: () -> Unit,
viewModel: SettingsViewModel = koinViewModel(),
) {
Box(modifier = Modifier.fillMaxSize()) {
Column(
@@ -77,7 +85,7 @@ fun AboutScreen(
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.background(MaterialTheme.colorScheme.background)
.verticalScroll(rememberScrollState())
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
@@ -118,9 +126,15 @@ fun AboutScreen(
Column(
modifier = Modifier.padding(16.dp)
) {
AboutInfoRow(labelRes = Res.string.version, value = "1.0.0")
AboutInfoRow(
labelRes = Res.string.version,
valueRes = Res.string.app_version_name
)
Spacer(modifier = Modifier.height(8.dp))
AboutInfoRow(labelRes = Res.string.build_version, value = "1")
AboutInfoRow(
labelRes = Res.string.build_version,
valueRes = Res.string.app_version_code
)
}
}
Spacer(modifier = Modifier.height(16.dp))
@@ -148,29 +162,29 @@ fun AboutScreen(
}
Spacer(modifier = Modifier.height(16.dp))
// 技术栈
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = stringResource(Res.string.tech_stack),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Medium
)
Spacer(modifier = Modifier.height(12.dp))
TechStackItem("Kotlin Multiplatform", Res.string.tech_stack_kmp)
TechStackItem("Jetpack Compose", Res.string.tech_stack_compose)
TechStackItem("Room Database", Res.string.tech_stack_room)
TechStackItem("Koin", Res.string.tech_stack_koin)
TechStackItem("Ktor", Res.string.tech_stack_ktor)
TechStackItem("MVI Architecture", Res.string.tech_stack_mvi)
}
}
Spacer(modifier = Modifier.height(16.dp))
// Card(
// modifier = Modifier.fillMaxWidth()
// ) {
// Column(
// modifier = Modifier.padding(16.dp)
// ) {
// Text(
// text = stringResource(Res.string.tech_stack),
// style = MaterialTheme.typography.titleMedium,
// fontWeight = FontWeight.Medium
// )
//
// Spacer(modifier = Modifier.height(12.dp))
//
// TechStackItem("Kotlin Multiplatform", Res.string.tech_stack_kmp)
// TechStackItem("Jetpack Compose", Res.string.tech_stack_compose)
// TechStackItem("Room Database", Res.string.tech_stack_room)
// TechStackItem("Koin", Res.string.tech_stack_koin)
// TechStackItem("Ktor", Res.string.tech_stack_ktor)
// TechStackItem("MVI Architecture", Res.string.tech_stack_mvi)
// }
// }
// Spacer(modifier = Modifier.height(16.dp))
// 开发者信息
Card(
modifier = Modifier.fillMaxWidth()
@@ -208,18 +222,22 @@ fun AboutScreen(
Spacer(modifier = Modifier.height(12.dp))
val email = stringResource(Res.string.email)
val emailUrl = stringResource(Res.string.setting_privacy_email_uri, email)
ContactItem(
icon = Icons.Default.Email,
labelRes = Res.string.email_text,
valueRes = Res.string.email
valueRes = Res.string.email,
onClick = { viewModel.handleIntent(SettingsIntent.OpenUrl(emailUrl)) }
)
Spacer(modifier = Modifier.height(8.dp))
val url = stringResource(Res.string.web_url)
ContactItem(
icon = Icons.Default.Language,
labelRes = Res.string.web_text,
valueRes = Res.string.web_url
valueRes = Res.string.web_url,
onClick = { viewModel.handleIntent(SettingsIntent.OpenUrl(url)) }
)
}
}
@@ -243,7 +261,7 @@ fun AboutScreen(
@Composable
private fun AboutInfoRow(
labelRes: StringResource,
value: String
valueRes: StringResource,
) {
Row(
modifier = Modifier.fillMaxWidth(),
@@ -255,7 +273,7 @@ private fun AboutInfoRow(
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Text(
text = value,
text = stringResource(valueRes),
style = MaterialTheme.typography.bodyMedium,
fontWeight = FontWeight.Medium
)
@@ -265,7 +283,7 @@ private fun AboutInfoRow(
@Composable
private fun TechStackItem(
name: String,
descriptionRes: StringResource
descriptionRes: StringResource,
) {
Column {
Text(
@@ -286,10 +304,13 @@ private fun TechStackItem(
private fun ContactItem(
icon: ImageVector,
labelRes: StringResource,
valueRes: StringResource
valueRes: StringResource,
onClick: (() -> Unit)? = null,
) {
Row(
verticalAlignment = Alignment.CenterVertically
modifier = Modifier.fillMaxWidth()
.clickable(enabled = onClick != null) { onClick?.invoke() },
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = icon,

View File

@@ -100,7 +100,7 @@ fun DataManagementScreen(
LazyColumn(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.background(MaterialTheme.colorScheme.background)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {

View File

@@ -42,7 +42,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.taskttl.core.domain.FeedbackType
import com.taskttl.core.ui.ErrorDialog
import com.taskttl.core.ui.LoadingScreen
import com.taskttl.core.ui.LoadingOverlay
import com.taskttl.core.utils.ToastUtils
import com.taskttl.data.network.domain.req.FeedbackReq
import com.taskttl.data.state.FeedbackEffect
@@ -110,7 +111,7 @@ fun FeedbackScreen(
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.background(MaterialTheme.colorScheme.background)
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
@@ -218,7 +219,7 @@ fun FeedbackScreen(
Spacer(modifier = Modifier.height(32.dp))
}
}
if (state.isLoading) LoadingScreen()
LoadingOverlay(state.isLoading)
}
}

View File

@@ -4,6 +4,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -29,7 +30,7 @@ fun PrivacyScreen(onNavigateBack: () -> Unit) {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5)),
.background(MaterialTheme.colorScheme.background),
) {
DevTTLWebView(
modifier = Modifier.fillMaxSize(),

View File

@@ -24,6 +24,7 @@ import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.filled.Storage
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -40,10 +41,14 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import com.taskttl.core.routes.Routes
import com.taskttl.core.utils.ExternalAppLauncher
import com.taskttl.data.state.SettingsIntent
import com.taskttl.data.viewmodel.SettingsViewModel
import com.taskttl.ui.components.AppHeader
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.koin.compose.viewmodel.koinViewModel
import taskttl.composeapp.generated.resources.Res
import taskttl.composeapp.generated.resources.section_data_management
import taskttl.composeapp.generated.resources.section_general_settings
@@ -65,6 +70,8 @@ import taskttl.composeapp.generated.resources.setting_language
import taskttl.composeapp.generated.resources.setting_language_desc
import taskttl.composeapp.generated.resources.setting_privacy_policy
import taskttl.composeapp.generated.resources.setting_privacy_policy_desc
import taskttl.composeapp.generated.resources.setting_privacy_rate
import taskttl.composeapp.generated.resources.setting_privacy_rate_desc
import taskttl.composeapp.generated.resources.setting_push_notification
import taskttl.composeapp.generated.resources.setting_push_notification_desc
import taskttl.composeapp.generated.resources.setting_share_achievement
@@ -79,6 +86,7 @@ import taskttl.composeapp.generated.resources.title_app_settings
@Preview
fun SettingsScreen(
navController: NavHostController,
viewModel: SettingsViewModel = koinViewModel()
) {
Box(
modifier = Modifier
@@ -96,7 +104,7 @@ fun SettingsScreen(
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.background(MaterialTheme.colorScheme.background)
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
@@ -218,13 +226,18 @@ fun SettingsScreen(
showArrow = true,
onClick = { navController.navigate(Routes.Main.Settings.Privacy) }
)
SettingItem(
titleRes = Res.string.setting_privacy_rate,
descriptionRes = Res.string.setting_privacy_rate_desc,
showArrow = true,
onClick = { viewModel.handleIntent(SettingsIntent.OpenAppRating) }
)
SettingItem(
titleRes = Res.string.setting_about_app,
descriptionRes = Res.string.setting_about_app_desc,
showArrow = true,
onClick = { navController.navigate(Routes.Main.Settings.About) }
)
Spacer(modifier = Modifier.height(24.dp))
}
}

View File

@@ -44,7 +44,7 @@ import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.taskttl.core.routes.Routes
import com.taskttl.core.ui.Chip
import com.taskttl.core.ui.LoadingScreen
import com.taskttl.data.local.model.Category
import com.taskttl.data.state.CountdownIntent
import com.taskttl.data.state.TaskIntent
@@ -95,7 +95,7 @@ fun StatisticsScreen(
LazyColumn(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.background(MaterialTheme.colorScheme.background)
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {

View File

@@ -37,7 +37,8 @@ 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.taskttl.core.ui.LoadingScreen
import com.taskttl.core.ui.LoadingOverlay
import com.taskttl.data.state.TaskEffect
import com.taskttl.data.state.TaskIntent
import com.taskttl.data.viewmodel.TaskViewModel
@@ -111,6 +112,7 @@ fun TaskDetailScreen(
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.Top
@@ -229,6 +231,6 @@ fun TaskDetailScreen(
}
}
if (state.isLoading) LoadingScreen()
LoadingOverlay(state.isLoading)
}
}

View File

@@ -1,5 +1,6 @@
package com.taskttl.presentation.task
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -39,7 +40,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.taskttl.core.routes.Routes
import com.taskttl.core.ui.LoadingScreen
import com.taskttl.core.ui.LoadingOverlay
import com.taskttl.core.utils.ToastUtils
import com.taskttl.data.local.model.Category
import com.taskttl.data.local.model.Task
@@ -168,6 +170,7 @@ fun TaskEditorScreen(
Column(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background)
.verticalScroll(rememberScrollState())
.padding(16.dp),
) {
@@ -292,6 +295,6 @@ fun TaskEditorScreen(
)
}
}
if (state.isLoading) LoadingScreen()
LoadingOverlay(state.isLoading)
}
}

View File

@@ -54,7 +54,8 @@ import androidx.navigation.NavHostController
import com.taskttl.core.routes.Routes
import com.taskttl.core.ui.ActionButtonListItem
import com.taskttl.core.ui.ErrorDialog
import com.taskttl.core.ui.LoadingScreen
import com.taskttl.core.ui.LoadingOverlay
import com.taskttl.core.utils.ToastUtils
import com.taskttl.data.local.model.Task
import com.taskttl.data.state.TaskEffect
@@ -129,7 +130,7 @@ fun TaskScreen(
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF5F5F5))
.background(MaterialTheme.colorScheme.background)
.padding(16.dp)
) {
if (state.isSearch) {
@@ -258,7 +259,7 @@ fun TaskScreen(
contentDescription = stringResource(Res.string.title_add_task)
)
}
if (state.isLoading) LoadingScreen()
LoadingOverlay(state.isLoading)
}
}

View File

@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -39,16 +40,19 @@ fun AppHeader(
showBack: Boolean = false,
onBackClick: (() -> Unit)? = null,
trailingIcon: ImageVector? = null,
onTrailingClick: (() -> Unit)? = null
onTrailingClick: (() -> Unit)? = null,
) {
val gradient = Brush.linearGradient(
colors = listOf(
MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.secondary
)
)
Row(
modifier = Modifier
.fillMaxWidth()
.background(
Brush.linearGradient(
colors = listOf(Color(0xFF667EEA), Color(0xFF764BA2))
)
)
.background(brush = gradient)
.padding(horizontal = 20.dp, vertical = 15.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically

View File

@@ -9,11 +9,11 @@ import androidx.compose.ui.graphics.Color
/** 浅色方案 */
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF6750A4),
primary = Color(0xFF667EEA),
onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFFEADDFF),
onPrimaryContainer = Color(0xFF21005D),
secondary = Color(0xFF625B71),
secondary = Color(0xFF764BA2),
onSecondary = Color(0xFFFFFFFF),
secondaryContainer = Color(0xFFE8DEF8),
onSecondaryContainer = Color(0xFF1D192B),
@@ -25,7 +25,7 @@ private val LightColorScheme = lightColorScheme(
onError = Color(0xFFFFFFFF),
errorContainer = Color(0xFFFFDAD6),
onErrorContainer = Color(0xFF410002),
background = Color(0xFFFFFBFE),
background = Color(0xFFF5F5F5),
onBackground = Color(0xFF1C1B1F),
surface = Color(0xFFFFFBFE),
onSurface = Color(0xFF1C1B1F),