封装BaseViewModel
This commit is contained in:
BIN
composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 684 B |
BIN
composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png
Normal file
BIN
composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
composeApp/src/androidMain/res/mipmap-ldpi/ic_launcher.png
Normal file
BIN
composeApp/src/androidMain/res/mipmap-ldpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 449 B |
BIN
composeApp/src/androidMain/res/mipmap-ldpi/ic_launcher_round.png
Normal file
BIN
composeApp/src/androidMain/res/mipmap-ldpi/ic_launcher_round.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 633 B |
316
composeApp/src/commonMain/composeResources/values-zh/strings.xml
Normal file
316
composeApp/src/commonMain/composeResources/values-zh/strings.xml
Normal file
@@ -0,0 +1,316 @@
|
||||
<resources>
|
||||
<!-- Onboarding -->
|
||||
<string name="continue_text">继续</string>
|
||||
<string name="skip_text">跳过</string>
|
||||
<string name="get_start_text">开始使用</string>
|
||||
|
||||
<string name="onboarding_welcome_title">欢迎使用 TaskTTL</string>
|
||||
<string name="onboarding_welcome_desc">一个简洁而强大的任务管理工具,帮助您高效管理日常任务和重要日期</string>
|
||||
<string name="onboarding_smart_title">智能任务管理</string>
|
||||
<string name="onboarding_smart_desc">创建、分类和跟踪您的任务。设置优先级,添加截止日期,让工作更有条理</string>
|
||||
<string name="onboarding_dates_title">重要日期提醒</string>
|
||||
<string name="onboarding_dates_desc">设置重要日期的倒数计时,永远不会错过生日、纪念日或重要的截止日期</string>
|
||||
<string name="onboarding_ready_title">准备就绪!</string>
|
||||
<string name="onboarding_ready_desc">现在您可以开始创建第一个任务,让我们一起提高工作效率吧!</string>
|
||||
|
||||
<!-- 应用信息 -->
|
||||
<string name="app_name">TaskTTL</string>
|
||||
<string name="app_name_description">任务管理与倒数日应用</string>
|
||||
<string name="app_name_remark">让每一天都更有意义</string>
|
||||
<string name="version">版本</string>
|
||||
<string name="build_version">构建版本</string>
|
||||
|
||||
<!-- 导航栏 -->
|
||||
<string name="nav_todo">待办</string>
|
||||
<string name="nav_countdown">倒数日</string>
|
||||
<string name="nav_statistics">统计</string>
|
||||
<string name="nav_settings">设置</string>
|
||||
|
||||
<!-- 任务模块 -->
|
||||
<string name="search_placeholder">搜索任务...</string>
|
||||
<string name="title_task">我的任务</string>
|
||||
<string name="title_task_info">任务详情</string>
|
||||
<string name="title_add_task">添加任务</string>
|
||||
<string name="title_edit_task">编辑任务</string>
|
||||
|
||||
<string name="label_task_list">任务列表</string>
|
||||
<string name="label_show_completed">显示已完成</string>
|
||||
<string name="text_no_tasks">暂无任务</string>
|
||||
<string name="text_add_task_hint">点击右下角按钮添加新任务</string>
|
||||
<string name="desc_completed">已完成</string>
|
||||
<string name="desc_incomplete">未完成</string>
|
||||
|
||||
<string name="title_task_title">任务标题</string>
|
||||
<string name="title_task_description">任务描述</string>
|
||||
<string name="title_select_category">选择分类</string>
|
||||
<string name="title_priority">优先级</string>
|
||||
<string name="title_due_date">截止日期(可选)</string>
|
||||
<string name="desc_select_date">选择日期</string>
|
||||
<string name="title_tags">标签(用逗号分隔)</string>
|
||||
<string name="hint_tags">例如:重要,紧急,工作</string>
|
||||
|
||||
<string name="text_task_not_found">任务不存在</string>
|
||||
<string name="label_due_date">截止日期:</string>
|
||||
<string name="label_none">无</string>
|
||||
<string name="label_created_at">创建时间:</string>
|
||||
<string name="label_description">任务描述</string>
|
||||
|
||||
<string name="task_add_success">任务添加成功</string>
|
||||
<string name="task_add_failed">添加任务失败</string>
|
||||
<string name="task_update_success">任务更新成功</string>
|
||||
<string name="task_update_failed">更新任务失败</string>
|
||||
<string name="task_delete_success">任务删除成功</string>
|
||||
<string name="task_delete_failed">删除任务失败</string>
|
||||
<string name="task_load_failed">加载任务失败</string>
|
||||
<string name="task_query_failed">查询任务失败</string>
|
||||
<string name="task_status_update_success">更新任务状态成功</string>
|
||||
<string name="task_status_update_failed">更新任务状态失败</string>
|
||||
|
||||
<!-- 倒数日模块 -->
|
||||
<string name="title_countdown">倒数日</string>
|
||||
<string name="title_countdown_info">倒数日详情</string>
|
||||
<string name="title_add_countdown">添加倒数日</string>
|
||||
<string name="title_edit_countdown">编辑倒数日</string>
|
||||
|
||||
<string name="label_countdown_list">倒数日列表</string>
|
||||
<string name="label_days">天</string>
|
||||
<string name="text_no_countdowns">暂无倒数日</string>
|
||||
<string name="text_add_countdown_tip">点击右下角按钮添加新的倒数日</string>
|
||||
<string name="desc_add_countdown">添加倒数日</string>
|
||||
|
||||
<string name="label_countdown_title">倒数日标题</string>
|
||||
<string name="label_countdown_description">倒数日描述</string>
|
||||
<string name="label_target_date">目标日期</string>
|
||||
<string name="label_notification_setting">通知设置</string>
|
||||
<string name="countdown_not_found">倒数日不存在</string>
|
||||
<string name="event_description">事件描述</string>
|
||||
<string name="detail_information">详细信息</string>
|
||||
<string name="reminder">提醒</string>
|
||||
|
||||
<string name="countdown_add_success">倒数日添加成功</string>
|
||||
<string name="countdown_add_failed">添加倒数日失败</string>
|
||||
<string name="countdown_update_success">倒数日更新成功</string>
|
||||
<string name="countdown_update_failed">更新倒数日失败</string>
|
||||
<string name="countdown_delete_success">倒数日删除成功</string>
|
||||
<string name="countdown_delete_failed">删除倒数日失败</string>
|
||||
<string name="countdown_load_failed">加载倒数日失败</string>
|
||||
<string name="countdown_query_failed">查询倒数日失败</string>
|
||||
|
||||
<!-- 统计模块 -->
|
||||
<string name="title_statistics">统计</string>
|
||||
<string name="overview">总览</string>
|
||||
<string name="category_statistics">分类统计</string>
|
||||
<string name="total_tasks">总任务</string>
|
||||
<string name="completed">已完成</string>
|
||||
<string name="completion_rate">完成率</string>
|
||||
<string name="total_countdowns">倒数日总数</string>
|
||||
<string name="active">活跃中</string>
|
||||
|
||||
<!-- 分类模块 -->
|
||||
<string name="category_task">任务</string>
|
||||
<string name="category_countdown">倒数日</string>
|
||||
|
||||
<string name="title_category">分类管理</string>
|
||||
<string name="title_add_category">添加分类</string>
|
||||
<string name="title_edit_category">编辑分类</string>
|
||||
<string name="label_no_category">暂无分类</string>
|
||||
<string name="label_add_category_hint">点击右下角按钮添加新分类</string>
|
||||
<string name="label_category_name">分类名称</string>
|
||||
<string name="placeholder_category_name">输入分类名称...</string>
|
||||
<string name="label_category_type">分类类型</string>
|
||||
<string name="label_select_color">选择颜色</string>
|
||||
<string name="label_select_icon">选择图标</string>
|
||||
<string name="label_task_category">任务分类</string>
|
||||
<string name="label_countdown_category">倒数日分类</string>
|
||||
|
||||
<string name="label_task_count">%1$d 个任务</string>
|
||||
<string name="label_countdown_count">%1$d 个倒数日</string>
|
||||
|
||||
<!-- 分类操作反馈 -->
|
||||
<string name="category_add_success">分类添加成功</string>
|
||||
<string name="category_add_failed">添加分类失败</string>
|
||||
<string name="category_update_success">分类更新成功</string>
|
||||
<string name="category_update_failed">更新分类失败</string>
|
||||
<string name="category_delete_success">分类删除成功</string>
|
||||
<string name="category_delete_failed">删除分类失败</string>
|
||||
<string name="category_load_failed">加载分类失败</string>
|
||||
<string name="category_stat_failed">加载统计数据失败</string>
|
||||
<string name="category_init_success">默认分类初始化成功</string>
|
||||
<string name="category_init_failed">初始化默认分类失败</string>
|
||||
<string name="category_not_found">查询分类失败</string>
|
||||
<string name="category_count_update_failed">更新分类计数失败</string>
|
||||
|
||||
<!-- 通用操作 -->
|
||||
<string name="enter">进入</string>
|
||||
<string name="edit">编辑</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="confirm">确定</string>
|
||||
<string name="delete">删除</string>
|
||||
<string name="export">导出</string>
|
||||
<string name="import">导入</string>
|
||||
<string name="back">返回</string>
|
||||
<string name="action">操作</string>
|
||||
<string name="search">搜索</string>
|
||||
<string name="clear_text">清除</string>
|
||||
<string name="all_text">全部</string>
|
||||
<string name="retry">重试</string>
|
||||
<string name="choose_file">选择文件</string>
|
||||
<string name="error_title">错误</string>
|
||||
<string name="loading">正在加载...</string>
|
||||
<string name="webview_loading_error">加载失败,请检查网络连接</string>
|
||||
|
||||
|
||||
<!-- 数据管理 -->
|
||||
<string name="title_data_management">数据管理</string>
|
||||
<string name="title_export_data">导出数据</string>
|
||||
<string name="desc_export_data">将所有任务和倒数日导出为文件</string>
|
||||
<string name="title_import_data">导入数据</string>
|
||||
<string name="desc_import_data">从文件导入任务和倒数日</string>
|
||||
<string name="title_auto_backup">自动备份</string>
|
||||
<string name="desc_auto_backup">定期自动备份数据到云端</string>
|
||||
<string name="title_clear_all_data">清除所有数据</string>
|
||||
<string name="desc_clear_all_data">删除所有任务、倒数日和设置</string>
|
||||
<string name="desc_clear_all_data_dialog">此操作将删除所有任务、倒数日和设置,且无法恢复。</string>
|
||||
<string name="title_clear_completed_tasks">清理已完成任务</string>
|
||||
<string name="title_clear_expired_countdowns">清理过期倒数日</string>
|
||||
<string name="desc_clear_completed_tasks">删除所有已完成的任务</string>
|
||||
<string name="desc_clear_expired_countdowns">删除所有已过期的倒数日</string>
|
||||
|
||||
<string name="title_backup_restore">备份与恢复</string>
|
||||
<string name="title_data_clean">数据清理</string>
|
||||
<string name="label_select_import_file">选择要导入的文件</string>
|
||||
<string name="label_select_file">选择文件</string>
|
||||
<string name="label_select_export_format">选择导出格式</string>
|
||||
<string name="label_json_format">JSON格式</string>
|
||||
<string name="label_csv_format">CSV格式</string>
|
||||
|
||||
<!-- 设置模块 -->
|
||||
<string name="title_app_settings">应用设置</string>
|
||||
<string name="section_general_settings">通用设置</string>
|
||||
<string name="section_data_management">数据管理</string>
|
||||
<string name="section_social_share">社交分享</string>
|
||||
<string name="section_help_feedback">帮助与反馈</string>
|
||||
|
||||
<string name="setting_push_notification">推送通知</string>
|
||||
<string name="setting_push_notification_desc">接收任务和倒数日提醒</string>
|
||||
<string name="setting_dark_mode">深色模式</string>
|
||||
<string name="setting_dark_mode_desc">使用深色主题</string>
|
||||
<string name="setting_language">语言设置</string>
|
||||
<string name="setting_language_desc">简体中文</string>
|
||||
|
||||
<string name="setting_category_management">分类管理</string>
|
||||
<string name="setting_category_management_desc">管理分类</string>
|
||||
<string name="setting_data_management">数据管理</string>
|
||||
<string name="setting_data_management_desc">备份和恢复数据</string>
|
||||
|
||||
<string name="setting_share_achievement">分享成就</string>
|
||||
<string name="setting_share_achievement_desc">分享任务完成成就</string>
|
||||
<string name="setting_invite_friend">推荐给朋友</string>
|
||||
<string name="setting_invite_friend_desc">邀请朋友使用 TaskMaster</string>
|
||||
|
||||
<string name="setting_feedback">意见反馈</string>
|
||||
<string name="setting_feedback_desc">告诉我们您的想法</string>
|
||||
<string name="setting_privacy_policy">隐私政策</string>
|
||||
<string name="setting_privacy_policy_desc">了解我们如何保护您的数据</string>
|
||||
<string name="setting_privacy_rate">应用评价</string>
|
||||
<string name="setting_privacy_rate_desc">如果喜欢,欢迎在商店留下五星好评</string>
|
||||
<string name="setting_about_app">关于应用</string>
|
||||
<string name="app_version_code">100001</string>
|
||||
<string name="app_version_name">1.0.1</string>
|
||||
<string name="setting_about_app_desc">版本 1.0.1</string>
|
||||
|
||||
<!-- 反馈与帮助 -->
|
||||
<string name="title_feedback">意见反馈</string>
|
||||
<string name="feedback_type">反馈类型</string>
|
||||
<string name="feedback_issue">问题反馈</string>
|
||||
<string name="feedback_suggestion">功能建议</string>
|
||||
<string name="feedback_description">问题描述</string>
|
||||
<string name="feedback_placeholder">请详细描述您遇到的问题或建议...</string>
|
||||
<string name="feedback_contact">联系方式(可选)</string>
|
||||
<string name="feedback_contact_placeholder">您的邮箱地址,方便我们回复</string>
|
||||
<string name="feedback_submitted">感谢您的反馈!我们会尽快处理。</string>
|
||||
<string name="feedback_error_empty">请填写反馈内容</string>
|
||||
<string name="button_send_feedback">发送反馈</string>
|
||||
|
||||
<!-- 关于页面 -->
|
||||
<string name="title_about">关于</string>
|
||||
<string name="app_intro_title">应用介绍</string>
|
||||
<string name="app_intro_content">TaskTTL 是一款现代化的任务管理与倒数日应用,\n支持分类管理、优先级设置与统计分析,让生活更有条理。</string>
|
||||
|
||||
<string name="title_privacy">隐私协议</string>
|
||||
|
||||
<string name="tech_stack">技术栈</string>
|
||||
<string name="tech_stack_kmp">Kotlin Multiplatform(跨平台开发框架)</string>
|
||||
<string name="tech_stack_compose">Jetpack Compose(现代化 UI 框架)</string>
|
||||
<string name="tech_stack_room">Room Database(本地存储)</string>
|
||||
<string name="tech_stack_koin">Koin(依赖注入框架)</string>
|
||||
<string name="tech_stack_ktor">Ktor(网络请求)</string>
|
||||
<string name="tech_stack_mvi">MVI Architecture(响应式架构模式)</string>
|
||||
|
||||
<string name="developer_text">开发者</string>
|
||||
<string name="devttl_team">DevTTL 团队</string>
|
||||
<string name="contact_us">联系我们</string>
|
||||
<string name="email_text">电子邮箱</string>
|
||||
<string name="setting_privacy_email_uri">mailto:%1$s</string>
|
||||
<string name="email">admin@devttl.com</string>
|
||||
<string name="web_text">官方网站</string>
|
||||
<string name="web_url">https://devttl.com</string>
|
||||
<string name="copyright_year">2025</string>
|
||||
<string name="all_rights_reserved">保留所有权利</string>
|
||||
|
||||
<string name="priority_low">低</string>
|
||||
<string name="priority_medium">中</string>
|
||||
<string name="priority_high">高</string>
|
||||
<string name="priority_urgent">紧急</string>
|
||||
|
||||
<string name="reminder_once">一次</string>
|
||||
<string name="reminder_daily">每天</string>
|
||||
<string name="reminder_weekly">每周</string>
|
||||
<string name="reminder_monthly">每月</string>
|
||||
<string name="reminder_off">关闭</string>
|
||||
|
||||
<!-- 工作与学习 -->
|
||||
<string name="category_briefcase">工作</string>
|
||||
<string name="category_book">学习</string>
|
||||
<string name="category_exam">考试</string>
|
||||
<string name="category_project">项目</string>
|
||||
|
||||
<!-- 生活与家庭 -->
|
||||
<string name="category_home">家庭</string>
|
||||
<string name="category_coffee">休闲</string>
|
||||
<string name="category_shopping">购物</string>
|
||||
<string name="category_food">美食</string>
|
||||
<string name="category_cleaning">家务</string>
|
||||
|
||||
<!-- 健康与运动 -->
|
||||
<string name="category_heart">健康</string>
|
||||
<string name="category_dumbbell">健身</string>
|
||||
<string name="category_sleep">作息</string>
|
||||
|
||||
<!-- 娱乐与兴趣 -->
|
||||
<string name="category_music">音乐</string>
|
||||
<string name="category_gamepad">娱乐</string>
|
||||
<string name="category_camera">摄影</string>
|
||||
<string name="category_movie">影视</string>
|
||||
|
||||
<!-- 出行与旅行 -->
|
||||
<string name="category_car">出行</string>
|
||||
<string name="category_plane">旅行</string>
|
||||
<string name="category_walk">步行</string>
|
||||
|
||||
<!-- 节日与纪念 -->
|
||||
<string name="category_birthday">生日</string>
|
||||
<string name="category_festival">节日</string>
|
||||
<string name="category_anniversary">纪念日</string>
|
||||
|
||||
<!-- 财务与计划 -->
|
||||
<string name="category_money">理财</string>
|
||||
<string name="category_goal">目标</string>
|
||||
<string name="category_reminder">提醒</string>
|
||||
|
||||
<string name="privacy_url">https://sites.google.com/view/taskttl/privacy</string>
|
||||
|
||||
<string name="feedback_success">反馈成功</string>
|
||||
<string name="feedback_error">反馈失败,请检查网络连接或稍后重试</string>
|
||||
|
||||
</resources>
|
@@ -0,0 +1,97 @@
|
||||
package com.taskttl.core.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* 基础视图模型
|
||||
* @author DevTTL
|
||||
* @date 2025/10/15
|
||||
* @constructor 创建[BaseViewModel]
|
||||
* @param [initialState] 初始状态
|
||||
*/
|
||||
abstract class BaseViewModel<S : BaseUiState, I, E>(initialState: S) : ViewModel() {
|
||||
|
||||
// 状态流
|
||||
private val _state = MutableStateFlow(initialState)
|
||||
val state: StateFlow<S> = _state.asStateFlow()
|
||||
|
||||
// 事件流
|
||||
private val _effects = MutableSharedFlow<E>()
|
||||
val effects: SharedFlow<E> = _effects.asSharedFlow()
|
||||
|
||||
// 对外入口处理 Intent
|
||||
fun processIntent(intent: I) {
|
||||
viewModelScope.launch { handleIntent(intent) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理意图的具体实现 - 子类必须实现此方法
|
||||
* @param intent 意图
|
||||
*/
|
||||
protected abstract fun handleIntent(intent: I)
|
||||
|
||||
|
||||
// private fun launchWithProcessing(
|
||||
// showLoading: Boolean = true,
|
||||
// block: suspend () -> Unit,
|
||||
// ) {
|
||||
// viewModelScope.launch {
|
||||
// val currentState = _state.value
|
||||
// if (currentState.isProcessing) return@launch // 防重入
|
||||
//
|
||||
// // 设置状态
|
||||
// updateState { copy(isLoading = showLoading, isProcessing = true) }
|
||||
// try {
|
||||
// block()
|
||||
// } finally {
|
||||
// updateState { copy(isLoading = false, isProcessing = false) }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* 发送单向事件
|
||||
*/
|
||||
protected fun sendEvent(event: E) {
|
||||
viewModelScope.launch { _effects.emit(event) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新状态
|
||||
*/
|
||||
protected fun updateState(reduce: S.() -> S) {
|
||||
viewModelScope.launch { _state.update { it.reduce() } }
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 清除错误
|
||||
// */
|
||||
// protected fun clearError() {
|
||||
// _state.value = _state.value.copy(error = null)
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 基本ui状态
|
||||
* @author DevTTL
|
||||
* @date 2025/10/15
|
||||
* @constructor 创建[BaseUiState]
|
||||
* @param [isLoading] 正在加载
|
||||
* @param [isProcessing] 正在处理
|
||||
* @param [error] 错误
|
||||
*/
|
||||
open class BaseUiState(
|
||||
open val isLoading: Boolean = false,
|
||||
open val isProcessing: Boolean = false,
|
||||
open val error: String? = null,
|
||||
)
|
@@ -1,5 +1,6 @@
|
||||
package com.taskttl.data.state
|
||||
|
||||
import com.taskttl.core.viewmodel.BaseUiState
|
||||
import com.taskttl.data.local.model.Category
|
||||
import com.taskttl.data.local.model.CategoryStatistics
|
||||
import com.taskttl.data.local.model.CategoryType
|
||||
@@ -23,6 +24,9 @@ import com.taskttl.data.local.model.CategoryType
|
||||
* @param [showDeleteDialog] 显示删除对话
|
||||
*/
|
||||
data class CategoryState(
|
||||
override val isLoading: Boolean = false,
|
||||
override val isProcessing: Boolean = false,
|
||||
override val error: String? = null,
|
||||
val categories: List<Category> = emptyList(),
|
||||
val editingCategory: Category? = null,
|
||||
val taskCategories: List<Category> = emptyList(),
|
||||
@@ -30,12 +34,10 @@ data class CategoryState(
|
||||
val categoryStatistics: List<CategoryStatistics> = emptyList(),
|
||||
val selectedCategory: Category? = null,
|
||||
val selectedType: CategoryType = CategoryType.TASK,
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null,
|
||||
val showAddDialog: Boolean = false,
|
||||
val showEditDialog: Boolean = false,
|
||||
val showDeleteDialog: Boolean = false
|
||||
)
|
||||
): BaseUiState()
|
||||
|
||||
/**
|
||||
* 类别意图
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package com.taskttl.data.state
|
||||
|
||||
import com.taskttl.core.viewmodel.BaseUiState
|
||||
import com.taskttl.data.local.model.Category
|
||||
import com.taskttl.data.local.model.Countdown
|
||||
|
||||
@@ -17,14 +18,15 @@ import com.taskttl.data.local.model.Countdown
|
||||
* @param [error] 错误
|
||||
*/
|
||||
data class CountdownState(
|
||||
override val isLoading: Boolean = false,
|
||||
override val isProcessing: Boolean = false,
|
||||
override val error: String? = null,
|
||||
val countdowns: List<Countdown> = emptyList(),
|
||||
val categories: List<Category> = emptyList(),
|
||||
val editingCountdown: Countdown? = null,
|
||||
val filteredCountdowns: List<Countdown> = emptyList(),
|
||||
val selectedCategory: Category? = null,
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null
|
||||
)
|
||||
): BaseUiState()
|
||||
|
||||
/**
|
||||
* 倒数日意图
|
||||
|
@@ -1,11 +1,13 @@
|
||||
package com.taskttl.data.state
|
||||
|
||||
import com.taskttl.core.viewmodel.BaseUiState
|
||||
import com.taskttl.data.network.domain.req.FeedbackReq
|
||||
|
||||
data class FeedbackState(
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null
|
||||
)
|
||||
override val isLoading: Boolean = false,
|
||||
override val isProcessing: Boolean = false,
|
||||
override val error: String? = null,
|
||||
) : BaseUiState()
|
||||
|
||||
sealed class FeedbackIntent {
|
||||
/**
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package com.taskttl.data.state
|
||||
|
||||
import com.taskttl.core.viewmodel.BaseUiState
|
||||
import com.taskttl.data.local.model.OnboardingPage
|
||||
|
||||
/**
|
||||
@@ -9,9 +10,11 @@ import com.taskttl.data.local.model.OnboardingPage
|
||||
* @constructor 创建[OnboardingState]
|
||||
*/
|
||||
data class OnboardingState(
|
||||
val isLoading: Boolean = false,
|
||||
val pages: List<OnboardingPage>
|
||||
)
|
||||
override val isLoading: Boolean = false,
|
||||
override val isProcessing: Boolean = false,
|
||||
override val error: String? = null,
|
||||
val pages: List<OnboardingPage> = OnboardingPage.entries,
|
||||
) : BaseUiState()
|
||||
|
||||
/**
|
||||
* 引导意图
|
||||
@@ -19,26 +22,29 @@ data class OnboardingState(
|
||||
* @date 2025/09/06
|
||||
* @constructor 创建[OnboardingIntent]
|
||||
*/
|
||||
sealed class OnboardingIntent {}
|
||||
sealed class OnboardingIntent {
|
||||
object NextPage : OnboardingIntent()
|
||||
object MarkOnboardingCompleted : OnboardingIntent()
|
||||
}
|
||||
|
||||
/**
|
||||
* 引导活动
|
||||
* @author DevTTL
|
||||
* @date 2025/09/06
|
||||
* @constructor 创建[OnboardingEvent]
|
||||
* @constructor 创建[OnboardingEffect]
|
||||
*/
|
||||
sealed class OnboardingEvent {
|
||||
sealed class OnboardingEffect {
|
||||
/**
|
||||
* 下一页
|
||||
* @author DevTTL
|
||||
* @date 2025/09/06
|
||||
*/
|
||||
data object NextPage : OnboardingEvent()
|
||||
data object NextPage : OnboardingEffect()
|
||||
|
||||
/**
|
||||
* 导航Main
|
||||
* @author admin
|
||||
* @date 2025/10/05
|
||||
*/
|
||||
data object NavMain : OnboardingEvent()
|
||||
data object NavMain : OnboardingEffect()
|
||||
}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
package com.taskttl.data.state
|
||||
|
||||
import com.taskttl.core.viewmodel.BaseUiState
|
||||
|
||||
/**
|
||||
* 设置状态
|
||||
* @author DevTTL
|
||||
@@ -9,9 +11,10 @@ package com.taskttl.data.state
|
||||
* @param [error] 错误
|
||||
*/
|
||||
data class SettingsState(
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null,
|
||||
)
|
||||
override val isLoading: Boolean = false,
|
||||
override val isProcessing: Boolean = false,
|
||||
override val error: String? = null,
|
||||
) : BaseUiState()
|
||||
|
||||
/**
|
||||
* 设置意图
|
||||
@@ -25,7 +28,7 @@ sealed class SettingsIntent {
|
||||
* @author DevTTL
|
||||
* @date 2025/10/14
|
||||
*/
|
||||
object OpenAppRating: SettingsIntent()
|
||||
object OpenAppRating : SettingsIntent()
|
||||
|
||||
/**
|
||||
* 打开网址
|
||||
@@ -34,7 +37,7 @@ sealed class SettingsIntent {
|
||||
* @constructor 创建[OpenUrl]
|
||||
* @param [url] 网址
|
||||
*/
|
||||
class OpenUrl(val url:String): SettingsIntent()
|
||||
class OpenUrl(val url: String) : SettingsIntent()
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1,30 +1,37 @@
|
||||
package com.taskttl.data.state
|
||||
|
||||
import com.taskttl.core.viewmodel.BaseUiState
|
||||
|
||||
/**
|
||||
* 启动页状态
|
||||
* @author admin
|
||||
* @date 2025/10/05
|
||||
* @constructor 创建[SplashState]
|
||||
*/
|
||||
sealed interface SplashState {
|
||||
/**
|
||||
* 加载中
|
||||
* @author admin
|
||||
* @date 2025/08/11
|
||||
*/
|
||||
data object Loading : SplashState
|
||||
data class SplashState(
|
||||
override val isLoading: Boolean = false,
|
||||
override val isProcessing: Boolean = false,
|
||||
override val error: String? = null,
|
||||
) : BaseUiState()
|
||||
|
||||
sealed class SplashIntent {
|
||||
object LoadApp : SplashIntent()
|
||||
}
|
||||
|
||||
|
||||
sealed class SplashEffect {
|
||||
|
||||
/**
|
||||
* 导航到首页
|
||||
* @author admin
|
||||
* @date 2025/08/11
|
||||
*/
|
||||
data object NavigateToMain : SplashState
|
||||
data object NavigateToMain : SplashEffect()
|
||||
|
||||
/**
|
||||
* 导航到引导页
|
||||
* @author admin
|
||||
* @date 2025/08/11
|
||||
*/
|
||||
data object NavigateToOnboarding : SplashState
|
||||
data object NavigateToOnboarding : SplashEffect()
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
package com.taskttl.data.state
|
||||
|
||||
import com.taskttl.core.viewmodel.BaseUiState
|
||||
import com.taskttl.data.local.model.Category
|
||||
import com.taskttl.data.local.model.Task
|
||||
|
||||
@@ -18,6 +19,9 @@ import com.taskttl.data.local.model.Task
|
||||
* @param [showCompleted] 显示已完成
|
||||
*/
|
||||
data class TaskState(
|
||||
override val isLoading: Boolean = false,
|
||||
override val isProcessing: Boolean = false,
|
||||
override val error: String? = null,
|
||||
val tasks: List<Task> = emptyList(),
|
||||
val categories: List<Category> = emptyList(),
|
||||
val editingTask: Task? = null,
|
||||
@@ -25,10 +29,8 @@ data class TaskState(
|
||||
val selectedCategory: Category? = null,
|
||||
val isSearch: Boolean = false,
|
||||
val searchQuery: String = "",
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null,
|
||||
val showCompleted: Boolean = false
|
||||
)
|
||||
): BaseUiState()
|
||||
|
||||
/**
|
||||
* 任务意图
|
||||
|
@@ -1,19 +1,13 @@
|
||||
package com.taskttl.data.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.taskttl.core.viewmodel.BaseViewModel
|
||||
import com.taskttl.data.local.model.Category
|
||||
import com.taskttl.data.local.model.CategoryType
|
||||
import com.taskttl.data.repository.CategoryRepository
|
||||
import com.taskttl.data.state.CategoryEffect
|
||||
import com.taskttl.data.state.CategoryIntent
|
||||
import com.taskttl.data.state.CategoryState
|
||||
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
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import taskttl.composeapp.generated.resources.Res
|
||||
@@ -37,20 +31,15 @@ import taskttl.composeapp.generated.resources.category_update_success
|
||||
* @constructor 创建[CategoryViewModel]
|
||||
* @param [categoryRepository] 类别存储库
|
||||
*/
|
||||
class CategoryViewModel(private val categoryRepository: CategoryRepository) : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow(CategoryState())
|
||||
val state: StateFlow<CategoryState> = _state.asStateFlow()
|
||||
|
||||
private val _effects = MutableSharedFlow<CategoryEffect>()
|
||||
val effects: SharedFlow<CategoryEffect> = _effects.asSharedFlow()
|
||||
class CategoryViewModel(private val categoryRepository: CategoryRepository) :
|
||||
BaseViewModel<CategoryState, CategoryIntent, CategoryEffect>(CategoryState()) {
|
||||
|
||||
init {
|
||||
handleIntent(CategoryIntent.LoadCategories)
|
||||
handleIntent(CategoryIntent.LoadCategoryStatistics)
|
||||
processIntent(CategoryIntent.LoadCategories)
|
||||
processIntent(CategoryIntent.LoadCategoryStatistics)
|
||||
}
|
||||
|
||||
fun handleIntent(intent: CategoryIntent) {
|
||||
public override fun handleIntent(intent: CategoryIntent) {
|
||||
when (intent) {
|
||||
is CategoryIntent.LoadCategories -> loadCategories()
|
||||
is CategoryIntent.LoadCategoriesByType -> loadCategoriesByType(intent.type)
|
||||
@@ -78,25 +67,25 @@ class CategoryViewModel(private val categoryRepository: CategoryRepository) : Vi
|
||||
*/
|
||||
private fun loadCategories() {
|
||||
viewModelScope.launch {
|
||||
_state.value = _state.value.copy(isLoading = true, error = null)
|
||||
updateState { copy(isLoading = true, error = null) }
|
||||
try {
|
||||
categoryRepository.getAllCategories().collect { categories ->
|
||||
val taskCategories = categories.filter { it.type == CategoryType.TASK }
|
||||
val countdownCategories =
|
||||
categories.filter { it.type == CategoryType.COUNTDOWN }
|
||||
|
||||
_state.value = _state.value.copy(
|
||||
categories = categories,
|
||||
taskCategories = taskCategories,
|
||||
countdownCategories = countdownCategories,
|
||||
isLoading = false
|
||||
)
|
||||
updateState {
|
||||
copy(
|
||||
categories = categories,
|
||||
taskCategories = taskCategories,
|
||||
countdownCategories = countdownCategories,
|
||||
isLoading = false
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_state.value = _state.value.copy(
|
||||
isLoading = false,
|
||||
error = e.message ?: getString(Res.string.category_load_failed)
|
||||
)
|
||||
val errStr = getString(Res.string.category_load_failed)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,10 +98,10 @@ class CategoryViewModel(private val categoryRepository: CategoryRepository) : Vi
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val category = categoryRepository.getCategoryById(categoryId)
|
||||
_state.value = _state.value.copy(editingCategory = category)
|
||||
updateState { copy(editingCategory = category) }
|
||||
} catch (e: Exception) {
|
||||
_state.value =
|
||||
_state.value.copy(error = e.message ?: getString(Res.string.category_not_found))
|
||||
val errStr = getString(Res.string.category_not_found)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,18 +116,17 @@ class CategoryViewModel(private val categoryRepository: CategoryRepository) : Vi
|
||||
categoryRepository.getCategoriesByType(type).collect { categories ->
|
||||
when (type) {
|
||||
CategoryType.TASK -> {
|
||||
_state.value = _state.value.copy(taskCategories = categories)
|
||||
updateState { copy(taskCategories = categories) }
|
||||
}
|
||||
|
||||
CategoryType.COUNTDOWN -> {
|
||||
_state.value = _state.value.copy(countdownCategories = categories)
|
||||
updateState { copy(countdownCategories = categories) }
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_state.value = _state.value.copy(
|
||||
error = e.message ?: getString(Res.string.category_load_failed)
|
||||
)
|
||||
val errStr = getString(Res.string.category_load_failed)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,22 +138,21 @@ class CategoryViewModel(private val categoryRepository: CategoryRepository) : Vi
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
categoryRepository.getCategoryStatistics().collect { statistics ->
|
||||
_state.value = _state.value.copy(categoryStatistics = statistics)
|
||||
updateState { copy(categoryStatistics = statistics) }
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_state.value = _state.value.copy(
|
||||
error = e.message ?: getString(Res.string.category_stat_failed)
|
||||
)
|
||||
val errStr = getString(Res.string.category_stat_failed)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectCategory(category: Category?) {
|
||||
_state.value = _state.value.copy(selectedCategory = category)
|
||||
updateState { copy(selectedCategory = category) }
|
||||
}
|
||||
|
||||
private fun selectType(type: CategoryType) {
|
||||
_state.value = _state.value.copy(selectedType = type)
|
||||
updateState { copy(selectedType = type) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,12 +163,11 @@ class CategoryViewModel(private val categoryRepository: CategoryRepository) : Vi
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
categoryRepository.insertCategory(category)
|
||||
_effects.emit(CategoryEffect.ShowMessage(getString(Res.string.category_add_success)))
|
||||
_effects.emit(CategoryEffect.NavigateBack)
|
||||
sendEvent(CategoryEffect.ShowMessage(getString(Res.string.category_add_success)))
|
||||
sendEvent(CategoryEffect.NavigateBack)
|
||||
} catch (e: Exception) {
|
||||
_state.value = _state.value.copy(
|
||||
error = e.message ?: getString(Res.string.category_add_failed)
|
||||
)
|
||||
val errStr = getString(Res.string.category_add_failed)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,13 +176,12 @@ class CategoryViewModel(private val categoryRepository: CategoryRepository) : Vi
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
categoryRepository.updateCategory(category)
|
||||
_effects.emit(CategoryEffect.ShowMessage(getString(Res.string.category_update_success)))
|
||||
_effects.emit(CategoryEffect.NavigateBack)
|
||||
sendEvent(CategoryEffect.ShowMessage(getString(Res.string.category_update_success)))
|
||||
sendEvent(CategoryEffect.NavigateBack)
|
||||
hideEditDialog()
|
||||
} catch (e: Exception) {
|
||||
_state.value = _state.value.copy(
|
||||
error = e.message ?: getString(Res.string.category_update_failed)
|
||||
)
|
||||
val errStr = getString(Res.string.category_update_failed)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,12 +190,11 @@ class CategoryViewModel(private val categoryRepository: CategoryRepository) : Vi
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
categoryRepository.deleteCategory(categoryId)
|
||||
_effects.emit(CategoryEffect.ShowMessage(getString(Res.string.category_delete_success)))
|
||||
sendEvent(CategoryEffect.ShowMessage(getString(Res.string.category_delete_success)))
|
||||
hideDeleteDialog()
|
||||
} catch (e: Exception) {
|
||||
_state.value = _state.value.copy(
|
||||
error = e.message ?: getString(Res.string.category_delete_failed)
|
||||
)
|
||||
val errStr = getString(Res.string.category_delete_failed)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,11 +203,10 @@ class CategoryViewModel(private val categoryRepository: CategoryRepository) : Vi
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
categoryRepository.initializeDefaultCategories()
|
||||
_effects.emit(CategoryEffect.ShowMessage(getString(Res.string.category_init_success)))
|
||||
sendEvent(CategoryEffect.ShowMessage(getString(Res.string.category_init_success)))
|
||||
} catch (e: Exception) {
|
||||
_state.value = _state.value.copy(
|
||||
error = e.message ?: getString(Res.string.category_init_failed)
|
||||
)
|
||||
val errStr = getString(Res.string.category_init_failed)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,50 +216,37 @@ class CategoryViewModel(private val categoryRepository: CategoryRepository) : Vi
|
||||
try {
|
||||
categoryRepository.updateCategoryCounts()
|
||||
} catch (e: Exception) {
|
||||
_state.value = _state.value.copy(
|
||||
error = e.message ?: getString(Res.string.category_count_update_failed)
|
||||
)
|
||||
val errStr = getString(Res.string.category_count_update_failed)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showAddDialog() {
|
||||
_state.value = _state.value.copy(showAddDialog = true)
|
||||
updateState { copy(showAddDialog = true) }
|
||||
}
|
||||
|
||||
private fun hideAddDialog() {
|
||||
_state.value = _state.value.copy(showAddDialog = false)
|
||||
updateState { copy(showAddDialog = false) }
|
||||
}
|
||||
|
||||
private fun showEditDialog(category: Category) {
|
||||
_state.value = _state.value.copy(
|
||||
selectedCategory = category,
|
||||
showEditDialog = true
|
||||
)
|
||||
updateState { copy(selectedCategory = category, showEditDialog = true) }
|
||||
}
|
||||
|
||||
private fun hideEditDialog() {
|
||||
_state.value = _state.value.copy(
|
||||
selectedCategory = null,
|
||||
showEditDialog = false
|
||||
)
|
||||
updateState { copy(selectedCategory = null, showEditDialog = false) }
|
||||
}
|
||||
|
||||
private fun showDeleteDialog(category: Category) {
|
||||
_state.value = _state.value.copy(
|
||||
selectedCategory = category,
|
||||
showDeleteDialog = true
|
||||
)
|
||||
updateState { copy(selectedCategory = category, showDeleteDialog = true) }
|
||||
}
|
||||
|
||||
private fun hideDeleteDialog() {
|
||||
_state.value = _state.value.copy(
|
||||
selectedCategory = null,
|
||||
showDeleteDialog = false
|
||||
)
|
||||
updateState { copy(selectedCategory = null, showDeleteDialog = false) }
|
||||
}
|
||||
|
||||
private fun clearError() {
|
||||
_state.value = _state.value.copy(error = null)
|
||||
updateState { copy(error = null) }
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
package com.taskttl.data.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.taskttl.core.viewmodel.BaseViewModel
|
||||
import com.taskttl.data.local.model.Category
|
||||
import com.taskttl.data.local.model.CategoryType
|
||||
import com.taskttl.data.local.model.Countdown
|
||||
@@ -10,12 +10,6 @@ import com.taskttl.data.repository.CountdownRepository
|
||||
import com.taskttl.data.state.CountdownEffect
|
||||
import com.taskttl.data.state.CountdownIntent
|
||||
import com.taskttl.data.state.CountdownState
|
||||
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
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import taskttl.composeapp.generated.resources.Res
|
||||
@@ -37,20 +31,15 @@ import taskttl.composeapp.generated.resources.countdown_update_success
|
||||
*/
|
||||
class CountdownViewModel(
|
||||
private val countdownRepository: CountdownRepository,
|
||||
private val categoryRepository: CategoryRepository
|
||||
) : ViewModel() {
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) : BaseViewModel<CountdownState, CountdownIntent, CountdownEffect>(CountdownState()) {
|
||||
|
||||
private val _state = MutableStateFlow(CountdownState())
|
||||
val state: StateFlow<CountdownState> = _state.asStateFlow()
|
||||
|
||||
private val _effects = MutableSharedFlow<CountdownEffect>()
|
||||
val effects: SharedFlow<CountdownEffect> = _effects.asSharedFlow()
|
||||
|
||||
init {
|
||||
handleIntent(CountdownIntent.LoadCountdowns)
|
||||
processIntent(CountdownIntent.LoadCountdowns)
|
||||
}
|
||||
|
||||
fun handleIntent(intent: CountdownIntent) {
|
||||
public override fun handleIntent(intent: CountdownIntent) {
|
||||
when (intent) {
|
||||
is CountdownIntent.LoadCountdowns -> loadCountdowns()
|
||||
is CountdownIntent.GetCountdownById -> getCountdownById(intent.countdownId)
|
||||
@@ -64,29 +53,27 @@ class CountdownViewModel(
|
||||
|
||||
private fun loadCountdowns() {
|
||||
viewModelScope.launch {
|
||||
_state.value = _state.value.copy(isLoading = true, error = null)
|
||||
updateState { copy(isLoading = true) }
|
||||
try {
|
||||
launch {
|
||||
categoryRepository.getCategoriesByType(CategoryType.COUNTDOWN)
|
||||
.collect { categories ->
|
||||
_state.value = _state.value.copy(categories = categories)
|
||||
}
|
||||
.collect { categories -> updateState { copy(categories = categories) } }
|
||||
}
|
||||
launch {
|
||||
countdownRepository.getAllCountdowns().collect { countdowns ->
|
||||
_state.value = _state.value.copy(
|
||||
countdowns = countdowns,
|
||||
filteredCountdowns = filterCountdowns(countdowns),
|
||||
isLoading = false
|
||||
)
|
||||
updateState {
|
||||
copy(
|
||||
countdowns = countdowns,
|
||||
filteredCountdowns = filterCountdowns(countdowns)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_state.value =
|
||||
_state.value.copy(
|
||||
isLoading = false,
|
||||
error = e.message ?: getString(Res.string.countdown_load_failed)
|
||||
)
|
||||
val errStr = getString(Res.string.countdown_load_failed)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
} finally {
|
||||
updateState { copy(isLoading = false) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,11 +83,10 @@ class CountdownViewModel(
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val countdown = countdownRepository.getCountdownById(countdownId)
|
||||
_state.value = _state.value.copy(editingCountdown = countdown)
|
||||
updateState { copy(editingCountdown = countdown) }
|
||||
} catch (e: Exception) {
|
||||
_state.value = _state.value.copy(
|
||||
error = e.message ?: getString(Res.string.countdown_query_failed)
|
||||
)
|
||||
val errStr = getString(Res.string.countdown_query_failed)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,12 +95,11 @@ class CountdownViewModel(
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
countdownRepository.insertCountdown(countdown)
|
||||
_effects.emit(CountdownEffect.ShowMessage(getString(Res.string.countdown_add_success)))
|
||||
_effects.emit(CountdownEffect.NavigateBack)
|
||||
sendEvent(CountdownEffect.ShowMessage(getString(Res.string.countdown_add_success)))
|
||||
sendEvent(CountdownEffect.NavigateBack)
|
||||
} catch (e: Exception) {
|
||||
_state.value = _state.value.copy(
|
||||
error = e.message ?: getString(Res.string.countdown_add_failed)
|
||||
)
|
||||
val errStr = getString(Res.string.countdown_add_failed)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,11 +108,11 @@ class CountdownViewModel(
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
countdownRepository.updateCountdown(countdown)
|
||||
_effects.emit(CountdownEffect.ShowMessage(getString(Res.string.countdown_update_success)))
|
||||
sendEvent(CountdownEffect.ShowMessage(getString(Res.string.countdown_update_success)))
|
||||
sendEvent(CountdownEffect.NavigateBack)
|
||||
} catch (e: Exception) {
|
||||
_state.value = _state.value.copy(
|
||||
error = e.message ?: getString(Res.string.countdown_update_failed)
|
||||
)
|
||||
val errStr = getString(Res.string.countdown_update_failed)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,26 +121,29 @@ class CountdownViewModel(
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
countdownRepository.deleteCountdown(countdownId)
|
||||
_effects.emit(CountdownEffect.ShowMessage(getString(Res.string.countdown_delete_success)))
|
||||
sendEvent(CountdownEffect.ShowMessage(getString(Res.string.countdown_delete_success)))
|
||||
} catch (e: Exception) {
|
||||
_state.value = _state.value.copy(error = e.message ?: getString(Res.string.countdown_delete_failed))
|
||||
val errStr = getString(Res.string.countdown_delete_failed)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun filterByCategory(category: Category?) {
|
||||
_state.value = _state.value.copy(
|
||||
selectedCategory = category,
|
||||
filteredCountdowns = filterCountdowns(_state.value.countdowns)
|
||||
)
|
||||
updateState {
|
||||
copy(
|
||||
selectedCategory = category,
|
||||
filteredCountdowns = filterCountdowns(state.value.countdowns)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearError() {
|
||||
_state.value = _state.value.copy(error = null)
|
||||
updateState { copy(error = null) }
|
||||
}
|
||||
|
||||
private fun filterCountdowns(countdowns: List<Countdown>): List<Countdown> {
|
||||
val currentState = _state.value
|
||||
val currentState = state.value
|
||||
return countdowns.filter { countdown ->
|
||||
currentState.selectedCategory?.let { countdown.category == it } ?: true
|
||||
}
|
||||
|
@@ -1,19 +1,12 @@
|
||||
package com.taskttl.data.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.taskttl.core.viewmodel.BaseViewModel
|
||||
import com.taskttl.data.network.TaskTTLApi
|
||||
import com.taskttl.data.network.domain.req.FeedbackReq
|
||||
import com.taskttl.data.state.FeedbackEffect
|
||||
import com.taskttl.data.state.FeedbackIntent
|
||||
import com.taskttl.data.state.FeedbackState
|
||||
import kotlinx.coroutines.delay
|
||||
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
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import taskttl.composeapp.generated.resources.Res
|
||||
@@ -26,16 +19,11 @@ import taskttl.composeapp.generated.resources.feedback_success
|
||||
* @date 2025/10/12
|
||||
* @constructor 创建[FeedbackViewModel]
|
||||
*/
|
||||
class FeedbackViewModel() : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow(FeedbackState())
|
||||
val state: StateFlow<FeedbackState> = _state.asStateFlow()
|
||||
|
||||
private val _effects = MutableSharedFlow<FeedbackEffect>()
|
||||
val effects: SharedFlow<FeedbackEffect> = _effects.asSharedFlow()
|
||||
class FeedbackViewModel() :
|
||||
BaseViewModel<FeedbackState, FeedbackIntent, FeedbackEffect>(FeedbackState()) {
|
||||
|
||||
|
||||
fun handleIntent(intent: FeedbackIntent) {
|
||||
public override fun handleIntent(intent: FeedbackIntent) {
|
||||
when (intent) {
|
||||
is FeedbackIntent.SubmitFeedback -> submitFeedback(intent.feedback)
|
||||
is FeedbackIntent.ClearError -> clearError()
|
||||
@@ -44,20 +32,17 @@ class FeedbackViewModel() : ViewModel() {
|
||||
|
||||
private fun submitFeedback(feedback: FeedbackReq) {
|
||||
viewModelScope.launch {
|
||||
_state.value = _state.value.copy(isLoading = true, error = null)
|
||||
if (state.value.isProcessing) return@launch
|
||||
updateState { copy(isLoading = true, isProcessing = true) }
|
||||
try {
|
||||
delay(10_000)
|
||||
TaskTTLApi.postFeedback(feedback)
|
||||
_effects.emit(FeedbackEffect.ShowMessage(getString(Res.string.feedback_success)))
|
||||
_state.value = _state.value.copy(isLoading = false)
|
||||
_effects.emit(FeedbackEffect.NavigateBack)
|
||||
sendEvent(FeedbackEffect.ShowMessage(getString(Res.string.feedback_success)))
|
||||
sendEvent(FeedbackEffect.NavigateBack)
|
||||
} catch (e: Exception) {
|
||||
_state.value = _state.value.copy(isLoading = false, error = e.message)
|
||||
_effects.emit(
|
||||
FeedbackEffect.ShowMessage(
|
||||
e.message ?: getString(Res.string.feedback_error)
|
||||
)
|
||||
)
|
||||
val errStr = getString(Res.string.feedback_error)
|
||||
sendEvent(FeedbackEffect.ShowMessage(e.message ?: errStr))
|
||||
} finally {
|
||||
updateState { copy(isLoading = false, isProcessing = false) }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -67,7 +52,7 @@ class FeedbackViewModel() : ViewModel() {
|
||||
* 清除错误
|
||||
*/
|
||||
private fun clearError() {
|
||||
_state.value = _state.value.copy(error = null)
|
||||
updateState { copy(error = null) }
|
||||
}
|
||||
|
||||
}
|
@@ -1,13 +1,12 @@
|
||||
package com.taskttl.data.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.taskttl.core.viewmodel.BaseViewModel
|
||||
import com.taskttl.data.repository.CategoryRepository
|
||||
import com.taskttl.data.repository.OnboardingRepository
|
||||
import com.taskttl.data.state.OnboardingEvent
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import com.taskttl.data.state.OnboardingEffect
|
||||
import com.taskttl.data.state.OnboardingIntent
|
||||
import com.taskttl.data.state.OnboardingState
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
@@ -20,30 +19,37 @@ import kotlinx.coroutines.launch
|
||||
*/
|
||||
class OnboardingViewModel(
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
private val categoryRepository: CategoryRepository
|
||||
) : ViewModel() {
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) : BaseViewModel<OnboardingState, OnboardingIntent, OnboardingEffect>(initialState = OnboardingState()) {
|
||||
|
||||
private val _events = Channel<OnboardingEvent>()
|
||||
val events: Flow<OnboardingEvent> = _events.receiveAsFlow()
|
||||
override fun handleIntent(intent: OnboardingIntent) {
|
||||
when (intent) {
|
||||
is OnboardingIntent.NextPage -> nextPage()
|
||||
is OnboardingIntent.MarkOnboardingCompleted -> markOnboardingCompleted()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送事件 - 提供统一的事件发送机制
|
||||
* @param event 事件
|
||||
* 下一页
|
||||
*/
|
||||
fun sendEvent(event: OnboardingEvent) {
|
||||
viewModelScope.launch {
|
||||
_events.trySend(event)
|
||||
}
|
||||
private fun nextPage() {
|
||||
sendEvent(OnboardingEffect.NextPage)
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记引导完成
|
||||
*/
|
||||
fun markOnboardingCompleted() {
|
||||
private fun markOnboardingCompleted() {
|
||||
viewModelScope.launch {
|
||||
categoryRepository.initializeDefaultCategories()
|
||||
onboardingRepository.markLaunched()
|
||||
_events.trySend(OnboardingEvent.NavMain)
|
||||
try {
|
||||
if (state.value.isProcessing) return@launch
|
||||
updateState { copy(isLoading = false, isProcessing = true) }
|
||||
categoryRepository.initializeDefaultCategories()
|
||||
onboardingRepository.markLaunched()
|
||||
sendEvent(OnboardingEffect.NavMain)
|
||||
} finally {
|
||||
updateState { copy(isLoading = false, isProcessing = false) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,17 +1,11 @@
|
||||
package com.taskttl.data.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.taskttl.core.utils.ExternalAppLauncher
|
||||
import com.taskttl.core.viewmodel.BaseViewModel
|
||||
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
|
||||
|
||||
/**
|
||||
@@ -20,16 +14,11 @@ import kotlinx.coroutines.launch
|
||||
* @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()
|
||||
class SettingsViewModel() :
|
||||
BaseViewModel<SettingsState, SettingsIntent, SettingsEffect>(SettingsState()) {
|
||||
|
||||
|
||||
fun handleIntent(intent: SettingsIntent) {
|
||||
public override fun handleIntent(intent: SettingsIntent) {
|
||||
when (intent) {
|
||||
is SettingsIntent.OpenAppRating -> openAppRating()
|
||||
is SettingsIntent.OpenUrl -> openUrl(intent.url)
|
||||
@@ -42,7 +31,7 @@ class SettingsViewModel() : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun openUrl(url:String) {
|
||||
private fun openUrl(url: String) {
|
||||
viewModelScope.launch {
|
||||
ExternalAppLauncher.openUrl(url)
|
||||
}
|
||||
@@ -52,7 +41,7 @@ class SettingsViewModel() : ViewModel() {
|
||||
* 清除错误
|
||||
*/
|
||||
private fun clearError() {
|
||||
_state.value = _state.value.copy(error = null)
|
||||
updateState { copy(error = null) }
|
||||
}
|
||||
|
||||
}
|
@@ -1,17 +1,15 @@
|
||||
package com.taskttl.data.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.taskttl.core.domain.constant.PointEvent
|
||||
import com.taskttl.core.utils.DeviceUtils
|
||||
import com.taskttl.core.utils.LogUtils
|
||||
import com.taskttl.core.utils.StorageUtils
|
||||
import com.taskttl.core.viewmodel.BaseViewModel
|
||||
import com.taskttl.data.network.TaskTTLApi
|
||||
import com.taskttl.data.repository.OnboardingRepository
|
||||
import com.taskttl.data.state.SplashEffect
|
||||
import com.taskttl.data.state.SplashIntent
|
||||
import com.taskttl.data.state.SplashState
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
@@ -23,12 +21,19 @@ import kotlinx.coroutines.launch
|
||||
*/
|
||||
class SplashViewModel(
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow<SplashState>(SplashState.Loading)
|
||||
val uiState: StateFlow<SplashState> = _uiState.asStateFlow()
|
||||
) : BaseViewModel<SplashState, SplashIntent, SplashEffect>(SplashState()) {
|
||||
|
||||
init {
|
||||
processIntent(SplashIntent.LoadApp)
|
||||
}
|
||||
|
||||
override fun handleIntent(intent: SplashIntent) {
|
||||
when (intent) {
|
||||
is SplashIntent.LoadApp -> loadApp()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadApp() {
|
||||
viewModelScope.launch {
|
||||
DeviceUtils.getUniqueId()
|
||||
|
||||
@@ -40,8 +45,11 @@ class SplashViewModel(
|
||||
TaskTTLApi.postPoint(PointEvent.AppLaunch)
|
||||
}
|
||||
val hasLaunched = onboardingRepository.isLaunchedBefore()
|
||||
_uiState.value =
|
||||
if (hasLaunched) SplashState.NavigateToOnboarding else SplashState.NavigateToMain
|
||||
if (hasLaunched) {
|
||||
sendEvent(SplashEffect.NavigateToOnboarding)
|
||||
} else {
|
||||
sendEvent(SplashEffect.NavigateToMain)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package com.taskttl.data.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.taskttl.core.utils.LogUtils
|
||||
import com.taskttl.core.viewmodel.BaseViewModel
|
||||
import com.taskttl.data.local.model.Category
|
||||
import com.taskttl.data.local.model.CategoryType
|
||||
import com.taskttl.data.local.model.Task
|
||||
@@ -11,12 +11,6 @@ import com.taskttl.data.repository.TaskRepository
|
||||
import com.taskttl.data.state.TaskEffect
|
||||
import com.taskttl.data.state.TaskIntent
|
||||
import com.taskttl.data.state.TaskState
|
||||
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
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import taskttl.composeapp.generated.resources.Res
|
||||
@@ -40,20 +34,15 @@ import taskttl.composeapp.generated.resources.task_update_success
|
||||
*/
|
||||
class TaskViewModel(
|
||||
private val taskRepository: TaskRepository,
|
||||
private val categoryRepository: CategoryRepository
|
||||
) : ViewModel() {
|
||||
private val categoryRepository: CategoryRepository,
|
||||
) : BaseViewModel<TaskState, TaskIntent, TaskEffect>(TaskState()) {
|
||||
|
||||
private val _state = MutableStateFlow(TaskState())
|
||||
val state: StateFlow<TaskState> = _state.asStateFlow()
|
||||
|
||||
private val _effects = MutableSharedFlow<TaskEffect>()
|
||||
val effects: SharedFlow<TaskEffect> = _effects.asSharedFlow()
|
||||
|
||||
init {
|
||||
handleIntent(TaskIntent.LoadTasks)
|
||||
processIntent(TaskIntent.LoadTasks)
|
||||
}
|
||||
|
||||
fun handleIntent(intent: TaskIntent) {
|
||||
public override fun handleIntent(intent: TaskIntent) {
|
||||
when (intent) {
|
||||
is TaskIntent.LoadTasks -> loadTasks()
|
||||
is TaskIntent.GetTaskById -> getTaskById(intent.taskId)
|
||||
@@ -76,7 +65,7 @@ class TaskViewModel(
|
||||
*/
|
||||
private fun navigateBack() {
|
||||
viewModelScope.launch {
|
||||
_effects.emit(TaskEffect.NavigateBack)
|
||||
sendEvent(TaskEffect.NavigateBack)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +74,7 @@ class TaskViewModel(
|
||||
*/
|
||||
private fun navigateToEditTask() {
|
||||
viewModelScope.launch {
|
||||
_effects.emit(TaskEffect.NavigateToEditTask)
|
||||
sendEvent(TaskEffect.NavigateToEditTask)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,31 +83,30 @@ class TaskViewModel(
|
||||
*/
|
||||
private fun loadTasks() {
|
||||
viewModelScope.launch {
|
||||
_state.value = _state.value.copy(isLoading = true, error = null)
|
||||
updateState { copy(isLoading = true, error = null) }
|
||||
try {
|
||||
launch {
|
||||
categoryRepository.getCategoriesByType(CategoryType.TASK)
|
||||
.collect { categories ->
|
||||
LogUtils.e("DevTTL", categories.toString())
|
||||
_state.value = _state.value.copy(categories = categories)
|
||||
updateState { copy(categories = categories) }
|
||||
}
|
||||
}
|
||||
launch {
|
||||
taskRepository.getAllTasks().collect { tasks ->
|
||||
_state.value = _state.value.copy(
|
||||
tasks = tasks,
|
||||
filteredTasks = filterTasks(tasks),
|
||||
isLoading = false
|
||||
)
|
||||
updateState {
|
||||
copy(
|
||||
tasks = tasks,
|
||||
filteredTasks = filterTasks(tasks),
|
||||
isLoading = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LogUtils.e("DevTTL", e.message.toString())
|
||||
_state.value =
|
||||
_state.value.copy(
|
||||
isLoading = false,
|
||||
error = e.message ?: getString(Res.string.task_load_failed)
|
||||
)
|
||||
val errStr = getString(Res.string.task_load_failed)
|
||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,10 +119,10 @@ class TaskViewModel(
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val task = taskRepository.getTaskById(taskId)
|
||||
_state.value = _state.value.copy(editingTask = task)
|
||||
updateState { copy(editingTask = task) }
|
||||
} catch (e: Exception) {
|
||||
_state.value =
|
||||
_state.value.copy(error = e.message ?: getString(Res.string.task_query_failed))
|
||||
val errStr = getString(Res.string.task_query_failed)
|
||||
updateState { copy(error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,11 +135,11 @@ class TaskViewModel(
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
taskRepository.insertTask(task)
|
||||
_effects.emit(TaskEffect.ShowMessage(getString(Res.string.task_add_success)))
|
||||
_effects.emit(TaskEffect.NavigateBack)
|
||||
sendEvent(TaskEffect.ShowMessage(getString(Res.string.task_add_success)))
|
||||
sendEvent(TaskEffect.NavigateBack)
|
||||
} catch (e: Exception) {
|
||||
_state.value =
|
||||
_state.value.copy(error = e.message ?: getString(Res.string.task_add_failed))
|
||||
val errStr = getString(Res.string.task_add_failed)
|
||||
updateState { copy(error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,11 +152,11 @@ class TaskViewModel(
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
taskRepository.updateTask(task)
|
||||
_effects.emit(TaskEffect.ShowMessage(getString(Res.string.task_update_success)))
|
||||
_effects.emit(TaskEffect.NavigateBack)
|
||||
sendEvent(TaskEffect.ShowMessage(getString(Res.string.task_update_success)))
|
||||
sendEvent(TaskEffect.NavigateBack)
|
||||
} catch (e: Exception) {
|
||||
_state.value =
|
||||
_state.value.copy(error = e.message ?: getString(Res.string.task_update_failed))
|
||||
val errStr = getString(Res.string.task_update_failed)
|
||||
updateState { copy(error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,10 +169,10 @@ class TaskViewModel(
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
taskRepository.deleteTask(taskId)
|
||||
_effects.emit(TaskEffect.ShowMessage(getString(Res.string.task_delete_success)))
|
||||
sendEvent(TaskEffect.ShowMessage(getString(Res.string.task_delete_success)))
|
||||
} catch (e: Exception) {
|
||||
_state.value =
|
||||
_state.value.copy(error = e.message ?: getString(Res.string.task_delete_failed))
|
||||
val errStr = getString(Res.string.task_delete_failed)
|
||||
updateState { copy(error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -196,16 +184,15 @@ class TaskViewModel(
|
||||
private fun toggleTaskCompletion(taskId: String) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val task = _state.value.tasks.find { it.id == taskId }
|
||||
val task = state.value.tasks.find { it.id == taskId }
|
||||
task?.let {
|
||||
val updatedTask = it.copy(isCompleted = !it.isCompleted)
|
||||
taskRepository.updateTask(updatedTask)
|
||||
_effects.emit(TaskEffect.ShowMessage(getString(Res.string.task_status_update_success)))
|
||||
sendEvent(TaskEffect.ShowMessage(getString(Res.string.task_status_update_success)))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_state.value = _state.value.copy(
|
||||
error = e.message ?: getString(Res.string.task_status_update_failed)
|
||||
)
|
||||
val errStr = getString(Res.string.task_status_update_failed)
|
||||
updateState { copy(error = e.message ?: errStr) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,8 +202,8 @@ class TaskViewModel(
|
||||
* @param [category] 类别
|
||||
*/
|
||||
private fun filterByCategory(category: Category?) {
|
||||
_state.value = _state.value.copy(selectedCategory = category)
|
||||
_state.value = _state.value.copy(filteredTasks = filterTasks(_state.value.tasks))
|
||||
updateState { copy(selectedCategory = category) }
|
||||
updateState { copy(filteredTasks = filterTasks(state.value.tasks)) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,8 +211,8 @@ class TaskViewModel(
|
||||
* @param [query] 怎么翻译
|
||||
*/
|
||||
private fun searchTasks(query: String) {
|
||||
_state.value = _state.value.copy(searchQuery = query)
|
||||
_state.value = _state.value.copy(filteredTasks = filterTasks(_state.value.tasks))
|
||||
updateState { copy(searchQuery = query) }
|
||||
updateState { copy(filteredTasks = filterTasks(state.value.tasks)) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,22 +220,22 @@ class TaskViewModel(
|
||||
* @param [show] 显示
|
||||
*/
|
||||
private fun toggleShowCompleted(show: Boolean) {
|
||||
_state.value = _state.value.copy(showCompleted = show)
|
||||
_state.value = _state.value.copy(filteredTasks = filterTasks(_state.value.tasks))
|
||||
updateState { copy(showCompleted = show) }
|
||||
updateState { copy(filteredTasks = filterTasks(state.value.tasks)) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索视图
|
||||
*/
|
||||
private fun searchView() {
|
||||
_state.value = _state.value.copy(isSearch = !_state.value.isSearch)
|
||||
updateState { copy(isSearch = !state.value.isSearch) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除错误
|
||||
*/
|
||||
private fun clearError() {
|
||||
_state.value = _state.value.copy(error = null)
|
||||
updateState { copy(error = null) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,7 +244,7 @@ class TaskViewModel(
|
||||
* @return [List<Task>]
|
||||
*/
|
||||
private fun filterTasks(tasks: List<Task>): List<Task> {
|
||||
val currentState = _state.value
|
||||
val currentState = state.value
|
||||
return tasks.filter { task ->
|
||||
val categoryMatch = currentState.selectedCategory?.let { task.category == it } ?: true
|
||||
|
||||
@@ -274,4 +261,4 @@ class TaskViewModel(
|
||||
categoryMatch && searchMatch && completionMatch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -23,6 +23,8 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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
|
||||
@@ -32,7 +34,8 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.taskttl.core.routes.Routes
|
||||
import com.taskttl.data.local.model.OnboardingPage
|
||||
import com.taskttl.data.state.OnboardingEvent
|
||||
import com.taskttl.data.state.OnboardingEffect
|
||||
import com.taskttl.data.state.OnboardingIntent
|
||||
import com.taskttl.data.viewmodel.OnboardingViewModel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
@@ -52,21 +55,22 @@ import taskttl.composeapp.generated.resources.skip_text
|
||||
@Composable
|
||||
fun OnboardingScreen(
|
||||
navigatorToRoute: (Routes) -> Unit,
|
||||
viewModel: OnboardingViewModel = koinViewModel()
|
||||
viewModel: OnboardingViewModel = koinViewModel(),
|
||||
) {
|
||||
val onboardingPages = OnboardingPage.entries
|
||||
val state by viewModel.state.collectAsState()
|
||||
val onboardingPages = state.pages
|
||||
|
||||
val pagerState =
|
||||
rememberPagerState(0, initialPageOffsetFraction = 0f, pageCount = { onboardingPages.size })
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.events.collectLatest { event ->
|
||||
viewModel.effects.collectLatest { event ->
|
||||
when (event) {
|
||||
is OnboardingEvent.NextPage -> {
|
||||
is OnboardingEffect.NextPage -> {
|
||||
pagerState.animateScrollToPage(pagerState.currentPage + 1)
|
||||
}
|
||||
|
||||
is OnboardingEvent.NavMain -> {
|
||||
is OnboardingEffect.NavMain -> {
|
||||
navigatorToRoute(Routes.Main)
|
||||
}
|
||||
}
|
||||
@@ -76,7 +80,7 @@ fun OnboardingScreen(
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
// 右上角跳过
|
||||
TextButton(
|
||||
onClick = { viewModel.markOnboardingCompleted() },
|
||||
onClick = { viewModel.processIntent(OnboardingIntent.MarkOnboardingCompleted) },
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(top = 32.dp, end = 16.dp)
|
||||
@@ -122,7 +126,7 @@ fun OnboardingScreen(
|
||||
Button(
|
||||
onClick = {
|
||||
if (pagerState.currentPage < onboardingPages.lastIndex) {
|
||||
viewModel.sendEvent(OnboardingEvent.NextPage)
|
||||
viewModel.processIntent(OnboardingIntent.NextPage)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -141,7 +145,7 @@ fun OnboardingScreen(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Button(
|
||||
onClick = { viewModel.markOnboardingCompleted() },
|
||||
onClick = { viewModel.processIntent(OnboardingIntent.MarkOnboardingCompleted) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF667EEA)),
|
||||
shape = MaterialTheme.shapes.medium
|
||||
|
@@ -21,7 +21,6 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -33,7 +32,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.taskttl.core.routes.Routes
|
||||
import com.taskttl.data.state.SplashState
|
||||
import com.taskttl.data.state.SplashEffect
|
||||
import com.taskttl.data.viewmodel.SplashViewModel
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
@@ -51,15 +50,20 @@ import taskttl.composeapp.generated.resources.app_name_remark
|
||||
@Composable
|
||||
fun SplashScreen(
|
||||
navigatorToRoute: (Routes) -> Unit,
|
||||
viewModel: SplashViewModel = koinViewModel()
|
||||
viewModel: SplashViewModel = koinViewModel(),
|
||||
) {
|
||||
val state by viewModel.uiState.collectAsState()
|
||||
|
||||
LaunchedEffect(state) {
|
||||
when (state) {
|
||||
SplashState.NavigateToOnboarding -> navigatorToRoute(Routes.Onboarding)
|
||||
SplashState.NavigateToMain -> navigatorToRoute(Routes.Main)
|
||||
else -> {}
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.effects.collect { effect ->
|
||||
when (effect) {
|
||||
is SplashEffect.NavigateToOnboarding -> {
|
||||
navigatorToRoute(Routes.Onboarding)
|
||||
}
|
||||
|
||||
is SplashEffect.NavigateToMain -> {
|
||||
navigatorToRoute(Routes.Main)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -71,14 +71,14 @@ import taskttl.composeapp.generated.resources.total_tasks
|
||||
fun StatisticsScreen(
|
||||
navController: NavHostController,
|
||||
taskViewModel: TaskViewModel = koinViewModel(),
|
||||
countdownViewModel: CountdownViewModel = koinViewModel()
|
||||
countdownViewModel: CountdownViewModel = koinViewModel(),
|
||||
) {
|
||||
|
||||
val taskState by taskViewModel.state.collectAsState()
|
||||
val countdownState by countdownViewModel.state.collectAsState()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
taskViewModel.handleIntent(TaskIntent.LoadTasks)
|
||||
taskViewModel.processIntent(TaskIntent.LoadTasks)
|
||||
countdownViewModel.handleIntent(CountdownIntent.LoadCountdowns)
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ private fun StatisticCard(
|
||||
value: String,
|
||||
icon: ImageVector,
|
||||
color: Color,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
@@ -264,7 +264,7 @@ private fun CategoryStatisticItem(
|
||||
category: Category,
|
||||
totalCount: Int,
|
||||
completedCount: Int,
|
||||
typeRes: StringResource
|
||||
typeRes: StringResource,
|
||||
) {
|
||||
if (totalCount == 0) return
|
||||
|
||||
|
@@ -81,7 +81,7 @@ import taskttl.composeapp.generated.resources.title_task
|
||||
@Preview
|
||||
fun TaskScreen(
|
||||
navController: NavHostController,
|
||||
viewModel: TaskViewModel = koinViewModel()
|
||||
viewModel: TaskViewModel = koinViewModel(),
|
||||
) {
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
@@ -283,7 +283,7 @@ fun TaskCardItem(
|
||||
onClick: () -> Unit,
|
||||
onToggleComplete: () -> Unit,
|
||||
onDeleteTask: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ActionButtonListItem(
|
||||
modifier = Modifier
|
||||
@@ -339,13 +339,29 @@ fun TaskCardItem(
|
||||
|
||||
if (task.description.isNotBlank()) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = task.description,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Row() {
|
||||
Text(
|
||||
text = task.description,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
textDecoration = if (task.isCompleted) TextDecoration.LineThrough else null,
|
||||
color = if (task.isCompleted) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurface,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
// 结束时间
|
||||
task.dueDate?.let {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = task.dueDate.toString(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
textDecoration = if (task.isCompleted) TextDecoration.LineThrough else null,
|
||||
color = if (task.isCompleted) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurface,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user