diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 2c0f824..d87377e 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -75,6 +75,13 @@ kotlin { // work implementation(libs.androidx.work) + + // 三方登录 + // implementation(libs.androidx.login.google) + implementation(libs.androidx.login.credentials) + implementation(libs.androidx.login.credentials.auth) + implementation(libs.androidx.login.googleid) + implementation(libs.androidx.login.facebook) } commonMain.dependencies { implementation(compose.runtime) @@ -193,6 +200,7 @@ dependencies { // add("kspCommonMainMetadata",libs.androidx.room.compiler) // add("kspCommonMain",libs.androidx.room.compiler) // add("kspWasmJs",libs.androidx.room.compiler) + add("kspJvm", libs.androidx.room.compiler) add("kspAndroid", libs.androidx.room.compiler) add("kspIosX64", libs.androidx.room.compiler) add("kspIosArm64", libs.androidx.room.compiler) diff --git a/composeApp/release/composeApp-release.aab b/composeApp/release/composeApp-release.aab deleted file mode 100644 index 10660f9..0000000 Binary files a/composeApp/release/composeApp-release.aab and /dev/null differ diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml index b31c1ba..f0df5bc 100644 --- a/composeApp/src/androidMain/AndroidManifest.xml +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -21,7 +21,9 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" - android:theme="@android:style/Theme.Material.Light.NoActionBar"> + android:enableOnBackInvokedCallback="true" + android:theme="@android:style/Theme.Material.Light.NoActionBar" + tools:targetApi="33"> + > diff --git a/composeApp/src/androidMain/kotlin/com/taskttl/MainActivity.kt b/composeApp/src/androidMain/kotlin/com/taskttl/MainActivity.kt index 677bcc7..3658328 100644 --- a/composeApp/src/androidMain/kotlin/com/taskttl/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/com/taskttl/MainActivity.kt @@ -9,6 +9,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsControllerCompat +import com.taskttl.app.App class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { diff --git a/composeApp/src/androidMain/kotlin/com/taskttl/MainApplication.kt b/composeApp/src/androidMain/kotlin/com/taskttl/MainApplication.kt index f71d337..59046ef 100644 --- a/composeApp/src/androidMain/kotlin/com/taskttl/MainApplication.kt +++ b/composeApp/src/androidMain/kotlin/com/taskttl/MainApplication.kt @@ -5,7 +5,7 @@ import android.app.Activity import android.app.Application import android.os.Bundle import com.google.firebase.FirebaseApp -import com.taskttl.data.di.initKoin +import com.taskttl.app.di.initKoin import com.tencent.mmkv.MMKV import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger @@ -32,12 +32,12 @@ class MainApplication : Application() { registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks { override fun onActivityResumed(activity: Activity) { - _currentActivity = activity + _currentActivity = activity } override fun onActivityPaused(activity: Activity) { - if (_currentActivity == activity) { - _currentActivity = null + if (_currentActivity == activity) { + _currentActivity = null } } diff --git a/composeApp/src/androidMain/kotlin/com/taskttl/app/di/AppModule.android.kt b/composeApp/src/androidMain/kotlin/com/taskttl/app/di/AppModule.android.kt new file mode 100644 index 0000000..c8e76fe --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/taskttl/app/di/AppModule.android.kt @@ -0,0 +1,9 @@ +package com.taskttl.app.di + +import com.taskttl.core.database.TaskTTLDatabase +import com.taskttl.core.database.getDatabaseBuilder +import org.koin.dsl.module + +actual val serviceModule = module { + single { getDatabaseBuilder() } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/taskttl/core/alarm/AlarmReceiver.kt b/composeApp/src/androidMain/kotlin/com/taskttl/core/alarm/AlarmReceiver.kt index 6059a59..4eac940 100644 --- a/composeApp/src/androidMain/kotlin/com/taskttl/core/alarm/AlarmReceiver.kt +++ b/composeApp/src/androidMain/kotlin/com/taskttl/core/alarm/AlarmReceiver.kt @@ -6,29 +6,38 @@ import android.content.Intent import com.taskttl.core.notification.AppNotificationManager import com.taskttl.core.notification.NotificationPayload import com.taskttl.core.notification.NotificationRepeatType +import com.taskttl.core.utils.LogUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class AlarmReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + LogUtils.d("AlarmDebug", "Alarm triggered: ${intent.action}") if (intent.action != "com.taskttl.ALARM_TRIGGER") return - val id = intent.getStringExtra("id") ?: return + val pendingResult = goAsync() // 延长广播生命周期 + val id = intent.getStringExtra("id") ?: run { pendingResult.finish(); return } val title = intent.getStringExtra("title") ?: "TaskTTL" val message = intent.getStringExtra("message") ?: "" // 使用协程调用统一通知方法 CoroutineScope(Dispatchers.Main).launch { - AppNotificationManager.scheduleNotification( - NotificationPayload( - id = id, - title = title, - message = message, - triggerTimeMillis = System.currentTimeMillis(), - repeatType = NotificationRepeatType.NONE + try { + AppNotificationManager.showImmediateNotification( + context.applicationContext, + NotificationPayload( + id = id, + title = title, + message = message, + triggerTimeMillis = System.currentTimeMillis(), + repeatType = NotificationRepeatType.NONE + ) ) - ) + } finally { + pendingResult.finish() // 完成广播 + } } } } diff --git a/composeApp/src/androidMain/kotlin/com/taskttl/core/ui/DevTTLWebView.android.kt b/composeApp/src/androidMain/kotlin/com/taskttl/core/common/DevTTLWebView.android.kt similarity index 99% rename from composeApp/src/androidMain/kotlin/com/taskttl/core/ui/DevTTLWebView.android.kt rename to composeApp/src/androidMain/kotlin/com/taskttl/core/common/DevTTLWebView.android.kt index 6350da2..849b1c1 100644 --- a/composeApp/src/androidMain/kotlin/com/taskttl/core/ui/DevTTLWebView.android.kt +++ b/composeApp/src/androidMain/kotlin/com/taskttl/core/common/DevTTLWebView.android.kt @@ -1,4 +1,4 @@ -package com.taskttl.core.ui +package com.taskttl.core.common import android.annotation.SuppressLint import android.graphics.Bitmap diff --git a/composeApp/src/androidMain/kotlin/com/taskttl/data/local/database/Database.android.kt b/composeApp/src/androidMain/kotlin/com/taskttl/core/database/Database.android.kt similarity index 92% rename from composeApp/src/androidMain/kotlin/com/taskttl/data/local/database/Database.android.kt rename to composeApp/src/androidMain/kotlin/com/taskttl/core/database/Database.android.kt index 268eb9b..eeca13e 100644 --- a/composeApp/src/androidMain/kotlin/com/taskttl/data/local/database/Database.android.kt +++ b/composeApp/src/androidMain/kotlin/com/taskttl/core/database/Database.android.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.local.database +package com.taskttl.core.database import androidx.room.Room import androidx.sqlite.driver.bundled.BundledSQLiteDriver diff --git a/composeApp/src/androidMain/kotlin/com/taskttl/core/notification/AppNotificationManager.android.kt b/composeApp/src/androidMain/kotlin/com/taskttl/core/notification/AppNotificationManager.android.kt index 43ac289..3e5d757 100644 --- a/composeApp/src/androidMain/kotlin/com/taskttl/core/notification/AppNotificationManager.android.kt +++ b/composeApp/src/androidMain/kotlin/com/taskttl/core/notification/AppNotificationManager.android.kt @@ -1,18 +1,14 @@ package com.taskttl.core.notification -import android.Manifest -import android.app.Activity +import android.annotation.SuppressLint import android.app.AlarmManager import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.content.pm.PackageManager import android.media.RingtoneManager import android.os.Build -import androidx.core.app.ActivityCompat import androidx.core.app.NotificationCompat -import androidx.core.content.ContextCompat import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.OneTimeWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder @@ -22,102 +18,109 @@ import com.taskttl.MainApplication import com.taskttl.R import com.taskttl.core.alarm.AlarmReceiver import com.taskttl.core.utils.LogUtils +import com.taskttl.data.constant.Constant import java.util.concurrent.TimeUnit import kotlin.math.max +@SuppressLint("StaticFieldLeak") actual object AppNotificationManager { + private val context: Context = MainApplication.instance.applicationContext + private val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - private val channelId = "taskttl_channel" + private const val ONE_DAY_MS = 24 * 60 * 60 * 1000L init { setupNotificationChannel() } - private fun getActivity(): Activity = MainApplication.currentActivity() - ?: throw IllegalStateException("No activity available") - /** 创建通知通道(Android 8+) */ private fun setupNotificationChannel() { - val activity = MainApplication.currentActivity() ?: return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val nm = - activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - if (nm.getNotificationChannel(channelId) == null) { + if (notificationManager.getNotificationChannel(Constant.CHANNEL_ID) == null) { val channel = android.app.NotificationChannel( - channelId, + Constant.CHANNEL_ID, "TaskTTL Notifications", NotificationManager.IMPORTANCE_HIGH ) - nm.createNotificationChannel(channel) - LogUtils.d("DevTTL_NotificationTest", "Notification channel created") + notificationManager.createNotificationChannel(channel) } } } - /** 调度通知 */ + /** 延迟时间计算 */ + private fun NotificationPayload.delayMillis(): Long = + max(0, triggerTimeMillis - System.currentTimeMillis()) + + /** 调度通知,安全可后台 */ actual suspend fun scheduleNotification(payload: NotificationPayload) { - val activity = getActivity() + val delay = payload.delayMillis() - // Android 13+ 权限检查 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && - ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) - != PackageManager.PERMISSION_GRANTED - ) { - ActivityCompat.requestPermissions( - activity, - arrayOf(Manifest.permission.POST_NOTIFICATIONS), - 1001 - ) - return - } + when { + delay == 0L -> { + LogUtils.d("AlarmDebug", "Showing immediate notification for id=${payload.id}") + showImmediateNotification(context, payload) + } - val delay = max(0, payload.triggerTimeMillis - System.currentTimeMillis()) - if (delay == 0L) { - showImmediateNotification(payload) - return - } + delay <= ONE_DAY_MS && payload.repeatType == NotificationRepeatType.NONE -> { + LogUtils.d("AlarmDebug", "Scheduling exact AlarmManager for id=${payload.id}") + scheduleExactAlarm(context, payload) + } - if (delay <= 24 * 60 * 60 * 1000L && payload.repeatType == NotificationRepeatType.NONE) { - scheduleExactAlarm(payload) - } else { - scheduleWorkManager(payload) + else -> { + LogUtils.d( + "AlarmDebug", + "Scheduling WorkManager for id=${payload.id}, repeat=${payload.repeatType}" + ) + scheduleWorkManager(context, payload) + } } } - /** AlarmManager 精确闹钟 */ - private fun scheduleExactAlarm(payload: NotificationPayload) { - val activity = getActivity() - val alarmManager = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager + /** AlarmManager 精确闹钟 */ + fun scheduleExactAlarm(context: Context, payload: NotificationPayload) { + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !alarmManager.canScheduleExactAlarms()) { - scheduleWorkManager(payload) // 回退 WorkManager + LogUtils.w( + "AlarmDebug", + "Cannot schedule exact alarms, falling back to WorkManager for id=${payload.id}" + ) + scheduleWorkManager(context, payload) return } - val intent = Intent(activity, AlarmReceiver::class.java).apply { - action = "com.taskttl.ALARM_TRIGGER" - putExtra("id", payload.id) - putExtra("title", payload.title) - putExtra("message", payload.message) - } - - val pendingIntent = PendingIntent.getBroadcast( - activity, - payload.id.hashCode(), - intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - + val pendingIntent = createAlarmPendingIntent(context, payload) alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, payload.triggerTimeMillis, pendingIntent ) + LogUtils.d("AlarmDebug", "Alarm set for id=${payload.id} time=${payload.triggerTimeMillis}") + } + + /** 创建 Alarm PendingIntent */ + private fun createAlarmPendingIntent( + context: Context, + payload: NotificationPayload, + ): PendingIntent { + val intent = Intent(context, AlarmReceiver::class.java).apply { + action = "com.taskttl.ALARM_TRIGGER" + putExtra("id", payload.id) + putExtra("title", payload.title) + putExtra("message", payload.message) + } + return PendingIntent.getBroadcast( + context, + payload.id.hashCode(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) } /** WorkManager 长期/周期通知 */ - private fun scheduleWorkManager(payload: NotificationPayload) { - val activity = getActivity() + private fun scheduleWorkManager(context: Context, payload: NotificationPayload) { + val workManager = WorkManager.getInstance(context) val data = workDataOf( "id" to payload.id, "title" to payload.title, @@ -125,22 +128,19 @@ actual object AppNotificationManager { "repeatType" to payload.repeatType.name ) - val workManager = WorkManager.getInstance(activity) - if (payload.repeatType == NotificationRepeatType.NONE) { - val delay = max(0, payload.triggerTimeMillis - System.currentTimeMillis()) val request = OneTimeWorkRequestBuilder() - .setInitialDelay(delay, TimeUnit.MILLISECONDS) + .setInitialDelay(payload.delayMillis(), TimeUnit.MILLISECONDS) .setInputData(data) .addTag(payload.id) .build() workManager.enqueue(request) } else { val interval = when (payload.repeatType) { - NotificationRepeatType.DAILY -> 24 * 60 * 60 * 1000L - NotificationRepeatType.WEEKLY -> 7 * 24 * 60 * 60 * 1000L - NotificationRepeatType.MONTHLY -> 30 * 24 * 60 * 60 * 1000L - else -> 24 * 60 * 60 * 1000L + NotificationRepeatType.DAILY -> ONE_DAY_MS + NotificationRepeatType.WEEKLY -> 7 * ONE_DAY_MS + NotificationRepeatType.MONTHLY -> 30 * ONE_DAY_MS + else -> ONE_DAY_MS } val request = @@ -158,50 +158,37 @@ actual object AppNotificationManager { } /** 立即显示通知 */ - private fun showImmediateNotification(payload: NotificationPayload) { - val activity = getActivity() - val nm = - activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - - val notification = NotificationCompat.Builder(activity, channelId) + fun showImmediateNotification(context: Context, payload: NotificationPayload) { + val notification = NotificationCompat.Builder(context, Constant.CHANNEL_ID) .setContentTitle(payload.title) .setContentText(payload.message) .setSmallIcon(R.mipmap.ic_launcher) .setPriority(NotificationCompat.PRIORITY_HIGH) .setAutoCancel(true) - // .setDefaults(NotificationCompat.DEFAULT_SOUND) .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) .build() - - nm.notify(payload.id.hashCode(), notification) + notificationManager.notify(payload.id.hashCode(), notification) } - /** 取消通知 */ actual suspend fun cancelNotification(id: String) { - val activity = getActivity() - WorkManager.getInstance(activity).cancelAllWorkByTag(id) + val workManager = WorkManager.getInstance(context) + workManager.cancelAllWorkByTag(id) - val alarmManager = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager - val intent = Intent(activity, AlarmReceiver::class.java) + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager val pendingIntent = PendingIntent.getBroadcast( - activity, + context, id.hashCode(), - intent, + Intent(context, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) alarmManager.cancel(pendingIntent) - - val nm = - activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - nm.cancel(id.hashCode()) + notificationManager.cancel(id.hashCode()) } + /** 取消所有通知 */ actual suspend fun cancelAll() { - val activity = getActivity() - WorkManager.getInstance(activity).cancelAllWork() - val nm = - activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - nm.cancelAll() + WorkManager.getInstance(context).cancelAllWork() + notificationManager.cancelAll() } } diff --git a/composeApp/src/androidMain/kotlin/com/taskttl/core/permission/ExactAlarmPermissionManager.android.kt b/composeApp/src/androidMain/kotlin/com/taskttl/core/permission/ExactAlarmPermissionManager.android.kt new file mode 100644 index 0000000..b3a2548 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/taskttl/core/permission/ExactAlarmPermissionManager.android.kt @@ -0,0 +1,36 @@ +package com.taskttl.core.permission + +import android.app.AlarmManager +import android.content.Context +import android.content.Intent +import android.os.Build +import com.taskttl.MainApplication + +/** + * 精确报警权限管理器 + * @author DevTTL + * @date 2025/10/23 + */// AndroidMain +actual object ExactAlarmPermissionManager { + private var appContext = MainApplication.instance.applicationContext + + const val ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM" + + actual fun requestPermission(): Boolean { + if (verifyPermission()) return true + val intent = Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + appContext.startActivity(intent) + return false + } + + actual fun verifyPermission(): Boolean { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return true + val alarmManager = appContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager + return alarmManager.canScheduleExactAlarms() + } + + actual fun disablePermission() { + // Android 无法强制撤销权限,通常无法实现 + } +} diff --git a/composeApp/src/androidMain/kotlin/com/taskttl/core/notification/NotificationPermissionHandler.android.kt b/composeApp/src/androidMain/kotlin/com/taskttl/core/permission/NotificationPermissionManager.android.kt similarity index 54% rename from composeApp/src/androidMain/kotlin/com/taskttl/core/notification/NotificationPermissionHandler.android.kt rename to composeApp/src/androidMain/kotlin/com/taskttl/core/permission/NotificationPermissionManager.android.kt index 1737daa..8dd446c 100644 --- a/composeApp/src/androidMain/kotlin/com/taskttl/core/notification/NotificationPermissionHandler.android.kt +++ b/composeApp/src/androidMain/kotlin/com/taskttl/core/permission/NotificationPermissionManager.android.kt @@ -1,4 +1,4 @@ -package com.taskttl.core.notification +package com.taskttl.core.permission import android.Manifest import android.content.Context @@ -8,78 +8,56 @@ import android.os.Build import android.util.Log import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat +import androidx.core.content.edit import androidx.core.net.toUri import com.taskttl.MainApplication -import androidx.core.content.edit +import com.taskttl.core.utils.LogUtils +/** + * 通知权限管理器 + * @author DevTTL + * @date 2025/10/23 + */ actual object NotificationPermissionManager { private const val REQUEST_CODE = 2025 - private var callback: NotificationPermissionCallback? = null + private const val PREFS_NAME = "app_prefs" + private const val PREF_FIRST_REQUEST = "notification_first_request" - /** - * 在 Activity 的 onRequestPermissionsResult 中调用 - */ - fun handlePermissionResult(requestCode: Int, grantResults: IntArray) { - if (requestCode == REQUEST_CODE) { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - callback?.onGranted() - } else { - callback?.onDenied() - } - } - } - - // expect/actual 适配:Android 需要 Activity 参数 - actual fun requestPermission(callback: NotificationPermissionCallback) { + actual fun requestPermission(): Boolean { // Android 13 以下无需权限 - if (Build.VERSION.SDK_INT < 33) { - callback.onGranted() - return - } + if (Build.VERSION.SDK_INT < 33) return true val activity = MainApplication.currentActivity() if (activity == null) { - Log.w("NotificationPermission", "No current activity found") - callback.onDenied() - return + LogUtils.w("NotificationPermission", "No current activity found") + return false } - this.callback = callback - val prefs = activity.getSharedPreferences("app_prefs", Context.MODE_PRIVATE) + val prefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) val permission = Manifest.permission.POST_NOTIFICATIONS val granted = ContextCompat.checkSelfPermission( activity, permission ) == PackageManager.PERMISSION_GRANTED - if (granted) { - callback.onGranted() + if (granted) return true + val isFirstRequest = prefs.getBoolean("notification_first_request", true) + val shouldShowRationale = + ActivityCompat.shouldShowRequestPermissionRationale(activity, permission) + + if (!shouldShowRationale && !isFirstRequest) { + openNotificationSettings() } else { - val isFirstRequest = prefs.getBoolean("notification_first_request", true) - val shouldShowRationale = - ActivityCompat.shouldShowRequestPermissionRationale(activity, permission) - - if (!shouldShowRationale && !isFirstRequest) { - openNotificationSettings() - callback.onDenied() - } else { - ActivityCompat.requestPermissions(activity, arrayOf(permission), REQUEST_CODE) - } + ActivityCompat.requestPermissions(activity, arrayOf(permission), REQUEST_CODE) } - prefs.edit { putBoolean("notification_first_request", false) } + return false } actual fun verifyPermission(): Boolean { // Android 13 以下无需权限 - if (Build.VERSION.SDK_INT < 33) { - return true - } - val activity = MainApplication.currentActivity() - if (activity == null) { - Log.w("NotificationPermission", "No current activity found") - return false - } + if (Build.VERSION.SDK_INT < 33) return true + val activity = MainApplication.currentActivity() ?: return false val permission = Manifest.permission.POST_NOTIFICATIONS val granted = ContextCompat.checkSelfPermission( activity, permission @@ -115,9 +93,9 @@ actual object NotificationPermissionManager { try { context.startActivity(intent) - Log.d("NotificationPermission", "打开通知设置页") + LogUtils.d("NotificationPermission", "打开通知设置页") } catch (e: Exception) { - Log.e("NotificationPermission", "无法打开通知设置页: ${e.message}") + LogUtils.e("NotificationPermission", "无法打开通知设置页: ${e.message}") } } } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/taskttl/core/utils/DeviceUtils.android.kt b/composeApp/src/androidMain/kotlin/com/taskttl/core/utils/DeviceUtils.android.kt index 2943523..6b1f862 100644 --- a/composeApp/src/androidMain/kotlin/com/taskttl/core/utils/DeviceUtils.android.kt +++ b/composeApp/src/androidMain/kotlin/com/taskttl/core/utils/DeviceUtils.android.kt @@ -2,7 +2,9 @@ package com.taskttl.core.utils import android.annotation.SuppressLint import android.content.Context +import android.content.res.Configuration import android.os.Build +import android.telephony.TelephonyManager import com.google.android.gms.ads.identifier.AdvertisingIdClient import com.taskttl.BuildConfig import com.taskttl.MainApplication @@ -136,4 +138,13 @@ actual object DeviceUtils { } }.toString() } + + fun getSimOrNetworkCountry(): String { + val tm = appContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager + return when { + !tm.networkCountryIso.isNullOrEmpty() -> tm.networkCountryIso.uppercase() + !tm.simCountryIso.isNullOrEmpty() -> tm.simCountryIso.uppercase() + else -> "" + } + } } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/taskttl/core/utils/ExternalAppLauncher.android.kt b/composeApp/src/androidMain/kotlin/com/taskttl/core/utils/ExternalAppLauncher.android.kt index e739b4c..82358a3 100644 --- a/composeApp/src/androidMain/kotlin/com/taskttl/core/utils/ExternalAppLauncher.android.kt +++ b/composeApp/src/androidMain/kotlin/com/taskttl/core/utils/ExternalAppLauncher.android.kt @@ -26,7 +26,7 @@ actual object ExternalAppLauncher { // 如果前面的方法都失败,直接打开网页版 openWebUrl(webUrl) } catch (e: Exception) { - // Log.e(TAG, getString(Res.string.external_app_launcher_open_rating_failed), e) + // LogUtils.w(TAG, getString(Res.string.external_app_launcher_open_rating_failed), e) openWebUrl(webUrl) } } @@ -51,7 +51,7 @@ actual object ExternalAppLauncher { return true } } catch (e: Exception) { - // Log.w(TAG, getString(Res.string.external_app_launcher_open_uri_failed, uri), e) + // LogUtils.w(TAG, getString(Res.string.external_app_launcher_open_uri_failed, uri), e) } return false } @@ -65,7 +65,7 @@ actual object ExternalAppLauncher { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) appContext.startActivity(intent) } catch (e: Exception) { - // Log.e(TAG, getString(Res.string.external_app_launcher_open_web_failed, url), e) + // LogUtils.w(TAG, getString(Res.string.external_app_launcher_open_web_failed, url), e) } } diff --git a/composeApp/src/androidMain/kotlin/com/taskttl/data/di/KoinModels.android.kt b/composeApp/src/androidMain/kotlin/com/taskttl/data/di/KoinModels.android.kt deleted file mode 100644 index 1e8aba0..0000000 --- a/composeApp/src/androidMain/kotlin/com/taskttl/data/di/KoinModels.android.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.taskttl.data.di - -import com.taskttl.data.local.database.TaskTTLDatabase -import com.taskttl.data.local.database.getDatabaseBuilder -import org.koin.dsl.module - -actual fun platformModule() = module { - single { getDatabaseBuilder() } -} - diff --git a/composeApp/src/androidMain/kotlin/com/taskttl/domain/repository/AuthRepository.android.kt b/composeApp/src/androidMain/kotlin/com/taskttl/domain/repository/AuthRepository.android.kt new file mode 100644 index 0000000..a4fc249 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/taskttl/domain/repository/AuthRepository.android.kt @@ -0,0 +1,106 @@ +package com.taskttl.domain.repository + +import android.credentials.GetCredentialException +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.credentials.CredentialManager +import androidx.credentials.CustomCredential +import androidx.credentials.GetCredentialRequest +import androidx.credentials.GetCredentialResponse +import com.facebook.login.LoginManager +import com.google.android.libraries.identity.googleid.GetSignInWithGoogleOption +import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential +import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException +import com.taskttl.MainApplication +import com.taskttl.core.utils.JsonUtils +import com.taskttl.core.utils.LogUtils +import com.taskttl.data.source.remote.dto.response.AuthResult +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext +import kotlin.coroutines.resume + +actual class AuthRepository { + + private val WEB_CLIENT_ID = + "649192447921-rtn0jklurc7cr4oalh9gh3684mnlklce.apps.googleusercontent.com" + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + actual suspend fun loginWithGoogle(): AuthResult = withContext(Dispatchers.Main) { + val activity = MainApplication.currentActivity() + ?: return@withContext AuthResult.Error("未找到当前活动实例") + + return@withContext try { + val credentialManager = CredentialManager.create(activity) + + val signInWithGoogleOption = + GetSignInWithGoogleOption.Builder(WEB_CLIENT_ID).build() + + val request = GetCredentialRequest.Builder() + .addCredentialOption(signInWithGoogleOption) + .build() + + + val response = credentialManager.getCredential(context = activity, request = request) + + handleSignInWithGoogleOption(response) + + // 提取 token 信息 + val googleIdCredential = response.credential + val token = googleIdCredential.data.getString("idToken") ?: "" + val userId = googleIdCredential.data.getString("id") ?: "" + + AuthResult.Success(userId = userId, token = token) + } catch (e: GetCredentialException) { + e.message?.let { LogUtils.e("DevTTL", it) } + AuthResult.Error("获取凭据失败: ${e.message}") + } catch (e: Exception) { + e.message?.let { LogUtils.e("DevTTL", it) } + AuthResult.Error("Google 登录失败: ${e.message}") + } + } + + actual suspend fun loginWithFacebook(): AuthResult = suspendCancellableCoroutine { cont -> + val activity = MainApplication.currentActivity() ?: return@suspendCancellableCoroutine + try { + LoginManager.getInstance().logInWithReadPermissions(activity, listOf("email")) + // TODO: handle Facebook callback + cont.resume(AuthResult.Success("demo_user", "facebook_token")) + } catch (e: Exception) { + cont.resume(AuthResult.Error(e.message ?: "Facebook 登录失败")) + } + } + + /** + * 使用谷歌选项处理登录 + * @param [result] 结果 + */ + fun handleSignInWithGoogleOption(result: GetCredentialResponse) { + val credential = result.credential + + when (credential) { + is CustomCredential -> { + if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) { + try { + val googleIdTokenCredential = + GoogleIdTokenCredential.createFrom(credential.data) + LogUtils.w( + "DevTTL", + JsonUtils.default.encodeToString(googleIdTokenCredential) + ) + } catch (e: GoogleIdTokenParsingException) { + LogUtils.e("DevTTL", "Received an invalid google id token response", e) + } + } else { + // Catch any unrecognized credential type here. + LogUtils.e("DevTTL", "Unexpected type of credential") + } + } + + else -> { + // Catch any unrecognized credential type here. + LogUtils.e("DevTTL", "Unexpected type of credential") + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/client_secret_649192447921-2rqgmislv7mbt2lgib0u422c43cuph61.apps.googleusercontent.com.json b/composeApp/src/client_secret_649192447921-2rqgmislv7mbt2lgib0u422c43cuph61.apps.googleusercontent.com.json new file mode 100644 index 0000000..0fdb7cb --- /dev/null +++ b/composeApp/src/client_secret_649192447921-2rqgmislv7mbt2lgib0u422c43cuph61.apps.googleusercontent.com.json @@ -0,0 +1,9 @@ +{ + "installed": { + "client_id": "649192447921-2rqgmislv7mbt2lgib0u422c43cuph61.apps.googleusercontent.com", + "project_id": "taskttl-476406", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs" + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_launcher.png b/composeApp/src/commonMain/composeResources/drawable/ic_launcher.png new file mode 100644 index 0000000..bac6ce2 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/ic_launcher.png differ diff --git a/composeApp/src/commonMain/composeResources/values-zh/strings.xml b/composeApp/src/commonMain/composeResources/values-zh/strings.xml index 4408a34..c04e5f0 100644 --- a/composeApp/src/commonMain/composeResources/values-zh/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-zh/strings.xml @@ -313,4 +313,6 @@ 反馈成功 反馈失败,请检查网络连接或稍后重试 + yyyy年MM月dd日 EEEE HH:mm + diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index c243ae4..3b39e6e 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -314,4 +314,6 @@ Feedback successful Feedback failed. Please check your network connection or try again later. + EEE, MMM d, yyyy h:mm a + diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/App.kt b/composeApp/src/commonMain/kotlin/com/taskttl/App.kt deleted file mode 100644 index 773a9ce..0000000 --- a/composeApp/src/commonMain/kotlin/com/taskttl/App.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.taskttl - -import androidx.compose.runtime.Composable -import com.taskttl.core.routes.AppNav -import com.taskttl.ui.theme.AppTheme -import org.jetbrains.compose.ui.tooling.preview.Preview - - -/** - * 应用 - */ -@Preview -@Composable -fun App() { - AppTheme { - AppNav() - } -} diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/app/App.kt b/composeApp/src/commonMain/kotlin/com/taskttl/app/App.kt new file mode 100644 index 0000000..0288715 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/app/App.kt @@ -0,0 +1,35 @@ +package com.taskttl.app + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import com.taskttl.core.manager.ThemeMode +import com.taskttl.navigation.AppNav +import com.taskttl.ui.theme.AppTheme +import org.jetbrains.compose.ui.tooling.preview.Preview +import org.koin.compose.viewmodel.koinViewModel + + +/** + * 应用 + */ +@Preview +@Composable +fun App(viewModel: AppViewModel = koinViewModel()) { + + val state by viewModel.state.collectAsState() + + // 获取系统主题设置 + val systemDarkTheme = isSystemInDarkTheme() + + val isDarkTheme = when (state.themeMode) { + ThemeMode.LIGHT -> false + ThemeMode.DARK -> true + ThemeMode.SYSTEM -> systemDarkTheme + } + + AppTheme(darkTheme = isDarkTheme) { + AppNav() + } +} diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/app/AppState.kt b/composeApp/src/commonMain/kotlin/com/taskttl/app/AppState.kt new file mode 100644 index 0000000..fa74f8f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/app/AppState.kt @@ -0,0 +1,29 @@ +package com.taskttl.app + +import com.taskttl.core.base.BaseState +import com.taskttl.core.manager.ThemeMode + +data class AppState( + override val isLoading: Boolean = false, + override val isProcessing: Boolean = false, + override val error: String? = null, + val themeMode: ThemeMode = ThemeMode.SYSTEM, +) : BaseState() + +/** + * 应用程序意图 + * @author DevTTL + * @date 2025/10/28 + * @constructor 创建[AppIntent] + */ +sealed class AppIntent { + + /** + * 加载主题 + * @author DevTTL + * @date 2025/10/28 + */ + object LoadTheme : AppIntent() +} + +sealed class AppEffect {} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/app/AppViewModel.kt b/composeApp/src/commonMain/kotlin/com/taskttl/app/AppViewModel.kt new file mode 100644 index 0000000..9e83c56 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/app/AppViewModel.kt @@ -0,0 +1,36 @@ +package com.taskttl.app + +import androidx.lifecycle.viewModelScope +import com.taskttl.core.base.BaseViewModel +import com.taskttl.core.manager.ThemeManager +import kotlinx.coroutines.launch + +/** + * 应用程序视图模型 + * @author DevTTL + * @date 2025/10/28 + * @constructor 创建[AppViewModel] + * @param [themeManager] 主题管理器 + */ +class AppViewModel(private val themeManager: ThemeManager) : + BaseViewModel(AppState()) { + + init { + viewModelScope.launch { + themeManager.themeMode.collect { mode -> updateState { copy(themeMode = mode) } } + } + // handleIntent(AppIntent.LoadTheme) + } + + public override fun handleIntent(intent: AppIntent) { + when (intent) { + is AppIntent.LoadTheme -> loadTheme() + } + } + + private fun loadTheme() { + viewModelScope.launch { + updateState { copy(themeMode = themeManager.themeMode.value) } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/app/di/AppModule.kt b/composeApp/src/commonMain/kotlin/com/taskttl/app/di/AppModule.kt new file mode 100644 index 0000000..ffaed40 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/app/di/AppModule.kt @@ -0,0 +1,5 @@ +package com.taskttl.app.di + +import org.koin.core.module.Module + +expect val serviceModule: Module \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/di/DataModels.kt b/composeApp/src/commonMain/kotlin/com/taskttl/app/di/DataModels.kt similarity index 55% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/di/DataModels.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/app/di/DataModels.kt index 88df36c..f135a41 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/di/DataModels.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/app/di/DataModels.kt @@ -1,16 +1,19 @@ -package com.taskttl.data.di +package com.taskttl.app.di -import com.taskttl.data.local.database.TaskTTLDatabase -import com.taskttl.data.local.database.getDatabaseBuilder + +import com.taskttl.core.database.TaskTTLDatabase +import com.taskttl.core.database.getDatabaseBuilder import com.taskttl.data.mapper.CategoryMapper import com.taskttl.data.mapper.CountdownMapper import com.taskttl.data.mapper.TaskMapper -import com.taskttl.data.repository.impl.CategoryRepositoryImpl -import com.taskttl.data.repository.impl.CountdownRepositoryImpl -import com.taskttl.data.repository.impl.TaskRepositoryImpl -import com.taskttl.data.repository.CategoryRepository -import com.taskttl.data.repository.CountdownRepository -import com.taskttl.data.repository.TaskRepository +import com.taskttl.data.repository.CategoryRepositoryImpl +import com.taskttl.data.repository.CountdownRepositoryImpl +import com.taskttl.data.repository.TaskRepositoryImpl +import com.taskttl.domain.repository.AuthRepository +import com.taskttl.domain.repository.CategoryRepository +import com.taskttl.domain.repository.CountdownRepository +import com.taskttl.domain.repository.TaskRepository +import org.koin.core.module.dsl.singleOf import org.koin.dsl.module @@ -29,4 +32,6 @@ val dataModule = module { single { TaskRepositoryImpl(get(), get()) } single { CountdownRepositoryImpl(get(), get()) } single { CategoryRepositoryImpl(get(), get(), get(), get()) } + + singleOf(::AuthRepository) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/app/di/KoinModels.kt b/composeApp/src/commonMain/kotlin/com/taskttl/app/di/KoinModels.kt new file mode 100644 index 0000000..be6ca5e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/app/di/KoinModels.kt @@ -0,0 +1,55 @@ +package com.taskttl.app.di + +import com.taskttl.app.AppViewModel +import com.taskttl.core.manager.ThemeManager +import com.taskttl.data.repository.OnboardingRepositoryImpl +import com.taskttl.data.repository.SettingsRepositoryImpl +import com.taskttl.domain.repository.OnboardingRepository +import com.taskttl.domain.repository.SettingsRepository +import com.taskttl.presentation.features.auth.AuthViewModel +import com.taskttl.presentation.features.category.list.CategoryViewModel +import com.taskttl.presentation.features.countdown.list.CountdownViewModel +import com.taskttl.presentation.features.onboarding.OnboardingViewModel +import com.taskttl.presentation.features.settings.feedback.FeedbackViewModel +import com.taskttl.presentation.features.settings.main.SettingsViewModel +import com.taskttl.presentation.features.splash.SplashViewModel +import com.taskttl.presentation.features.task.list.TaskViewModel +import org.koin.core.KoinApplication +import org.koin.core.context.startKoin +import org.koin.core.module.dsl.singleOf +import org.koin.core.module.dsl.viewModelOf +import org.koin.dsl.bind +import org.koin.dsl.module + + +/** + * 初始化Koin + * @param [config] 配置 + */ +fun initKoin(config: (KoinApplication.() -> Unit)? = null) { + startKoin { + config?.invoke(this) + modules(repositoryModule, viewModelModule, serviceModule, dataModule) + } +} + +/** 存储库模块 */ +val repositoryModule = module { + singleOf(::ThemeManager) + singleOf(::OnboardingRepositoryImpl).bind(OnboardingRepository::class) + singleOf(::SettingsRepositoryImpl).bind(SettingsRepository::class) +} + +/** 视图模型模块 */ +val viewModelModule = module { + + viewModelOf(::AppViewModel) + viewModelOf(::SplashViewModel) + viewModelOf(::OnboardingViewModel) + viewModelOf(::TaskViewModel) + viewModelOf(::CategoryViewModel) + viewModelOf(::CountdownViewModel) + viewModelOf(::SettingsViewModel) + viewModelOf(::FeedbackViewModel) + viewModelOf(::AuthViewModel) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/base/BaseState.kt b/composeApp/src/commonMain/kotlin/com/taskttl/core/base/BaseState.kt new file mode 100644 index 0000000..6d1541a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/core/base/BaseState.kt @@ -0,0 +1,16 @@ +package com.taskttl.core.base + +/** + * 基本ui状态 + * @author DevTTL + * @date 2025/10/15 + * @constructor 创建[BaseState] + * @param [isLoading] 正在加载 + * @param [isProcessing] 正在处理 + * @param [error] 错误 + */ +open class BaseState( + open val isLoading: Boolean = false, + open val isProcessing: Boolean = false, + open val error: String? = null, +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/viewmodel/BaseViewModel.kt b/composeApp/src/commonMain/kotlin/com/taskttl/core/base/BaseViewModel.kt similarity index 82% rename from composeApp/src/commonMain/kotlin/com/taskttl/core/viewmodel/BaseViewModel.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/core/base/BaseViewModel.kt index 4f83f45..a512711 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/core/viewmodel/BaseViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/core/base/BaseViewModel.kt @@ -1,4 +1,4 @@ -package com.taskttl.core.viewmodel +package com.taskttl.core.base import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -18,7 +18,7 @@ import kotlinx.coroutines.launch * @constructor 创建[BaseViewModel] * @param [initialState] 初始状态 */ -abstract class BaseViewModel(initialState: S) : ViewModel() { +abstract class BaseViewModel(initialState: S) : ViewModel() { // 状态流 private val _state = MutableStateFlow(initialState) @@ -81,17 +81,3 @@ abstract class BaseViewModel(initialState: S) : ViewModel // } } -/** - * 基本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, -) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/DevTTLWebView.kt b/composeApp/src/commonMain/kotlin/com/taskttl/core/common/DevTTLWebView.kt similarity index 87% rename from composeApp/src/commonMain/kotlin/com/taskttl/core/ui/DevTTLWebView.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/core/common/DevTTLWebView.kt index e308d77..11e068c 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/DevTTLWebView.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/core/common/DevTTLWebView.kt @@ -1,4 +1,4 @@ -package com.taskttl.core.ui +package com.taskttl.core.common import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/database/Database.kt b/composeApp/src/commonMain/kotlin/com/taskttl/core/database/Database.kt similarity index 68% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/local/database/Database.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/core/database/Database.kt index bdf1b55..54f6cea 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/database/Database.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/core/database/Database.kt @@ -1,13 +1,13 @@ -package com.taskttl.data.local.database +package com.taskttl.core.database import androidx.room.Database import androidx.room.RoomDatabase -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.local.entity.CategoryEntity -import com.taskttl.data.local.entity.CountdownEntity -import com.taskttl.data.local.entity.TaskEntity +import com.taskttl.data.source.local.dao.CategoryDao +import com.taskttl.data.source.local.dao.CountdownDao +import com.taskttl.data.source.local.dao.TaskDao +import com.taskttl.data.source.local.entity.CategoryEntity +import com.taskttl.data.source.local.entity.CountdownEntity +import com.taskttl.data.source.local.entity.TaskEntity /** * TaskTTL数据库 diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/manager/ThemeManager.kt b/composeApp/src/commonMain/kotlin/com/taskttl/core/manager/ThemeManager.kt new file mode 100644 index 0000000..6eb32cd --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/core/manager/ThemeManager.kt @@ -0,0 +1,42 @@ +package com.taskttl.core.manager + +import com.taskttl.core.utils.StorageUtils +import com.taskttl.data.constant.Constant +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +/** + * 主题管理器 + * @author DevTTL + * @date 2025/10/28 + * @constructor 创建[ThemeManager] + */ +class ThemeManager() { + private val _themeMode = MutableStateFlow(ThemeMode.SYSTEM) + val themeMode: StateFlow = _themeMode.asStateFlow() + + init { + // 从本地存储加载主题设置 + loadThemeFromPreferences() + } + + /** + * 从本地存储加载主题 + */ + private fun loadThemeFromPreferences() { + val modeString = StorageUtils.getString(Constant.KEY_DARK_MODE, ThemeMode.SYSTEM.name) + _themeMode.value = ThemeMode.fromName(modeString) + + } + + /** + * 切换主题模式 + */ + fun setDarkTheme(isDark: ThemeMode) { + _themeMode.value = isDark + // 保存到本地存储 + StorageUtils.saveString(Constant.KEY_DARK_MODE, isDark.name) + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/manager/ThemeMode.kt b/composeApp/src/commonMain/kotlin/com/taskttl/core/manager/ThemeMode.kt new file mode 100644 index 0000000..d51a0d2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/core/manager/ThemeMode.kt @@ -0,0 +1,21 @@ +package com.taskttl.core.manager + +/** + * 主题模式枚举 + */ +enum class ThemeMode { + LIGHT, // 明亮模式 + DARK, // 暗黑模式 + SYSTEM; // 跟随系统 + + companion object { + fun fromName(name: String?): ThemeMode { + return when (name?.uppercase()) { + "LIGHT" -> LIGHT + "DARK" -> DARK + "SYSTEM" -> SYSTEM + else -> SYSTEM // 默认返回 SYSTEM + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/notification/NotificationPermissionHandler.kt b/composeApp/src/commonMain/kotlin/com/taskttl/core/notification/NotificationPermissionHandler.kt deleted file mode 100644 index 50a7133..0000000 --- a/composeApp/src/commonMain/kotlin/com/taskttl/core/notification/NotificationPermissionHandler.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.taskttl.core.notification - -/** - * 通知权限回调接口 - */ -interface NotificationPermissionCallback { - /** - * 授予 - */ - fun onGranted() - - /** - * 被否认 - */ - fun onDenied() -} - -/** - * 跨平台通知权限处理器 - */ -expect object NotificationPermissionManager { - /** - * 请求通知权限 - * @return 是否允许 - */ - fun requestPermission(callback: NotificationPermissionCallback) - - fun verifyPermission(): Boolean - - fun disablePermission() -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/permission/ExactAlarmPermissionManager.kt b/composeApp/src/commonMain/kotlin/com/taskttl/core/permission/ExactAlarmPermissionManager.kt new file mode 100644 index 0000000..6735c88 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/core/permission/ExactAlarmPermissionManager.kt @@ -0,0 +1,25 @@ +package com.taskttl.core.permission + +/** + * 跨平台精确闹钟权限处理器 + */ +expect object ExactAlarmPermissionManager { + + /** + * 请求精确闹钟权限 + * Android 12+ 需要用户允许 + * iOS/其他平台无需操作,直接回调 true + * + */ + fun requestPermission(): Boolean + + /** + * 验证当前是否有精确闹钟权限 + */ + fun verifyPermission(): Boolean + + /** + * 禁用或撤销权限(如果平台支持) + */ + fun disablePermission() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/permission/NotificationPermissionManager.kt b/composeApp/src/commonMain/kotlin/com/taskttl/core/permission/NotificationPermissionManager.kt new file mode 100644 index 0000000..7d00e58 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/core/permission/NotificationPermissionManager.kt @@ -0,0 +1,17 @@ +package com.taskttl.core.permission + + +/** + * 跨平台通知权限处理器 + */ +expect object NotificationPermissionManager { + /** + * 请求通知权限 + * @return 是否允许 + */ + fun requestPermission(): Boolean + + fun verifyPermission(): Boolean + + fun disablePermission() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/NotchedBottomBar.kt b/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/NotchedBottomBar.kt deleted file mode 100644 index d8e5226..0000000 --- a/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/NotchedBottomBar.kt +++ /dev/null @@ -1,314 +0,0 @@ -package com.taskttl.core.ui - -import androidx.annotation.DrawableRes -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.aspectRatio -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.FloatingActionButtonDefaults -import androidx.compose.material3.FloatingActionButtonElevation -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.NavigationBarItemDefaults -import androidx.compose.material3.Surface -import androidx.compose.material3.contentColorFor -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.Path -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.layout.SubcomposeLayout -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastForEachIndexed -import androidx.compose.ui.util.fastMapIndexed - - -/** - * Note: fabeSize maintains the same height of modifier - */ -@Composable -fun NotchedBottomBar( - modifier: Modifier = Modifier, - icons: List, - selectedIndex: Int, - fabIcon: ImageVector, - fabIconSize: Dp = 28.dp, - onIconClick: (index: Int) -> Unit, - onFabClick: () -> Unit, - fabSize: Dp, - fabColor: Color = FloatingActionButtonDefaults.containerColor, - fabContainerColor: Color = contentColorFor(fabColor), - fabElevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), - iconColor: Color = NavigationBarItemDefaults.colors().unselectedIconColor, - selectedIconColor: Color = NavigationBarItemDefaults.colors().selectedIconColor, - iconContainerColor: Color = NavigationBarItemDefaults.colors().selectedIndicatorColor, - containerColor: Color = MaterialTheme.colorScheme.surfaceContainer -) { - val density = LocalDensity.current - SubcomposeLayout( - modifier = modifier - ) { constraints -> - // measure fab - val fabPlaceables = subcompose("fab") { - FloatingActionButton( - onClick = onFabClick, - modifier = Modifier.size(fabSize), - containerColor = fabContainerColor, - contentColor = fabColor, - elevation = fabElevation, - shape = CircleShape - ) { - Icon( - modifier = Modifier.size(fabIconSize), - imageVector = fabIcon, - contentDescription = "fab", - tint = fabColor - ) - } - }.map { it.measure(constraints) } - - val fabWidth = fabPlaceables.maxOf { it.width } - val fabHeight = fabPlaceables.maxOf { it.height } - - // measure icons - val iconPlaceables = icons.fastMapIndexed { index, icon -> - val measurable = subcompose("icon_$index") { - val selected = selectedIndex == index - Surface( - onClick = { onIconClick(index) }, - shape = CircleShape, - modifier = Modifier - .padding(vertical = 12.dp) - .aspectRatio(16 / 9f), - color = if (selected) iconContainerColor else Color.Transparent, - contentColor = if (selected) selectedIconColor else iconColor - ) { - Icon( - imageVector = icon, - contentDescription = "$index-icon", - tint = if (selected) selectedIconColor else iconColor - ) - } - }.first().measure(constraints) - measurable - } - - val canvasHeight = iconPlaceables.maxOf { it.height } - - // measure background - val backgroundPlaceables = subcompose("background") { - Canvas( - modifier = Modifier - .fillMaxWidth() - .height(with(density) { canvasHeight.toDp() }) - ) { - val width = size.width - val height = size.height - val fabRadius = fabWidth / 2f - val centerX = width / 2f - val curveDepth = fabHeight * 0.6f - val controlOffsetX = fabWidth.toFloat() - - val path = Path().apply { - moveTo(0f, 0f) - lineTo(centerX - fabRadius - controlOffsetX, 0f) - cubicTo( - centerX - fabRadius, 0f, - centerX - fabRadius, curveDepth, - centerX, curveDepth - ) - cubicTo( - centerX + fabRadius, curveDepth, - centerX + fabRadius, 0f, - centerX + fabRadius + controlOffsetX, 0f - ) - lineTo(width, 0f) - lineTo(width, height) - lineTo(0f, height) - close() - } - drawPath(path, color = containerColor) - } - }.map { it.measure(constraints) } - - // calculate icon space - val totalItemsWidth = iconPlaceables.sumOf { it.width } + fabWidth - val spaceCount = icons.size + 1 - val spaceWidth = (constraints.maxWidth - totalItemsWidth).coerceAtLeast(0) / spaceCount - - layout(constraints.maxWidth, constraints.maxHeight) { - backgroundPlaceables.forEach { - it.place(0, constraints.maxHeight - it.height) - } - - var x = spaceWidth - iconPlaceables.fastForEachIndexed { index, it -> - it.place(x, 0) - x += it.width + if (index == (icons.size / 2 - 1)) { - spaceWidth + fabWidth // keep the space for fab - } else { - spaceWidth - } - } - - fabPlaceables.forEach { - it.place( - (constraints.maxWidth - it.width) / 2, - 0 - fabHeight / 2 - ) - } - } - } -} - - -/** - * Note: fabeSize maintains the same height of modifier - */ -@Composable -fun NotchedBottomBar( - modifier: Modifier = Modifier, - icons: List, - selectedIndex: Int, - @DrawableRes fabIcon: Int, - fabIconSize: Dp = 28.dp, - onIconClick: (index: Int) -> Unit, - onFabClick: () -> Unit, - fabSize: Dp, - fabColor: Color = FloatingActionButtonDefaults.containerColor, - fabContainerColor: Color = contentColorFor(fabColor), - fabElevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), - iconColor: Color = NavigationBarItemDefaults.colors().unselectedIconColor, - selectedIconColor: Color = NavigationBarItemDefaults.colors().selectedIconColor, - iconContainerColor: Color = NavigationBarItemDefaults.colors().selectedIndicatorColor, - containerColor: Color = MaterialTheme.colorScheme.surfaceContainer -) { - val density = LocalDensity.current - SubcomposeLayout( - modifier = modifier - ) { constraints -> - // measure fab - val fabPlaceables = subcompose("fab") { - FloatingActionButton( - onClick = onFabClick, - modifier = Modifier.size(fabSize), - containerColor = fabContainerColor, - contentColor = fabColor, - elevation = fabElevation, - shape = CircleShape - ) { - Image( - modifier = Modifier.size(fabIconSize), - painter = painterResource(fabIcon), - contentDescription = "fab", - colorFilter = ColorFilter.tint(fabColor) - ) - } - }.map { it.measure(constraints) } - - val fabWidth = fabPlaceables.maxOf { it.width } - val fabHeight = fabPlaceables.maxOf { it.height } - - // measure icons - val iconPlaceables = icons.fastMapIndexed { index, icon -> - val measurable = subcompose("icon_$index") { - val selected = selectedIndex == index - Surface( - onClick = { onIconClick(index) }, - shape = CircleShape, - modifier = Modifier - .padding(vertical = 12.dp) - .aspectRatio(16 / 9f), - color = if (selected) iconContainerColor else Color.Transparent, - contentColor = if (selected) selectedIconColor else iconColor - ) { - Image( - modifier= Modifier - .wrapContentSize().padding(vertical = 3.dp), - painter = painterResource(icon), - contentDescription = "$index-icon", - colorFilter = ColorFilter.tint(iconColor) - ) - } - }.first().measure(constraints) - measurable - } - - val canvasHeight = iconPlaceables.maxOf { it.height } - - // measure background - val backgroundPlaceables = subcompose("background") { - Canvas( - modifier = Modifier - .fillMaxWidth() - .height(with(density) { canvasHeight.toDp() }) - ) { - val width = size.width - val height = size.height - val fabRadius = fabWidth / 2f - val centerX = width / 2f - val curveDepth = fabHeight * 0.6f - val controlOffsetX = fabWidth.toFloat() - - val path = Path().apply { - moveTo(0f, 0f) - lineTo(centerX - fabRadius - controlOffsetX, 0f) - cubicTo( - centerX - fabRadius, 0f, - centerX - fabRadius, curveDepth, - centerX, curveDepth - ) - cubicTo( - centerX + fabRadius, curveDepth, - centerX + fabRadius, 0f, - centerX + fabRadius + controlOffsetX, 0f - ) - lineTo(width, 0f) - lineTo(width, height) - lineTo(0f, height) - close() - } - drawPath(path, color = containerColor) - } - }.map { it.measure(constraints) } - - // calculate icon space - val totalItemsWidth = iconPlaceables.sumOf { it.width } + fabWidth - val spaceCount = icons.size + 1 - val spaceWidth = (constraints.maxWidth - totalItemsWidth).coerceAtLeast(0) / spaceCount - - layout(constraints.maxWidth, constraints.maxHeight) { - backgroundPlaceables.forEach { - it.place(0, constraints.maxHeight - it.height) - } - - var x = spaceWidth - iconPlaceables.fastForEachIndexed { index, it -> - it.place(x, 0) - x += it.width + if (index == (icons.size / 2 - 1)) { - spaceWidth + fabWidth // keep the space for fab - } else { - spaceWidth - } - } - - fabPlaceables.forEach { - it.place( - (constraints.maxWidth - it.width) / 2, - 0 - fabHeight / 2 - ) - } - } - } -} - diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/utils/DateUtils.kt b/composeApp/src/commonMain/kotlin/com/taskttl/core/utils/DateUtils.kt index fe9eaca..fc0e893 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/core/utils/DateUtils.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/core/utils/DateUtils.kt @@ -1,6 +1,6 @@ package com.taskttl.core.utils -import com.taskttl.data.local.model.CountdownTime +import com.taskttl.domain.model.CountdownTime import kotlinx.datetime.DatePeriod import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.LocalDate diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/utils/DeviceUtils.kt b/composeApp/src/commonMain/kotlin/com/taskttl/core/utils/DeviceUtils.kt index e7f2124..f2aeed5 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/core/utils/DeviceUtils.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/core/utils/DeviceUtils.kt @@ -22,4 +22,4 @@ expect object DeviceUtils { */ suspend fun getDeviceInfo(): BaseReq -} \ No newline at end of file +} diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/constant/Constant.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/constant/Constant.kt new file mode 100644 index 0000000..9a3929e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/constant/Constant.kt @@ -0,0 +1,14 @@ +package com.taskttl.data.constant + +/** + * 常量 + * @author DevTTL + * @date 2025/10/24 + */ +object Constant { + /** 频道id */ + const val CHANNEL_ID = "taskttl_channel" + + /** 按键暗模式 */ + const val KEY_DARK_MODE = "dark_mode" +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/di/KoinModels.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/di/KoinModels.kt deleted file mode 100644 index 35fc2ab..0000000 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/di/KoinModels.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.taskttl.data.di - -import com.taskttl.data.repository.OnboardingRepository -import com.taskttl.data.repository.SettingsRepository -import com.taskttl.data.repository.impl.OnboardingRepositoryImpl -import com.taskttl.data.repository.impl.SettingsRepositoryImpl -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 -import org.koin.core.context.startKoin -import org.koin.core.module.Module -import org.koin.core.module.dsl.singleOf -import org.koin.core.module.dsl.viewModelOf -import org.koin.dsl.bind -import org.koin.dsl.module - -expect fun platformModule(): Module - -/** - * 初始化Koin - * @param [config] 配置 - */ -fun initKoin(config: (KoinApplication.() -> Unit)? = null) { - startKoin { - config?.invoke(this) - modules(repositoryModule, viewModelModule, platformModule(), dataModule) - } -} - -/** 存储库模块 */ -val repositoryModule = module { - singleOf(::OnboardingRepositoryImpl).bind(OnboardingRepository::class) - singleOf(::SettingsRepositoryImpl).bind(SettingsRepository::class) -} - -/** 视图模型模块 */ -val viewModelModule = module { - viewModelOf(::SplashViewModel) - viewModelOf(::OnboardingViewModel) - viewModelOf(::TaskViewModel) - viewModelOf(::CategoryViewModel) - viewModelOf(::CountdownViewModel) - viewModelOf(::SettingsViewModel) - viewModelOf(::FeedbackViewModel) -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/mapper/CategoryMapper.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/mapper/CategoryMapper.kt index 99eda56..c29063e 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/mapper/CategoryMapper.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/mapper/CategoryMapper.kt @@ -1,10 +1,10 @@ package com.taskttl.data.mapper -import com.taskttl.data.local.entity.CategoryEntity -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.CategoryType +import com.taskttl.data.source.local.entity.CategoryEntity +import com.taskttl.domain.model.Category +import com.taskttl.domain.model.CategoryColor +import com.taskttl.domain.model.CategoryIcon +import com.taskttl.domain.model.CategoryType import kotlinx.datetime.LocalDateTime /** diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/mapper/CountdownMapper.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/mapper/CountdownMapper.kt index 2a89516..142379b 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/mapper/CountdownMapper.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/mapper/CountdownMapper.kt @@ -1,13 +1,13 @@ package com.taskttl.data.mapper -import com.taskttl.data.local.entity.CountdownEntity -import com.taskttl.data.local.entity.CountdownWithCategory -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.CategoryType -import com.taskttl.data.local.model.Countdown -import com.taskttl.data.local.model.ReminderFrequency +import com.taskttl.data.source.local.entity.CountdownEntity +import com.taskttl.data.source.local.entity.CountdownWithCategory +import com.taskttl.domain.model.Category +import com.taskttl.domain.model.CategoryColor +import com.taskttl.domain.model.CategoryIcon +import com.taskttl.domain.model.CategoryType +import com.taskttl.domain.model.Countdown +import com.taskttl.domain.model.ReminderFrequency import kotlinx.datetime.LocalDateTime /** diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/mapper/TaskMapper.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/mapper/TaskMapper.kt index 90561eb..d5755f9 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/mapper/TaskMapper.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/mapper/TaskMapper.kt @@ -1,13 +1,14 @@ package com.taskttl.data.mapper -import com.taskttl.data.local.entity.TaskEntity -import com.taskttl.data.local.entity.TaskWithCategory -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.CategoryType -import com.taskttl.data.local.model.Task -import com.taskttl.data.local.model.TaskPriority +import com.taskttl.data.source.local.entity.TaskEntity +import com.taskttl.data.source.local.entity.TaskWithCategory +import com.taskttl.domain.model.Category +import com.taskttl.domain.model.CategoryColor +import com.taskttl.domain.model.CategoryIcon +import com.taskttl.domain.model.CategoryType +import com.taskttl.domain.model.Task +import com.taskttl.domain.model.TaskPriority + import kotlinx.datetime.LocalDateTime import kotlinx.serialization.json.Json diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/CategoryRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/CategoryRepositoryImpl.kt similarity index 94% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/CategoryRepositoryImpl.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/repository/CategoryRepositoryImpl.kt index 878c2a6..c40941d 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/CategoryRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/CategoryRepositoryImpl.kt @@ -1,15 +1,15 @@ -package com.taskttl.data.repository.impl +package com.taskttl.data.repository -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.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 com.taskttl.data.source.local.dao.CategoryDao +import com.taskttl.data.source.local.dao.CountdownDao +import com.taskttl.data.source.local.dao.TaskDao +import com.taskttl.domain.model.Category +import com.taskttl.domain.model.CategoryColor +import com.taskttl.domain.model.CategoryIcon +import com.taskttl.domain.model.CategoryStatistics +import com.taskttl.domain.model.CategoryType +import com.taskttl.domain.repository.CategoryRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/CountdownRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/CountdownRepositoryImpl.kt similarity index 91% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/CountdownRepositoryImpl.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/repository/CountdownRepositoryImpl.kt index cb85055..b6494c6 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/CountdownRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/CountdownRepositoryImpl.kt @@ -1,9 +1,9 @@ -package com.taskttl.data.repository.impl +package com.taskttl.data.repository -import com.taskttl.data.local.dao.CountdownDao +import com.taskttl.data.source.local.dao.CountdownDao import com.taskttl.data.mapper.CountdownMapper -import com.taskttl.data.local.model.Countdown -import com.taskttl.data.repository.CountdownRepository +import com.taskttl.domain.model.Countdown +import com.taskttl.domain.repository.CountdownRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/OnboardingRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/OnboardingRepositoryImpl.kt similarity index 86% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/OnboardingRepositoryImpl.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/repository/OnboardingRepositoryImpl.kt index f8a36e3..c98f66e 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/OnboardingRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/OnboardingRepositoryImpl.kt @@ -1,7 +1,7 @@ -package com.taskttl.data.repository.impl +package com.taskttl.data.repository import com.taskttl.core.utils.StorageUtils -import com.taskttl.data.repository.OnboardingRepository +import com.taskttl.domain.repository.OnboardingRepository /** * 设置存储库impl diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/SettingsRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/SettingsRepositoryImpl.kt similarity index 86% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/SettingsRepositoryImpl.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/repository/SettingsRepositoryImpl.kt index 80d34c4..95ee398 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/SettingsRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/SettingsRepositoryImpl.kt @@ -1,7 +1,7 @@ -package com.taskttl.data.repository.impl +package com.taskttl.data.repository import com.taskttl.core.utils.StorageUtils -import com.taskttl.data.repository.SettingsRepository +import com.taskttl.domain.repository.SettingsRepository /** * 设置存储库impl diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/TaskRepositoryImpl.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/TaskRepositoryImpl.kt similarity index 92% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/TaskRepositoryImpl.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/repository/TaskRepositoryImpl.kt index cdaa269..e8f53ef 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/impl/TaskRepositoryImpl.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/TaskRepositoryImpl.kt @@ -1,9 +1,9 @@ -package com.taskttl.data.repository.impl +package com.taskttl.data.repository -import com.taskttl.data.local.dao.TaskDao +import com.taskttl.data.source.local.dao.TaskDao import com.taskttl.data.mapper.TaskMapper -import com.taskttl.data.local.model.Task -import com.taskttl.data.repository.TaskRepository +import com.taskttl.domain.model.Task +import com.taskttl.domain.repository.TaskRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/dao/CategoryDao.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/dao/CategoryDao.kt similarity index 96% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/local/dao/CategoryDao.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/dao/CategoryDao.kt index a38e0db..5d99235 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/dao/CategoryDao.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/dao/CategoryDao.kt @@ -1,7 +1,7 @@ -package com.taskttl.data.local.dao +package com.taskttl.data.source.local.dao import androidx.room.* -import com.taskttl.data.local.entity.CategoryEntity +import com.taskttl.data.source.local.entity.CategoryEntity import kotlinx.coroutines.flow.Flow /** diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/dao/CountdownDao.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/dao/CountdownDao.kt similarity index 90% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/local/dao/CountdownDao.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/dao/CountdownDao.kt index f765a01..53b0460 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/dao/CountdownDao.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/dao/CountdownDao.kt @@ -1,8 +1,8 @@ -package com.taskttl.data.local.dao +package com.taskttl.data.source.local.dao import androidx.room.* -import com.taskttl.data.local.entity.CountdownEntity -import com.taskttl.data.local.entity.CountdownWithCategory +import com.taskttl.data.source.local.entity.CountdownEntity +import com.taskttl.data.source.local.entity.CountdownWithCategory import kotlinx.coroutines.flow.Flow /** diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/dao/TaskDao.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/dao/TaskDao.kt similarity index 92% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/local/dao/TaskDao.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/dao/TaskDao.kt index 6244fa4..a27552e 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/dao/TaskDao.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/dao/TaskDao.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.local.dao +package com.taskttl.data.source.local.dao import androidx.room.Dao import androidx.room.Insert @@ -6,8 +6,8 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Transaction import androidx.room.Update -import com.taskttl.data.local.entity.TaskEntity -import com.taskttl.data.local.entity.TaskWithCategory +import com.taskttl.data.source.local.entity.TaskEntity +import com.taskttl.data.source.local.entity.TaskWithCategory import kotlinx.coroutines.flow.Flow /** diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/CategoryEntity.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/CategoryEntity.kt similarity index 94% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/CategoryEntity.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/CategoryEntity.kt index eabeb0a..2972818 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/CategoryEntity.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/CategoryEntity.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.local.entity +package com.taskttl.data.source.local.entity import androidx.room.Entity import androidx.room.PrimaryKey diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/CountdownEntity.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/CountdownEntity.kt similarity index 94% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/CountdownEntity.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/CountdownEntity.kt index c37ac22..349ec20 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/CountdownEntity.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/CountdownEntity.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.local.entity +package com.taskttl.data.source.local.entity import androidx.room.Entity import androidx.room.PrimaryKey diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/CountdownWithCategory.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/CountdownWithCategory.kt similarity index 90% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/CountdownWithCategory.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/CountdownWithCategory.kt index 0bc0ff8..487326b 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/CountdownWithCategory.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/CountdownWithCategory.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.local.entity +package com.taskttl.data.source.local.entity import androidx.room.Embedded import androidx.room.Relation diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/TaskEntity.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/TaskEntity.kt similarity index 94% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/TaskEntity.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/TaskEntity.kt index e4cd5fd..78a8330 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/TaskEntity.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/TaskEntity.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.local.entity +package com.taskttl.data.source.local.entity import androidx.room.Entity import androidx.room.PrimaryKey diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/TaskWithCategory.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/TaskWithCategory.kt similarity index 89% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/TaskWithCategory.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/TaskWithCategory.kt index 265bfc4..cd7ed23 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/entity/TaskWithCategory.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/local/entity/TaskWithCategory.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.local.entity +package com.taskttl.data.source.local.entity import androidx.room.Embedded import androidx.room.Relation diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/network/TaskTTLApi.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/api/TaskTTLApi.kt similarity index 85% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/network/TaskTTLApi.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/api/TaskTTLApi.kt index c008a0c..238ba31 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/network/TaskTTLApi.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/api/TaskTTLApi.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.network +package com.taskttl.data.source.remote.api import com.taskttl.core.domain.constant.PointEvent import com.taskttl.core.domain.toJson @@ -6,9 +6,9 @@ import com.taskttl.core.network.ApiConfig import com.taskttl.core.network.KtorClient import com.taskttl.core.utils.DeviceUtils import com.taskttl.core.utils.LogUtils -import com.taskttl.data.network.domain.req.FeedbackReq -import com.taskttl.data.network.domain.req.PointReq -import com.taskttl.data.network.domain.resp.FeedbackResp +import com.taskttl.data.source.remote.dto.request.FeedbackReq +import com.taskttl.data.source.remote.dto.request.PointReq +import com.taskttl.data.source.remote.dto.response.FeedbackResp import org.jetbrains.compose.resources.getString import taskttl.composeapp.generated.resources.Res import taskttl.composeapp.generated.resources.feedback_error diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/network/domain/req/FeedbackReq.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/dto/request/FeedbackReq.kt similarity index 91% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/network/domain/req/FeedbackReq.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/dto/request/FeedbackReq.kt index 6971b2b..b34e6cf 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/network/domain/req/FeedbackReq.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/dto/request/FeedbackReq.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.network.domain.req +package com.taskttl.data.source.remote.dto.request import com.taskttl.core.domain.BaseReqWith import kotlinx.serialization.SerialName diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/network/domain/req/PointReq.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/dto/request/PointReq.kt similarity index 88% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/network/domain/req/PointReq.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/dto/request/PointReq.kt index 1ba9455..b79959c 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/network/domain/req/PointReq.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/dto/request/PointReq.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.network.domain.req +package com.taskttl.data.source.remote.dto.request import com.taskttl.core.domain.BaseReqWith import kotlinx.serialization.Serializable diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/dto/response/AuthResult.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/dto/response/AuthResult.kt new file mode 100644 index 0000000..3e59170 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/dto/response/AuthResult.kt @@ -0,0 +1,7 @@ +package com.taskttl.data.source.remote.dto.response + +sealed class AuthResult { + data class Success(val userId: String, val token: String) : AuthResult() + data class Error(val message: String) : AuthResult() + data object Canceled : AuthResult() +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/network/domain/resp/FeedbackResp.kt b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/dto/response/FeedbackResp.kt similarity index 72% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/network/domain/resp/FeedbackResp.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/dto/response/FeedbackResp.kt index db96198..d1d9a15 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/network/domain/resp/FeedbackResp.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/data/source/remote/dto/response/FeedbackResp.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.network.domain.resp +package com.taskttl.data.source.remote.dto.response import kotlinx.serialization.Serializable diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/model/Category.kt b/composeApp/src/commonMain/kotlin/com/taskttl/domain/model/Category.kt similarity index 99% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/local/model/Category.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/domain/model/Category.kt index 7b230a5..0cb1dc9 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/model/Category.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/domain/model/Category.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.local.model +package com.taskttl.domain.model import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Assignment diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/model/Countdown.kt b/composeApp/src/commonMain/kotlin/com/taskttl/domain/model/Countdown.kt similarity index 98% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/local/model/Countdown.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/domain/model/Countdown.kt index f603199..9311764 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/model/Countdown.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/domain/model/Countdown.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.local.model +package com.taskttl.domain.model import kotlinx.datetime.LocalDateTime import kotlinx.serialization.Serializable diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/model/Onboarding.kt b/composeApp/src/commonMain/kotlin/com/taskttl/domain/model/Onboarding.kt similarity index 98% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/local/model/Onboarding.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/domain/model/Onboarding.kt index bf50cba..a2ce492 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/model/Onboarding.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/domain/model/Onboarding.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.local.model +package com.taskttl.domain.model import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CalendarToday diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/model/Task.kt b/composeApp/src/commonMain/kotlin/com/taskttl/domain/model/Task.kt similarity index 97% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/local/model/Task.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/domain/model/Task.kt index bff5317..4862168 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/local/model/Task.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/domain/model/Task.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.local.model +package com.taskttl.domain.model import androidx.compose.ui.graphics.Color import kotlinx.datetime.LocalDateTime diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/AuthRepository.kt b/composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/AuthRepository.kt new file mode 100644 index 0000000..a6caefe --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/AuthRepository.kt @@ -0,0 +1,8 @@ +package com.taskttl.domain.repository + +import com.taskttl.data.source.remote.dto.response.AuthResult + +expect class AuthRepository() { + suspend fun loginWithGoogle(): AuthResult + suspend fun loginWithFacebook(): AuthResult +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/CategoryRepository.kt b/composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/CategoryRepository.kt similarity index 91% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/repository/CategoryRepository.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/CategoryRepository.kt index 65da803..5035009 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/CategoryRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/CategoryRepository.kt @@ -1,8 +1,8 @@ -package com.taskttl.data.repository +package com.taskttl.domain.repository -import com.taskttl.data.local.model.Category -import com.taskttl.data.local.model.CategoryStatistics -import com.taskttl.data.local.model.CategoryType +import com.taskttl.domain.model.Category +import com.taskttl.domain.model.CategoryStatistics +import com.taskttl.domain.model.CategoryType import kotlinx.coroutines.flow.Flow /** diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/CountdownRepository.kt b/composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/CountdownRepository.kt similarity index 92% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/repository/CountdownRepository.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/CountdownRepository.kt index 308a91b..f4b6edb 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/CountdownRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/CountdownRepository.kt @@ -1,6 +1,6 @@ -package com.taskttl.data.repository +package com.taskttl.domain.repository -import com.taskttl.data.local.model.Countdown +import com.taskttl.domain.model.Countdown import kotlinx.coroutines.flow.Flow /** diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/OnboardingRepository.kt b/composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/OnboardingRepository.kt similarity index 89% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/repository/OnboardingRepository.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/OnboardingRepository.kt index 39a3f31..c6de885 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/OnboardingRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/OnboardingRepository.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.repository +package com.taskttl.domain.repository /** * 设置存储库 diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/SettingsRepository.kt b/composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/SettingsRepository.kt similarity index 89% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/repository/SettingsRepository.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/SettingsRepository.kt index b41374d..efcee21 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/SettingsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/SettingsRepository.kt @@ -1,4 +1,4 @@ -package com.taskttl.data.repository +package com.taskttl.domain.repository /** * 设置存储库 diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/TaskRepository.kt b/composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/TaskRepository.kt similarity index 93% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/repository/TaskRepository.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/TaskRepository.kt index 1f4614c..04e13c9 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/repository/TaskRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/domain/repository/TaskRepository.kt @@ -1,6 +1,6 @@ -package com.taskttl.data.repository +package com.taskttl.domain.repository -import com.taskttl.data.local.model.Task +import com.taskttl.domain.model.Task import kotlinx.coroutines.flow.Flow /** diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/routes/AppNav.kt b/composeApp/src/commonMain/kotlin/com/taskttl/navigation/AppNav.kt similarity index 87% rename from composeApp/src/commonMain/kotlin/com/taskttl/core/routes/AppNav.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/navigation/AppNav.kt index bc02260..51940bb 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/core/routes/AppNav.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/navigation/AppNav.kt @@ -1,11 +1,11 @@ -package com.taskttl.core.routes +package com.taskttl.navigation import androidx.compose.runtime.Composable import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import com.taskttl.presentation.onboarding.OnboardingScreen -import com.taskttl.presentation.splash.SplashScreen +import com.taskttl.presentation.features.onboarding.OnboardingScreen +import com.taskttl.presentation.features.splash.SplashScreen /** * 应用导航 diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/routes/MainNav.kt b/composeApp/src/commonMain/kotlin/com/taskttl/navigation/MainNav.kt similarity index 63% rename from composeApp/src/commonMain/kotlin/com/taskttl/core/routes/MainNav.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/navigation/MainNav.kt index cfd97e6..941c269 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/core/routes/MainNav.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/navigation/MainNav.kt @@ -1,9 +1,8 @@ -package com.taskttl.core.routes +package com.taskttl.navigation import androidx.compose.foundation.background import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.List @@ -23,22 +22,22 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.toRoute -import com.taskttl.core.routes.Routes.Main -import com.taskttl.core.ui.CustomBottomBar -import com.taskttl.presentation.category.CategoryEditScreen -import com.taskttl.presentation.category.CategoryScreen -import com.taskttl.presentation.countdown.CountdownDetailScreen -import com.taskttl.presentation.countdown.CountdownEditScreen -import com.taskttl.presentation.countdown.CountdownScreen -import com.taskttl.presentation.settings.AboutScreen -import com.taskttl.presentation.settings.DataManagementScreen -import com.taskttl.presentation.settings.FeedbackScreen -import com.taskttl.presentation.settings.PrivacyScreen -import com.taskttl.presentation.settings.SettingsScreen -import com.taskttl.presentation.statistics.StatisticsScreen -import com.taskttl.presentation.task.TaskDetailScreen -import com.taskttl.presentation.task.TaskEditorScreen -import com.taskttl.presentation.task.TaskScreen +import com.taskttl.presentation.common.components.CustomBottomBar +import com.taskttl.presentation.features.auth.LoginScreen +import com.taskttl.presentation.features.category.editor.CategoryEditScreen +import com.taskttl.presentation.features.category.list.CategoryScreen +import com.taskttl.presentation.features.countdown.detail.CountdownDetailScreen +import com.taskttl.presentation.features.countdown.editor.CountdownEditScreen +import com.taskttl.presentation.features.countdown.list.CountdownScreen +import com.taskttl.presentation.features.settings.about.AboutScreen +import com.taskttl.presentation.features.settings.dataManagement.DataManagementScreen +import com.taskttl.presentation.features.settings.feedback.FeedbackScreen +import com.taskttl.presentation.features.settings.main.SettingsScreen +import com.taskttl.presentation.features.settings.privacy.PrivacyScreen +import com.taskttl.presentation.features.statistics.StatisticsScreen +import com.taskttl.presentation.features.task.detail.TaskEditorScreen +import com.taskttl.presentation.features.task.editor.TaskDetailScreen +import com.taskttl.presentation.features.task.list.TaskScreen import taskttl.composeapp.generated.resources.Res import taskttl.composeapp.generated.resources.nav_countdown import taskttl.composeapp.generated.resources.nav_settings @@ -54,10 +53,10 @@ fun MainNav() { val bottomItems = remember { listOf( - Triple(Icons.AutoMirrored.Filled.List, Res.string.nav_todo, Main.Task), - Triple(Icons.Default.AccessTime, Res.string.nav_countdown, Main.Countdown), - Triple(Icons.Default.BarChart, Res.string.nav_statistics, Main.Statistics), - Triple(Icons.Default.Settings, Res.string.nav_settings, Main.Settings), + Triple(Icons.AutoMirrored.Filled.List, Res.string.nav_todo, Routes.Main.Task), + Triple(Icons.Default.AccessTime, Res.string.nav_countdown, Routes.Main.Countdown), + Triple(Icons.Default.BarChart, Res.string.nav_statistics, Routes.Main.Statistics), + Triple(Icons.Default.Settings, Res.string.nav_settings, Routes.Main.Settings), ) } @@ -75,7 +74,7 @@ fun MainNav() { return@CustomBottomBar } mainNavController.navigate(route) { - popUpTo(Main.Task) { saveState = true } + popUpTo(Routes.Main.Task) { saveState = true } launchSingleTop = true restoreState = true } @@ -87,114 +86,121 @@ fun MainNav() { modifier = Modifier.fillMaxSize() .padding(bottom = paddingValues.calculateBottomPadding()), navController = mainNavController, - startDestination = Main.Task + startDestination = Routes.Main.Task ) { // 任务 - composable { + composable { TaskScreen(navController = mainNavController) } - composable { + composable { TaskEditorScreen( taskId = null, onNavigateBack = { mainNavController.popBackStack() } ) } - composable { backStackEntry -> - val taskDetail: Main.Task.EditTask = backStackEntry.toRoute() + composable { backStackEntry -> + val taskDetail: Routes.Main.Task.EditTask = backStackEntry.toRoute() TaskEditorScreen( taskDetail.taskId, onNavigateBack = { mainNavController.popBackStack() } ) } - composable { backStackEntry -> - val taskDetail: Main.Task.TaskDetail = backStackEntry.toRoute() + composable { backStackEntry -> + val taskDetail: Routes.Main.Task.TaskDetail = backStackEntry.toRoute() TaskDetailScreen( taskId = taskDetail.taskId, onNavigateBack = { mainNavController.popBackStack() }, - onNavigateToEdit = { mainNavController.navigate(Main.Task.EditTask(taskDetail.taskId)) } + onNavigateToEdit = { mainNavController.navigate(Routes.Main.Task.EditTask(taskDetail.taskId)) } ) } // 倒数日 - composable { + composable { CountdownScreen(navController = mainNavController) } - composable { + composable { CountdownEditScreen( countdownId = null, onNavigateBack = { mainNavController.popBackStack() } ) } - composable { backStackEntry -> - val countdown: Main.Countdown.EditCountdown = backStackEntry.toRoute() + composable { backStackEntry -> + val countdown: Routes.Main.Countdown.EditCountdown = backStackEntry.toRoute() CountdownEditScreen( countdownId = countdown.countdownId, onNavigateBack = { mainNavController.popBackStack() } ) } - composable { backStackEntry -> - val countdown: Main.Countdown.CountdownDetail = backStackEntry.toRoute() + composable { backStackEntry -> + val countdown: Routes.Main.Countdown.CountdownDetail = backStackEntry.toRoute() CountdownDetailScreen( countdownId = countdown.countdownId, onNavigateBack = { mainNavController.popBackStack() }, onNavigateToEdit = { - mainNavController.navigate(Main.Countdown.EditCountdown(countdown.countdownId)) + mainNavController.navigate(Routes.Main.Countdown.EditCountdown(countdown.countdownId)) } ) } - composable { + composable { StatisticsScreen(navController = mainNavController) } // 设置界面 - composable { + composable { SettingsScreen(navController = mainNavController) } // 分类管理 - composable { backStackEntry -> + composable { backStackEntry -> CategoryScreen( navController = mainNavController, - onAddCategory = { mainNavController.navigate(Main.Settings.AddCategory) }, + onAddCategory = { mainNavController.navigate(Routes.Main.Settings.AddCategory) }, onNavigateBack = { mainNavController.popBackStack() } ) } // 添加分类 - composable { + composable { CategoryEditScreen( categoryId = null, onNavigateBack = { mainNavController.popBackStack() } ) } // 编辑分类 - composable { backStackEntry -> - val editCategory: Main.Settings.EditCategory = backStackEntry.toRoute() + composable { backStackEntry -> + val editCategory: Routes.Main.Settings.EditCategory = backStackEntry.toRoute() CategoryEditScreen( categoryId = editCategory.categoryId, onNavigateBack = { mainNavController.popBackStack() } ) } // 数据管理 - composable { + composable { DataManagementScreen(onNavigateBack = { mainNavController.popBackStack() }) } // 反馈页面 - composable { + composable { FeedbackScreen(onNavigateBack = { mainNavController.popBackStack() }) } // 隐私 - composable { + composable { PrivacyScreen( onNavigateBack = { mainNavController.popBackStack() } ) } // 关于页面 - composable { + composable { AboutScreen( onNavigateBack = { mainNavController.popBackStack() } ) } + + // 登录页面 + composable { + LoginScreen( + onNavigateBack = { mainNavController.popBackStack() } + ) + } } } diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/routes/Routes.kt b/composeApp/src/commonMain/kotlin/com/taskttl/navigation/Routes.kt similarity index 94% rename from composeApp/src/commonMain/kotlin/com/taskttl/core/routes/Routes.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/navigation/Routes.kt index c516cdd..9055df6 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/core/routes/Routes.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/navigation/Routes.kt @@ -1,4 +1,4 @@ -package com.taskttl.core.routes +package com.taskttl.navigation import kotlinx.serialization.Serializable @@ -73,6 +73,9 @@ sealed interface Routes { @Serializable data object About : Routes + + @Serializable + data object Login : Routes } diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/ActionButtonListItem.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/ActionButtonListItem.kt similarity index 99% rename from composeApp/src/commonMain/kotlin/com/taskttl/core/ui/ActionButtonListItem.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/ActionButtonListItem.kt index 6245b8a..ee7eaf0 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/ActionButtonListItem.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/ActionButtonListItem.kt @@ -1,4 +1,4 @@ -package com.taskttl.core.ui +package com.taskttl.presentation.common.components import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/ui/components/AppHeader.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/AppHeader.kt similarity index 92% rename from composeApp/src/commonMain/kotlin/com/taskttl/ui/components/AppHeader.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/AppHeader.kt index e1219ea..ebd2afe 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/ui/components/AppHeader.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/AppHeader.kt @@ -1,4 +1,4 @@ -package com.taskttl.ui.components +package com.taskttl.presentation.common.components import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -16,7 +16,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush -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 @@ -63,14 +62,14 @@ fun AppHeader( Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(Res.string.back), - tint = Color.White, + tint = MaterialTheme.colorScheme.surface, modifier = Modifier.clickable { onBackClick() } ) } Text( text = stringResource(title), - color = Color.White, + color = MaterialTheme.colorScheme.surface, fontSize = 20.sp, fontWeight = FontWeight.Bold ) @@ -79,7 +78,7 @@ fun AppHeader( Icon( imageVector = trailingIcon, contentDescription = stringResource(Res.string.action), - tint = Color.White, + tint = MaterialTheme.colorScheme.surface, modifier = Modifier.clickable { onTrailingClick?.invoke() } ) } diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/ui/components/CategoryFilter.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/CategoryFilter.kt similarity index 94% rename from composeApp/src/commonMain/kotlin/com/taskttl/ui/components/CategoryFilter.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/CategoryFilter.kt index 22519f2..0eff106 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/ui/components/CategoryFilter.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/CategoryFilter.kt @@ -1,4 +1,4 @@ -package com.taskttl.ui.components +package com.taskttl.presentation.common.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.lazy.LazyRow @@ -9,7 +9,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.taskttl.data.local.model.Category +import com.taskttl.domain.model.Category import org.jetbrains.compose.resources.stringResource import taskttl.composeapp.generated.resources.Res import taskttl.composeapp.generated.resources.all_text diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/Chip.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/Chip.kt similarity index 87% rename from composeApp/src/commonMain/kotlin/com/taskttl/core/ui/Chip.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/Chip.kt index 1ff4e58..f1766a3 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/Chip.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/Chip.kt @@ -1,4 +1,4 @@ -package com.taskttl.core.ui +package com.taskttl.presentation.common.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape 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 @@ -26,8 +27,8 @@ import org.jetbrains.compose.resources.stringResource fun Chip( textRes: StringResource, icon: ImageVector? = null, - backgroundColor: Color = Color(0xFFF5F5F5), - contentColor: Color = Color(0xFF555555), + backgroundColor: Color = MaterialTheme.colorScheme.surfaceContainer, + contentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant, modifier: Modifier = Modifier ) { Row( @@ -63,8 +64,8 @@ fun Chip( fun Chip( text: String, icon: ImageVector? = null, - backgroundColor: Color = Color(0xFFF5F5F5), - contentColor: Color = Color(0xFF555555), + backgroundColor: Color = MaterialTheme.colorScheme.surfaceContainer, + contentColor: Color = MaterialTheme.colorScheme.onSurfaceVariant, modifier: Modifier = Modifier ) { Row( diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/ui/components/CompactDatePickerDialog.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/CompactDatePickerDialog.kt similarity index 97% rename from composeApp/src/commonMain/kotlin/com/taskttl/ui/components/CompactDatePickerDialog.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/CompactDatePickerDialog.kt index 540a730..ce4c20b 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/ui/components/CompactDatePickerDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/CompactDatePickerDialog.kt @@ -1,4 +1,4 @@ -package com.taskttl.ui.components +package com.taskttl.presentation.common.components import androidx.compose.material3.DatePicker import androidx.compose.material3.DatePickerDialog diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/CustomBottom.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/CustomBottom.kt similarity index 92% rename from composeApp/src/commonMain/kotlin/com/taskttl/core/ui/CustomBottom.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/CustomBottom.kt index 19db245..f8b734a 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/CustomBottom.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/CustomBottom.kt @@ -1,4 +1,4 @@ -package com.taskttl.core.ui +package com.taskttl.presentation.common.components import androidx.compose.foundation.LocalIndication import androidx.compose.foundation.background @@ -20,11 +20,10 @@ 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 import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.taskttl.core.routes.Routes +import com.taskttl.navigation.Routes import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.stringResource @@ -44,13 +43,13 @@ fun CustomBottomBar( Box( modifier = Modifier.navigationBarsPadding().fillMaxWidth().height(barHeight) - .background(MaterialTheme.colorScheme.background) + .background(MaterialTheme.colorScheme.surface) ) { Row( modifier = Modifier .align(Alignment.BottomCenter) .fillMaxWidth() - .height(56.dp).background(Color.White) + .height(56.dp).background(MaterialTheme.colorScheme.surface) .padding(horizontal = 8.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically @@ -85,7 +84,7 @@ fun BottomBarItem( modifier: Modifier = Modifier ) { val color = - if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant + if (isSelected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface val interactionSource = remember { MutableInteractionSource() } Column( modifier = modifier diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/ErrorDialog.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/ErrorDialog.kt similarity index 94% rename from composeApp/src/commonMain/kotlin/com/taskttl/core/ui/ErrorDialog.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/ErrorDialog.kt index dae6ef5..37ec492 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/ErrorDialog.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/ErrorDialog.kt @@ -1,4 +1,4 @@ -package com.taskttl.core.ui +package com.taskttl.presentation.common.components import androidx.compose.material3.AlertDialog import androidx.compose.material3.Text diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/LoadingOverlay.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/LoadingOverlay.kt similarity index 95% rename from composeApp/src/commonMain/kotlin/com/taskttl/core/ui/LoadingOverlay.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/LoadingOverlay.kt index 6f363ad..606ee60 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/core/ui/LoadingOverlay.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/LoadingOverlay.kt @@ -1,4 +1,4 @@ -package com.taskttl.core.ui +package com.taskttl.presentation.common.components import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.LinearEasing @@ -71,9 +71,9 @@ fun LoadingOverlay( size = 56.dp, stroke = 6.dp, gradientColors = listOf( - Color(0xFF4285F4), - Color(0xFF73A7F9), - Color(0xFF4285F4) + MaterialTheme.colorScheme.primary, + MaterialTheme.colorScheme.secondary, + MaterialTheme.colorScheme.primary ) ) Spacer(Modifier.height(16.dp)) diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/NotchedBottomBar.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/NotchedBottomBar.kt new file mode 100644 index 0000000..4b4982a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/NotchedBottomBar.kt @@ -0,0 +1,315 @@ +// package com.taskttl.presentation.common.components +// +// import androidx.annotation.DrawableRes +// import androidx.compose.foundation.Canvas +// import androidx.compose.foundation.Image +// import androidx.compose.foundation.layout.aspectRatio +// import androidx.compose.foundation.layout.fillMaxWidth +// import androidx.compose.foundation.layout.height +// import androidx.compose.foundation.layout.padding +// import androidx.compose.foundation.layout.size +// import androidx.compose.foundation.layout.wrapContentSize +// import androidx.compose.foundation.shape.CircleShape +// import androidx.compose.material3.FloatingActionButton +// import androidx.compose.material3.FloatingActionButtonDefaults +// import androidx.compose.material3.FloatingActionButtonElevation +// import androidx.compose.material3.Icon +// import androidx.compose.material3.MaterialTheme +// import androidx.compose.material3.NavigationBarItemDefaults +// import androidx.compose.material3.Surface +// import androidx.compose.material3.contentColorFor +// import androidx.compose.runtime.Composable +// import androidx.compose.ui.Modifier +// import androidx.compose.ui.graphics.Color +// import androidx.compose.ui.graphics.ColorFilter +// import androidx.compose.ui.graphics.Path +// import androidx.compose.ui.graphics.vector.ImageVector +// import androidx.compose.ui.layout.SubcomposeLayout +// import androidx.compose.ui.platform.LocalDensity +// import androidx.compose.ui.res.painterResource +// import androidx.compose.ui.unit.Dp +// import androidx.compose.ui.unit.dp +// import androidx.compose.ui.util.fastForEachIndexed +// import androidx.compose.ui.util.fastMapIndexed +// import org.jetbrains.compose.resources.painterResource +// +// +// /** +// * Note: fabeSize maintains the same height of modifier +// */ +// @Composable +// fun NotchedBottomBar( +// modifier: Modifier = Modifier, +// icons: List, +// selectedIndex: Int, +// fabIcon: ImageVector, +// fabIconSize: Dp = 28.dp, +// onIconClick: (index: Int) -> Unit, +// onFabClick: () -> Unit, +// fabSize: Dp, +// fabColor: Color = FloatingActionButtonDefaults.containerColor, +// fabContainerColor: Color = contentColorFor(fabColor), +// fabElevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), +// iconColor: Color = NavigationBarItemDefaults.colors().unselectedIconColor, +// selectedIconColor: Color = NavigationBarItemDefaults.colors().selectedIconColor, +// iconContainerColor: Color = NavigationBarItemDefaults.colors().selectedIndicatorColor, +// containerColor: Color = MaterialTheme.colorScheme.surfaceContainer +// ) { +// val density = LocalDensity.current +// SubcomposeLayout( +// modifier = modifier +// ) { constraints -> +// // measure fab +// val fabPlaceables = subcompose("fab") { +// FloatingActionButton( +// onClick = onFabClick, +// modifier = Modifier.size(fabSize), +// containerColor = fabContainerColor, +// contentColor = fabColor, +// elevation = fabElevation, +// shape = CircleShape +// ) { +// Icon( +// modifier = Modifier.size(fabIconSize), +// imageVector = fabIcon, +// contentDescription = "fab", +// tint = fabColor +// ) +// } +// }.map { it.measure(constraints) } +// +// val fabWidth = fabPlaceables.maxOf { it.width } +// val fabHeight = fabPlaceables.maxOf { it.height } +// +// // measure icons +// val iconPlaceables = icons.fastMapIndexed { index, icon -> +// val measurable = subcompose("icon_$index") { +// val selected = selectedIndex == index +// Surface( +// onClick = { onIconClick(index) }, +// shape = CircleShape, +// modifier = Modifier +// .padding(vertical = 12.dp) +// .aspectRatio(16 / 9f), +// color = if (selected) iconContainerColor else Color.Transparent, +// contentColor = if (selected) selectedIconColor else iconColor +// ) { +// Icon( +// imageVector = icon, +// contentDescription = "$index-icon", +// tint = if (selected) selectedIconColor else iconColor +// ) +// } +// }.first().measure(constraints) +// measurable +// } +// +// val canvasHeight = iconPlaceables.maxOf { it.height } +// +// // measure background +// val backgroundPlaceables = subcompose("background") { +// Canvas( +// modifier = Modifier +// .fillMaxWidth() +// .height(with(density) { canvasHeight.toDp() }) +// ) { +// val width = size.width +// val height = size.height +// val fabRadius = fabWidth / 2f +// val centerX = width / 2f +// val curveDepth = fabHeight * 0.6f +// val controlOffsetX = fabWidth.toFloat() +// +// val path = Path().apply { +// moveTo(0f, 0f) +// lineTo(centerX - fabRadius - controlOffsetX, 0f) +// cubicTo( +// centerX - fabRadius, 0f, +// centerX - fabRadius, curveDepth, +// centerX, curveDepth +// ) +// cubicTo( +// centerX + fabRadius, curveDepth, +// centerX + fabRadius, 0f, +// centerX + fabRadius + controlOffsetX, 0f +// ) +// lineTo(width, 0f) +// lineTo(width, height) +// lineTo(0f, height) +// close() +// } +// drawPath(path, color = containerColor) +// } +// }.map { it.measure(constraints) } +// +// // calculate icon space +// val totalItemsWidth = iconPlaceables.sumOf { it.width } + fabWidth +// val spaceCount = icons.size + 1 +// val spaceWidth = (constraints.maxWidth - totalItemsWidth).coerceAtLeast(0) / spaceCount +// +// layout(constraints.maxWidth, constraints.maxHeight) { +// backgroundPlaceables.forEach { +// it.place(0, constraints.maxHeight - it.height) +// } +// +// var x = spaceWidth +// iconPlaceables.fastForEachIndexed { index, it -> +// it.place(x, 0) +// x += it.width + if (index == (icons.size / 2 - 1)) { +// spaceWidth + fabWidth // keep the space for fab +// } else { +// spaceWidth +// } +// } +// +// fabPlaceables.forEach { +// it.place( +// (constraints.maxWidth - it.width) / 2, +// 0 - fabHeight / 2 +// ) +// } +// } +// } +// } +// +// +// /** +// * Note: fabeSize maintains the same height of modifier +// */ +// @Composable +// fun NotchedBottomBar( +// modifier: Modifier = Modifier, +// icons: List, +// selectedIndex: Int, +// @DrawableRes fabIcon: Int, +// fabIconSize: Dp = 28.dp, +// onIconClick: (index: Int) -> Unit, +// onFabClick: () -> Unit, +// fabSize: Dp, +// fabColor: Color = FloatingActionButtonDefaults.containerColor, +// fabContainerColor: Color = contentColorFor(fabColor), +// fabElevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), +// iconColor: Color = NavigationBarItemDefaults.colors().unselectedIconColor, +// selectedIconColor: Color = NavigationBarItemDefaults.colors().selectedIconColor, +// iconContainerColor: Color = NavigationBarItemDefaults.colors().selectedIndicatorColor, +// containerColor: Color = MaterialTheme.colorScheme.surfaceContainer +// ) { +// val density = LocalDensity.current +// SubcomposeLayout( +// modifier = modifier +// ) { constraints -> +// // measure fab +// val fabPlaceables = subcompose("fab") { +// FloatingActionButton( +// onClick = onFabClick, +// modifier = Modifier.size(fabSize), +// containerColor = fabContainerColor, +// contentColor = fabColor, +// elevation = fabElevation, +// shape = CircleShape +// ) { +// Image( +// modifier = Modifier.size(fabIconSize), +// painter = painterResource(fabIcon), +// contentDescription = "fab", +// colorFilter = ColorFilter.tint(fabColor) +// ) +// } +// }.map { it.measure(constraints) } +// +// val fabWidth = fabPlaceables.maxOf { it.width } +// val fabHeight = fabPlaceables.maxOf { it.height } +// +// // measure icons +// val iconPlaceables = icons.fastMapIndexed { index, icon -> +// val measurable = subcompose("icon_$index") { +// val selected = selectedIndex == index +// Surface( +// onClick = { onIconClick(index) }, +// shape = CircleShape, +// modifier = Modifier +// .padding(vertical = 12.dp) +// .aspectRatio(16 / 9f), +// color = if (selected) iconContainerColor else Color.Transparent, +// contentColor = if (selected) selectedIconColor else iconColor +// ) { +// Image( +// modifier= Modifier +// .wrapContentSize().padding(vertical = 3.dp), +// painter = painterResource(icon), +// contentDescription = "$index-icon", +// colorFilter = ColorFilter.tint(iconColor) +// ) +// } +// }.first().measure(constraints) +// measurable +// } +// +// val canvasHeight = iconPlaceables.maxOf { it.height } +// +// // measure background +// val backgroundPlaceables = subcompose("background") { +// Canvas( +// modifier = Modifier +// .fillMaxWidth() +// .height(with(density) { canvasHeight.toDp() }) +// ) { +// val width = size.width +// val height = size.height +// val fabRadius = fabWidth / 2f +// val centerX = width / 2f +// val curveDepth = fabHeight * 0.6f +// val controlOffsetX = fabWidth.toFloat() +// +// val path = Path().apply { +// moveTo(0f, 0f) +// lineTo(centerX - fabRadius - controlOffsetX, 0f) +// cubicTo( +// centerX - fabRadius, 0f, +// centerX - fabRadius, curveDepth, +// centerX, curveDepth +// ) +// cubicTo( +// centerX + fabRadius, curveDepth, +// centerX + fabRadius, 0f, +// centerX + fabRadius + controlOffsetX, 0f +// ) +// lineTo(width, 0f) +// lineTo(width, height) +// lineTo(0f, height) +// close() +// } +// drawPath(path, color = containerColor) +// } +// }.map { it.measure(constraints) } +// +// // calculate icon space +// val totalItemsWidth = iconPlaceables.sumOf { it.width } + fabWidth +// val spaceCount = icons.size + 1 +// val spaceWidth = (constraints.maxWidth - totalItemsWidth).coerceAtLeast(0) / spaceCount +// +// layout(constraints.maxWidth, constraints.maxHeight) { +// backgroundPlaceables.forEach { +// it.place(0, constraints.maxHeight - it.height) +// } +// +// var x = spaceWidth +// iconPlaceables.fastForEachIndexed { index, it -> +// it.place(x, 0) +// x += it.width + if (index == (icons.size / 2 - 1)) { +// spaceWidth + fabWidth // keep the space for fab +// } else { +// spaceWidth +// } +// } +// +// fabPlaceables.forEach { +// it.place( +// (constraints.maxWidth - it.width) / 2, +// 0 - fabHeight / 2 +// ) +// } +// } +// } +// } +// diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/ui/components/SearchBar.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/SearchBar.kt similarity index 97% rename from composeApp/src/commonMain/kotlin/com/taskttl/ui/components/SearchBar.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/SearchBar.kt index a7a7126..7fcfcf8 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/ui/components/SearchBar.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/SearchBar.kt @@ -1,4 +1,4 @@ -package com.taskttl.ui.components +package com.taskttl.presentation.common.components import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Clear diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/ThemeModeDialog.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/ThemeModeDialog.kt new file mode 100644 index 0000000..222b6f2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/components/ThemeModeDialog.kt @@ -0,0 +1,159 @@ +package com.taskttl.presentation.common.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.RadioButtonDefaults +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import com.taskttl.core.manager.ThemeMode + +/** + * 主题模式选择弹窗 + */ +@Composable +fun ThemeModeDialog( + currentMode: ThemeMode, + onModeSelected: (ThemeMode) -> Unit, + onDismiss: () -> Unit, +) { + Dialog(onDismissRequest = onDismiss) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surface + ) + ) { + Column( + modifier = Modifier.padding(24.dp) + ) { + Text( + text = "选择主题模式", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onSurface + ) + + Spacer(modifier = Modifier.height(20.dp)) + + // 明亮模式 + ThemeModeDialogOption( + label = "☀️ 明亮", + description = "始终使用明亮主题", + selected = currentMode == ThemeMode.LIGHT, + onClick = { onModeSelected(ThemeMode.LIGHT) } + ) + + Spacer(modifier = Modifier.height(8.dp)) + + // 暗黑模式 + ThemeModeDialogOption( + label = "🌙 暗黑", + description = "始终使用暗黑主题", + selected = currentMode == ThemeMode.DARK, + onClick = { onModeSelected(ThemeMode.DARK) } + ) + + Spacer(modifier = Modifier.height(8.dp)) + + // 跟随系统 + ThemeModeDialogOption( + label = "🔄 跟随系统", + description = "自动跟随系统主题设置", + selected = currentMode == ThemeMode.SYSTEM, + onClick = { onModeSelected(ThemeMode.SYSTEM) } + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // 取消按钮 + TextButton( + onClick = onDismiss, + modifier = Modifier.align(Alignment.End) + ) { + Text("取消") + } + } + } + } +} + +/** + * 弹窗中的主题模式选项 + */ +@Composable +fun ThemeModeDialogOption( + label: String, + description: String, + selected: Boolean, + onClick: () -> Unit, +) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), + color = if (selected) { + MaterialTheme.colorScheme.primaryContainer + } else { + MaterialTheme.colorScheme.surfaceVariant + }, + onClick = onClick + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = label, + style = MaterialTheme.typography.bodyLarge, + fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal, + color = if (selected) { + MaterialTheme.colorScheme.onPrimaryContainer + } else { + MaterialTheme.colorScheme.onSurfaceVariant + } + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = description, + style = MaterialTheme.typography.bodySmall, + color = if (selected) { + MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.7f) + } else { + MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f) + } + ) + } + + RadioButton( + selected = selected, + onClick = null, + colors = RadioButtonDefaults.colors( + selectedColor = MaterialTheme.colorScheme.primary + ) + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/theme/Theme.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/theme/Theme.kt new file mode 100644 index 0000000..5022eaa --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/theme/Theme.kt @@ -0,0 +1,139 @@ +package com.taskttl.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +/** 浅色方案 - 优化后的配色方案(Material Design 3风格)*/ +private val LightColorScheme = lightColorScheme( + // 主色调 - 更鲜艳的紫蓝色系,提升视觉冲击力 + primary = Color(0xFF5B6FF8), + onPrimary = Color(0xFFFFFFFF), + primaryContainer = Color(0xFFE1E4FF), + onPrimaryContainer = Color(0xFF001A41), + + // 次要色 - 优化紫色系,更柔和协调 + secondary = Color(0xFF8B5FBF), + onSecondary = Color(0xFFFFFFFF), + secondaryContainer = Color(0xFFF3E8FF), + onSecondaryContainer = Color(0xFF2C0051), + + // 第三色 - 温暖的橙色系(用于倒计时等) + tertiary = Color(0xFFFF9500), + onTertiary = Color(0xFFFFFFFF), + tertiaryContainer = Color(0xFFFFE6CC), + onTertiaryContainer = Color(0xFF331F00), + + // 错误色 - 更现代的红色系 + error = Color(0xFFDC3545), + onError = Color(0xFFFFFFFF), + errorContainer = Color(0xFFFFE5E5), + onErrorContainer = Color(0xFF5F0010), + + // 背景色 - 更柔和的浅色背景 + background = Color(0xFFFAFBFC), + onBackground = Color(0xFF1A1C1E), + + // 表面色 - 纯净白色 + surface = Color(0xFFFFFFFF), + onSurface = Color(0xFF1A1C1E), + surfaceVariant = Color(0xFFE4E6EB), + onSurfaceVariant = Color(0xFF45464F), + + // 轮廓色 - 更细腻的边框 + outline = Color(0xFFCED0D6), + outlineVariant = Color(0xFFE4E6EB), + + // 其他 + scrim = Color(0xFF000000), + inverseSurface = Color(0xFF2F3033), + inverseOnSurface = Color(0xFFF1F0F4), + inversePrimary = Color(0xFFAAB4FF), + + // 表面容器色 - 更丰富的层次 + surfaceDim = Color(0xFFDBDCE0), + surfaceBright = Color(0xFFFFFFFF), + surfaceContainerLowest = Color(0xFFFFFFFF), + surfaceContainerLow = Color(0xFFF5F6FA), + surfaceContainer = Color(0xFFEFF0F5), + surfaceContainerHigh = Color(0xFFE9EAF0), + surfaceContainerHighest = Color(0xFFE3E4EA) +) + +/** 深色配色方案 - 优化后的深色模式(Material Design 3风格)*/ +private val DarkColorScheme = darkColorScheme( + // 主色调 - 柔和亮丽的紫蓝色系 + primary = Color(0xFFAAB4FF), + onPrimary = Color(0xFF001A41), + primaryContainer = Color(0xFF3D4DB8), + onPrimaryContainer = Color(0xFFE1E4FF), + + // 次要色 - 优雅的亮紫色系 + secondary = Color(0xFFD4BCFF), + onSecondary = Color(0xFF2C0051), + secondaryContainer = Color(0xFF5A3D85), + onSecondaryContainer = Color(0xFFF3E8FF), + + // 第三色 - 温暖的亮橙色系 + tertiary = Color(0xFFFFBB5C), + onTertiary = Color(0xFF331F00), + tertiaryContainer = Color(0xFFCC7700), + onTertiaryContainer = Color(0xFFFFE6CC), + + // 错误色 - 柔和的亮红色系 + error = Color(0xFFFF7782), + onError = Color(0xFF5F0010), + errorContainer = Color(0xFFB42734), + onErrorContainer = Color(0xFFFFE5E5), + + // 背景色 - OLED友好的纯黑 + background = Color(0xFF1A1C1E), + onBackground = Color(0xFFE3E2E6), + + // 表面色 - 稍亮的深色 + surface = Color(0xFF1A1C1E), + onSurface = Color(0xFFE3E2E6), + surfaceVariant = Color(0xFF45464F), + onSurfaceVariant = Color(0xFFC5C6D0), + + // 轮廓色 - 更好的边框可见度 + outline = Color(0xFF8F9099), + outlineVariant = Color(0xFF45464F), + + // 其他 + scrim = Color(0xFF000000), + inverseSurface = Color(0xFFE3E2E6), + inverseOnSurface = Color(0xFF2F3033), + inversePrimary = Color(0xFF5B6FF8), + + // 表面容器色 - 深色模式下的精细层次 + surfaceDim = Color(0xFF121316), + surfaceBright = Color(0xFF393B3F), + surfaceContainerLowest = Color(0xFF0D0E11), + surfaceContainerLow = Color(0xFF1A1C1E), + surfaceContainer = Color(0xFF1E2023), + surfaceContainerHigh = Color(0xFF282A2D), + surfaceContainerHighest = Color(0xFF333538) +) + +/** + * 应用主题 + * @param [darkTheme] 黑暗主题 + * @param [content] 内容 + */ +@Composable +fun AppTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit, +) { + val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme + + MaterialTheme( + colorScheme = colorScheme, + typography = mainTypography(), + content = content + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/ui/theme/Type.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/theme/Type.kt similarity index 100% rename from composeApp/src/commonMain/kotlin/com/taskttl/ui/theme/Type.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/common/theme/Type.kt diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/auth/AuthState.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/auth/AuthState.kt new file mode 100644 index 0000000..b87bfd6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/auth/AuthState.kt @@ -0,0 +1,50 @@ +package com.taskttl.presentation.features.auth + +import com.taskttl.core.base.BaseState + + +/** + * 身份验证状态 + * @author admin + * @date 2025/10/26 + * @constructor 创建[AuthState] + * @param [isLoading] 正在加载 + * @param [isProcessing] 正在处理 + * @param [error] 错误 + */ +data class AuthState( + override val isLoading: Boolean = false, + override val isProcessing: Boolean = false, + override val error: String? = null, +) : BaseState() + + +/** + * 身份验证意图 + * @author admin + * @date 2025/10/26 + * @constructor 创建[AuthIntent] + */ +sealed class AuthIntent { + /** + * 使用谷歌登录 + * @author admin + * @date 2025/10/26 + */ + object LoginWithGoogle: AuthIntent() + + /** + * 使用脸书登录 + * @author admin + * @date 2025/10/26 + */ + object LoginWithFacebook: AuthIntent() +} + +/** + * 身份验证效果 + * @author admin + * @date 2025/10/26 + * @constructor 创建[AuthEffect] + */ +sealed class AuthEffect {} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/auth/AuthViewModel.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/auth/AuthViewModel.kt new file mode 100644 index 0000000..13d33bb --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/auth/AuthViewModel.kt @@ -0,0 +1,37 @@ +package com.taskttl.presentation.features.auth + +import androidx.lifecycle.viewModelScope +import com.taskttl.core.base.BaseViewModel +import com.taskttl.domain.repository.AuthRepository +import kotlinx.coroutines.launch + +/** + * 身份验证视图模型 + * @author admin + * @date 2025/10/26 + * @constructor 创建[AuthViewModel] + */ +class AuthViewModel(private val authRepository: AuthRepository) : + BaseViewModel(AuthState()) { + + + public override fun handleIntent(intent: AuthIntent) { + when (intent) { + AuthIntent.LoginWithGoogle -> { + // TODO: 调用 Google 登录逻辑 + println("Google 登录触发") + viewModelScope.launch { + authRepository.loginWithGoogle() + } + } + + AuthIntent.LoginWithFacebook -> { + // TODO: 调用 Facebook 登录逻辑 + println("Facebook 登录触发") + viewModelScope.launch { + authRepository.loginWithFacebook() + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/auth/LoginScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/auth/LoginScreen.kt new file mode 100644 index 0000000..e2a9740 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/auth/LoginScreen.kt @@ -0,0 +1,252 @@ +package com.taskttl.presentation.features.auth + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.LocalIndication +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Facebook +import androidx.compose.material.icons.filled.Language +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +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 +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.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import coil3.compose.AsyncImage +import com.taskttl.presentation.common.components.AppHeader +import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.InternalResourceApi +import org.koin.compose.viewmodel.koinViewModel +import taskttl.composeapp.generated.resources.Res +import taskttl.composeapp.generated.resources.app_name + +@OptIn(ExperimentalMaterial3Api::class, InternalResourceApi::class) +@Composable +fun LoginScreen( + onNavigateBack: () -> Unit, + viewModel: AuthViewModel = koinViewModel(), +) { + Box( + modifier = Modifier.fillMaxSize() + ) { + Column( + modifier = Modifier.fillMaxSize(), + ) { + AppHeader( + title = Res.string.app_name, + showBack = true, + onBackClick = { onNavigateBack.invoke() } + ) + + // --- 动画状态 --- + val logoAlpha = remember { Animatable(0f) } + val logoScale = rememberInfiniteTransition().animateFloat( + initialValue = 0.95f, + targetValue = 1.05f, + animationSpec = infiniteRepeatable( + animation = tween(2000, easing = FastOutSlowInEasing), + repeatMode = RepeatMode.Reverse + ) + ) + + val cardOffset = remember { Animatable(200f) } + val cardAlpha = remember { Animatable(0f) } + + val googleButtonAlpha = remember { Animatable(0f) } + val facebookButtonAlpha = remember { Animatable(0f) } + val copyrightAlpha = remember { Animatable(0f) } + + // --- 启动动画顺序 --- + LaunchedEffect(Unit) { + // Logo 渐显 + logoAlpha.animateTo(1f, tween(500)) + // 卡片滑入 + launch { cardOffset.animateTo(0f, tween(700, easing = FastOutSlowInEasing)) } + launch { cardAlpha.animateTo(1f, tween(700)) } + // 按钮依次出现 + googleButtonAlpha.animateTo(1f, tween(400)) + facebookButtonAlpha.animateTo(1f, tween(400)) + // 版权淡入 + copyrightAlpha.animateTo(1f, tween(500)) + } + Column( + modifier = Modifier.fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceBetween + ) { + // --- 登录卡片 --- + Card( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 24.dp) + .graphicsLayer { + translationY = cardOffset.value + alpha = cardAlpha.value + }, + shape = RoundedCornerShape(24.dp), + elevation = CardDefaults.cardElevation(8.dp), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + // Logo + AsyncImage( + model = Res.getUri("drawable/ic_launcher.png"), + contentDescription = null, + modifier = Modifier + .size(120.dp) + .graphicsLayer { + alpha = logoAlpha.value + scaleX = logoScale.value + scaleY = logoScale.value + }, + contentScale = ContentScale.Fit + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "TaskTTL 登录", + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = "欢迎回来,请选择登录方式:", + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(24.dp)) + + AnimatedLoginButton( + icon = Icons.Default.Language, + text = "使用 Google 登录", + backgroundColor = Color(0xFFDB4437), + alpha = googleButtonAlpha.value, + onClick = { viewModel.handleIntent(AuthIntent.LoginWithGoogle) } + ) + + Spacer(modifier = Modifier.height(16.dp)) + + AnimatedLoginButton( + icon = Icons.Default.Facebook, + text = "使用 Facebook 登录", + backgroundColor = Color(0xFF1877F2), + alpha = facebookButtonAlpha.value, + onClick = { viewModel.handleIntent(AuthIntent.LoginWithFacebook) } + ) + } + } + + Text( + text = "© DevTTL Team. All rights reserved.", + style = MaterialTheme.typography.bodySmall, + textAlign = TextAlign.Center, + modifier = Modifier.padding(bottom = 8.dp) + ) + } + } + } +} + +@Composable +private fun AnimatedLoginButton( + icon: ImageVector, + text: String, + backgroundColor: Color, + alpha: Float = 1f, + onClick: () -> Unit, +) { + var pressed by remember { mutableStateOf(false) } + val scale by animateFloatAsState(if (pressed) 0.95f else 1f, tween(100)) + + Row( + modifier = Modifier + .fillMaxWidth() + .height(52.dp) + .graphicsLayer { scaleX = scale; scaleY = scale; this.alpha = alpha } + .background(color = backgroundColor, shape = RoundedCornerShape(12.dp)) + .clickable( + onClick = onClick, + onClickLabel = text, + interactionSource = remember { MutableInteractionSource() }, + indication = LocalIndication.current + ) + .pointerInput(Unit) { + detectTapGestures( + onPress = { + pressed = true + tryAwaitRelease() + pressed = false + } + ) + } + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.surface, + modifier = Modifier.size(22.dp) + ) + Spacer(modifier = Modifier.width(12.dp)) + Text( + text = text, + color = MaterialTheme.colorScheme.surface, + fontWeight = FontWeight.Medium, + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.weight(1f) + ) + } +} diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/category/CategoryEditorScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/editor/CategoryEditorScreen.kt similarity index 94% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/category/CategoryEditorScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/editor/CategoryEditorScreen.kt index 6b5eae4..56a0902 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/category/CategoryEditorScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/editor/CategoryEditorScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.category +package com.taskttl.presentation.features.category.editor import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background @@ -45,16 +45,16 @@ 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.LoadingOverlay import com.taskttl.core.utils.ToastUtils -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.CategoryType -import com.taskttl.data.state.CategoryEffect -import com.taskttl.data.state.CategoryIntent -import com.taskttl.data.viewmodel.CategoryViewModel -import com.taskttl.ui.components.AppHeader +import com.taskttl.domain.model.Category +import com.taskttl.domain.model.CategoryColor +import com.taskttl.domain.model.CategoryIcon +import com.taskttl.domain.model.CategoryType +import com.taskttl.presentation.common.components.AppHeader +import com.taskttl.presentation.common.components.LoadingOverlay +import com.taskttl.presentation.features.category.list.CategoryEffect +import com.taskttl.presentation.features.category.list.CategoryIntent +import com.taskttl.presentation.features.category.list.CategoryViewModel import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime import org.jetbrains.compose.resources.stringResource @@ -337,11 +337,11 @@ private fun IconOption(item: CategoryIcon, selected: Boolean, onClick: () -> Uni modifier = Modifier .size(48.dp) .clip(RoundedCornerShape(8.dp)) - .background(if (selected) MaterialTheme.colorScheme.primary.copy(alpha = 0.06f) else Color.White) + .background(if (selected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.surface) .border( BorderStroke( if (selected) 2.dp else 1.dp, - if (selected) MaterialTheme.colorScheme.primary else Color(0xFFE6E6E6) + if (selected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline ), RoundedCornerShape(8.dp) ) @@ -351,7 +351,7 @@ private fun IconOption(item: CategoryIcon, selected: Boolean, onClick: () -> Uni Icon( imageVector = item.icon, contentDescription = stringResource(item.displayNameRes), - tint = Color(0xFF666666) + tint = if (selected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant ) } } @@ -375,7 +375,7 @@ private fun ColorOption( .border( BorderStroke( if (selected) 2.dp else 0.dp, - if (selected) Color.Black else Color.Transparent + if (selected) MaterialTheme.colorScheme.primary else Color.Transparent ), CircleShape ) diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/category/CategoryScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/list/CategoryScreen.kt similarity index 90% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/category/CategoryScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/list/CategoryScreen.kt index a057b69..9fec21a 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/category/CategoryScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/list/CategoryScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.category +package com.taskttl.presentation.features.category.list import androidx.compose.animation.animateColorAsState @@ -53,20 +53,15 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp 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.LoadingOverlay import com.taskttl.core.utils.ToastUtils -import com.taskttl.data.local.model.Category -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.state.TaskIntent -import com.taskttl.data.viewmodel.CategoryViewModel -import com.taskttl.ui.components.AppHeader +import com.taskttl.domain.model.Category +import com.taskttl.domain.model.CategoryType +import com.taskttl.navigation.Routes +import com.taskttl.presentation.common.components.ActionButtonListItem +import com.taskttl.presentation.common.components.AppHeader +import com.taskttl.presentation.common.components.ErrorDialog +import com.taskttl.presentation.common.components.LoadingOverlay import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewmodel.koinViewModel import taskttl.composeapp.generated.resources.Res @@ -84,7 +79,7 @@ fun CategoryScreen( navController: NavHostController, onAddCategory: () -> Unit, onNavigateBack: () -> Unit, - viewModel: CategoryViewModel = koinViewModel() + viewModel: CategoryViewModel = koinViewModel(), ) { val state by viewModel.state.collectAsState() @@ -94,6 +89,7 @@ fun CategoryScreen( is CategoryEffect.ShowMessage -> { ToastUtils.show(effect.message) } + is CategoryEffect.NavigateBack -> { onNavigateBack.invoke() } @@ -114,7 +110,7 @@ fun CategoryScreen( Box( modifier = Modifier .fillMaxSize() - .background(Color.White) + .background(MaterialTheme.colorScheme.surface) ) { Column( modifier = Modifier.fillMaxSize() @@ -186,7 +182,7 @@ fun CategoryScreen( isOpen = isOpen, onOpenChange = {}, onEditClick = { - navController.navigate(Main.Settings.EditCategory(category.id)) + navController.navigate(Routes.Main.Settings.EditCategory(category.id)) }, onDeleteClick = { viewModel.handleIntent(CategoryIntent.DeleteCategory(category.id)) @@ -204,7 +200,7 @@ fun CategoryScreen( modifier = Modifier .align(Alignment.BottomEnd) .padding(20.dp), - containerColor = Color(0xFF667EEA) + containerColor = MaterialTheme.colorScheme.primary ) { Icon( imageVector = Icons.Default.Add, @@ -223,7 +219,7 @@ fun CategoryCardItem( onOpenChange: (Boolean) -> Unit, onEditClick: () -> Unit, onDeleteClick: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { ActionButtonListItem( modifier = Modifier @@ -236,7 +232,7 @@ fun CategoryCardItem( ) { Card( modifier = modifier.fillMaxWidth().background(Color.Transparent), - colors = CardDefaults.cardColors(containerColor = Color.White), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) ) { Row( @@ -313,8 +309,8 @@ fun CategoryCardItem( .size(32.dp) .aspectRatio(1f), colors = IconButtonDefaults.filledIconButtonColors( - containerColor = Color(0xffff1111), - contentColor = Color.White + containerColor = MaterialTheme.colorScheme.error, + contentColor = MaterialTheme.colorScheme.onError ) ) { Icon( @@ -329,7 +325,7 @@ fun CategoryCardItem( fun CategoryFilterTabs( selectedType: CategoryType, onSelected: (CategoryType) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { val containerShape = RoundedCornerShape(8.dp) val tabShape = RoundedCornerShape(6.dp) @@ -337,19 +333,17 @@ fun CategoryFilterTabs( Row( modifier = modifier .fillMaxWidth() - .background(color = Color(0xFFF5F5F5), shape = containerShape) - .border(width = 1.dp, color = Color(0xFFE0E0E0), shape = containerShape) + .background(color = MaterialTheme.colorScheme.surfaceContainer, shape = containerShape) + .border(width = 1.dp, color = MaterialTheme.colorScheme.outline, shape = containerShape) .padding(4.dp), verticalAlignment = Alignment.CenterVertically ) { CategoryType.entries.forEach { type -> val active = type == selectedType val textColor by animateColorAsState( - if (active) Color(0xFF333333) else Color( - 0xFF666666 - ) + if (active) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant ) - val backgroundColor by animateColorAsState(if (active) Color.White else Color.Transparent) + val backgroundColor by animateColorAsState(if (active) MaterialTheme.colorScheme.surface else Color.Transparent) val elevation = if (active) 4.dp else 0.dp Box( diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/state/CategoryState.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/list/CategoryState.kt similarity index 92% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/state/CategoryState.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/list/CategoryState.kt index 64184fd..20ec3ad 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/state/CategoryState.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/list/CategoryState.kt @@ -1,9 +1,9 @@ -package com.taskttl.data.state +package com.taskttl.presentation.features.category.list -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 +import com.taskttl.core.base.BaseState +import com.taskttl.domain.model.Category +import com.taskttl.domain.model.CategoryStatistics +import com.taskttl.domain.model.CategoryType /** * 类别状态 @@ -37,7 +37,7 @@ data class CategoryState( val showAddDialog: Boolean = false, val showEditDialog: Boolean = false, val showDeleteDialog: Boolean = false -): BaseUiState() +): BaseState() /** * 类别意图 diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/CategoryViewModel.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/list/CategoryViewModel.kt similarity index 96% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/CategoryViewModel.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/list/CategoryViewModel.kt index 7c66d7b..28a828b 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/CategoryViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/list/CategoryViewModel.kt @@ -1,13 +1,10 @@ -package com.taskttl.data.viewmodel +package com.taskttl.presentation.features.category.list 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 com.taskttl.core.base.BaseViewModel +import com.taskttl.domain.model.Category +import com.taskttl.domain.model.CategoryType +import com.taskttl.domain.repository.CategoryRepository import kotlinx.coroutines.launch import org.jetbrains.compose.resources.getString import taskttl.composeapp.generated.resources.Res diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/ui/components/CategoryCard.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/list/common/CategoryCard.kt similarity index 98% rename from composeApp/src/commonMain/kotlin/com/taskttl/ui/components/CategoryCard.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/list/common/CategoryCard.kt index 9aed2b1..0c7fe8e 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/ui/components/CategoryCard.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/category/list/common/CategoryCard.kt @@ -22,7 +22,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp -import com.taskttl.data.local.model.Category +import com.taskttl.domain.model.Category import org.jetbrains.compose.resources.stringResource @OptIn(ExperimentalMaterial3Api::class) diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/countdown/CountdownDetailScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/detail/CountdownDetailScreen.kt similarity index 87% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/countdown/CountdownDetailScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/detail/CountdownDetailScreen.kt index 5308d34..a6a432c 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/countdown/CountdownDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/detail/CountdownDetailScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.countdown +package com.taskttl.presentation.features.countdown.detail import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -29,17 +29,18 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Brush 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.Chip -import com.taskttl.core.ui.LoadingOverlay import com.taskttl.core.utils.DateUtils -import com.taskttl.data.state.CountdownEffect -import com.taskttl.data.viewmodel.CountdownViewModel -import com.taskttl.ui.components.AppHeader +import com.taskttl.presentation.common.components.AppHeader +import com.taskttl.presentation.common.components.Chip +import com.taskttl.presentation.common.components.LoadingOverlay +import com.taskttl.presentation.features.countdown.list.CountdownEffect +import com.taskttl.presentation.features.countdown.list.CountdownViewModel import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewmodel.koinViewModel import taskttl.composeapp.generated.resources.Res @@ -90,7 +91,7 @@ fun CountdownDetailScreen( Box( modifier = Modifier .fillMaxSize() - .background(Color.White) + .background(MaterialTheme.colorScheme.surface) ) { Column( modifier = Modifier.fillMaxSize() @@ -117,7 +118,7 @@ fun CountdownDetailScreen( .fillMaxWidth() .clip(RoundedCornerShape(20.dp)) .background( - brush = androidx.compose.ui.graphics.Brush.linearGradient( + brush = Brush.linearGradient( colors = listOf( countdown.category.color.backgroundColor, Color.Transparent @@ -132,7 +133,7 @@ fun CountdownDetailScreen( text = countdown.title, fontSize = 18.sp, fontWeight = FontWeight.ExtraBold, - color = Color(0xFF111111) + color = MaterialTheme.colorScheme.onSurface ) Spacer(modifier = Modifier.width(6.dp)) Chip(text = countdown.category.name) @@ -147,7 +148,7 @@ fun CountdownDetailScreen( Text( text = countdown.targetDate.toString(), fontSize = 14.sp, - color = Color(0xFF444444) + color = MaterialTheme.colorScheme.onSurfaceVariant ) } } @@ -161,13 +162,13 @@ fun CountdownDetailScreen( text = daysRemaining.toString(), fontSize = 44.sp, fontWeight = FontWeight.ExtraBold, - color = Color(0xFF111111) + color = MaterialTheme.colorScheme.onSurface ) Text( text = stringResource(Res.string.label_days), fontSize = 12.sp, fontWeight = FontWeight.SemiBold, - color = Color(0xFF666666) + color = MaterialTheme.colorScheme.onSurfaceVariant ) } } @@ -180,14 +181,14 @@ fun CountdownDetailScreen( text = stringResource(Res.string.event_description), fontWeight = FontWeight.SemiBold, fontSize = 14.sp, - color = Color(0xFF333333) + color = MaterialTheme.colorScheme.onSurface ) Spacer(modifier = Modifier.height(10.dp)) Box( modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(12.dp)) - .background(Color.White) + .background(MaterialTheme.colorScheme.surface) .padding(12.dp) ) { Column { @@ -208,7 +209,7 @@ fun CountdownDetailScreen( text = stringResource(Res.string.detail_information), fontWeight = FontWeight.SemiBold, fontSize = 14.sp, - color = Color(0xFF333333) + color = MaterialTheme.colorScheme.onSurface ) Spacer(modifier = Modifier.height(10.dp)) Column { @@ -217,7 +218,7 @@ fun CountdownDetailScreen( horizontalArrangement = Arrangement.spacedBy(10.dp) ) { InfoItem( - iconTint = Color(0xFF667EEA), + iconTint = MaterialTheme.colorScheme.primary, text = "${stringResource(Res.string.reminder)}:${ stringResource(countdown.notificationEnabled.displayNameRes) }", @@ -227,7 +228,7 @@ fun CountdownDetailScreen( Spacer(modifier = Modifier.height(10.dp)) Row(modifier = Modifier.fillMaxWidth()) { InfoItem( - iconTint = Color(0xFF999999), + iconTint = MaterialTheme.colorScheme.onSurfaceVariant, text = "${stringResource(Res.string.label_created_at)}${countdown.createdAt}", modifier = Modifier.fillMaxWidth() ) @@ -246,7 +247,7 @@ private fun InfoItem(iconTint: Color, text: String, modifier: Modifier = Modifie Row( modifier = modifier .clip(RoundedCornerShape(10.dp)) - .background(Color.White) + .background(MaterialTheme.colorScheme.surface) .padding(12.dp), verticalAlignment = Alignment.CenterVertically ) { @@ -257,6 +258,6 @@ private fun InfoItem(iconTint: Color, text: String, modifier: Modifier = Modifie .background(iconTint) ) {} Spacer(modifier = Modifier.width(10.dp)) - Text(text = text, fontSize = 13.sp, color = Color(0xFF555555)) + Text(text = text, fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/countdown/CountdownEditorScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/editor/CountdownEditorScreen.kt similarity index 93% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/countdown/CountdownEditorScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/editor/CountdownEditorScreen.kt index 4e0d4f7..659bc41 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/countdown/CountdownEditorScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/editor/CountdownEditorScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.countdown +package com.taskttl.presentation.features.countdown.editor import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -41,18 +41,18 @@ 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.LoadingOverlay import com.taskttl.core.utils.ToastUtils -import com.taskttl.data.local.model.Category -import com.taskttl.data.local.model.Countdown -import com.taskttl.data.local.model.ReminderFrequency -import com.taskttl.data.state.CountdownEffect -import com.taskttl.data.state.CountdownIntent -import com.taskttl.data.viewmodel.CountdownViewModel -import com.taskttl.ui.components.AppHeader +import com.taskttl.domain.model.Category +import com.taskttl.domain.model.Countdown +import com.taskttl.domain.model.ReminderFrequency +import com.taskttl.presentation.common.components.AppHeader +import com.taskttl.presentation.common.components.CompactDatePickerDialog +import com.taskttl.presentation.common.components.LoadingOverlay +import com.taskttl.presentation.features.countdown.list.CountdownEffect +import com.taskttl.presentation.features.countdown.list.CountdownIntent +import com.taskttl.presentation.features.countdown.list.CountdownViewModel import com.taskttl.ui.components.CategoryCard -import com.taskttl.ui.components.CompactDatePickerDialog import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime @@ -117,6 +117,12 @@ fun CountdownEditScreen( } } + LaunchedEffect(state.categories) { + if (state.categories.isNotEmpty() && state.selectedCategory == null) { + selectedCategory = state.categories.first() + } + } + LaunchedEffect(editingCountdown) { editingCountdown?.let { title = it.title diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/countdown/CountdownScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/list/CountdownScreen.kt similarity index 92% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/countdown/CountdownScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/list/CountdownScreen.kt index 6e51e65..b30f70d 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/countdown/CountdownScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/list/CountdownScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.countdown +package com.taskttl.presentation.features.countdown.list import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -49,24 +49,20 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow 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.ui.ErrorDialog -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.CountdownEffect -import com.taskttl.data.state.CountdownIntent -import com.taskttl.data.viewmodel.CountdownViewModel -import com.taskttl.ui.components.AppHeader -import com.taskttl.ui.components.CategoryFilter +import com.taskttl.domain.model.Countdown +import com.taskttl.navigation.Routes +import com.taskttl.presentation.common.components.AppHeader +import com.taskttl.presentation.common.components.CategoryFilter +import com.taskttl.presentation.common.components.ErrorDialog +import com.taskttl.presentation.common.components.LoadingOverlay import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview import org.koin.compose.viewmodel.koinViewModel @@ -112,7 +108,7 @@ fun CountdownScreen( Box( modifier = Modifier .fillMaxSize() - .background(Color.White) + .background(MaterialTheme.colorScheme.surface) ) { Column(modifier = Modifier.fillMaxSize()) { AppHeader( @@ -211,7 +207,7 @@ fun CountdownScreen( modifier = Modifier .align(Alignment.BottomEnd) .padding(20.dp), - containerColor = Color(0xFF667EEA) + containerColor = MaterialTheme.colorScheme.primary ) { Icon( imageVector = Icons.Default.Add, @@ -235,7 +231,7 @@ fun CountdownCard( modifier = Modifier .fillMaxWidth() .shadow(4.dp, RoundedCornerShape(12.dp)) - .background(Color.White, RoundedCornerShape(12.dp)) + .background(MaterialTheme.colorScheme.surface, RoundedCornerShape(12.dp)) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null @@ -261,21 +257,21 @@ fun CountdownCard( text = countdown.title, fontSize = 18.sp, fontWeight = FontWeight.SemiBold, - color = Color(0xFF1A1A1A) + color = MaterialTheme.colorScheme.onSurface ) Spacer(modifier = Modifier.height(4.dp)) Row(verticalAlignment = Alignment.CenterVertically) { Icon( imageVector = Icons.Default.CalendarToday, contentDescription = null, - tint = Color(0xFF666666), + tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(14.dp), ) Spacer(modifier = Modifier.width(6.dp)) Text( text = countdown.targetDate.toString(), fontSize = 14.sp, - color = Color(0xFF666666) + color = MaterialTheme.colorScheme.onSurfaceVariant ) } } @@ -294,7 +290,7 @@ fun CountdownCard( text = stringResource(Res.string.label_days), fontSize = 12.sp, fontWeight = FontWeight.Medium, - color = Color(0xFF999999) + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f) ) } } @@ -309,7 +305,7 @@ fun CountdownCard( Text( text = countdown.description, fontSize = 13.sp, - color = Color(0xFF999999), + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f), maxLines = 2, overflow = TextOverflow.Ellipsis ) @@ -375,7 +371,7 @@ fun CountdownCard( text = { Text( text = stringResource(Res.string.delete), - color = Color.Red + color = MaterialTheme.colorScheme.error ) }, onClick = { @@ -404,14 +400,14 @@ fun IconBut(onClick: () -> Unit = {}, icon: ImageVector) { modifier = Modifier .size(32.dp) .clip(CircleShape) - .background(Color(0xFFF5F5F5)) + .background(MaterialTheme.colorScheme.surfaceContainer) .clickable { onClick() }, contentAlignment = Alignment.Center ) { Icon( imageVector = icon, contentDescription = null, - tint = Color(0xFF666666), + tint = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.size(20.dp) ) } @@ -526,19 +522,19 @@ private fun ActionItem( .fillMaxWidth() .clip(RoundedCornerShape(8.dp)) .clickable { onClick() } - .background(if (danger) Color(0xFFFFE6E6) else Color(0xFFF5F5F5)) + .background(if (danger) MaterialTheme.colorScheme.errorContainer else MaterialTheme.colorScheme.surfaceContainer) .padding(horizontal = 12.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically ) { Icon( imageVector = icon, contentDescription = null, - tint = if (danger) Color(0xFFE53E3E) else Color(0xFF555555) + tint = if (danger) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurfaceVariant ) Spacer(modifier = Modifier.width(12.dp)) Text( text = text, - color = if (danger) Color(0xFFE53E3E) else Color(0xFF333333), + color = if (danger) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyMedium ) } diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/state/CountdownState.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/list/CountdownState.kt similarity index 89% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/state/CountdownState.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/list/CountdownState.kt index fb6b08b..89d0c64 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/state/CountdownState.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/list/CountdownState.kt @@ -1,8 +1,8 @@ -package com.taskttl.data.state +package com.taskttl.presentation.features.countdown.list -import com.taskttl.core.viewmodel.BaseUiState -import com.taskttl.data.local.model.Category -import com.taskttl.data.local.model.Countdown +import com.taskttl.core.base.BaseState +import com.taskttl.domain.model.Category +import com.taskttl.domain.model.Countdown /** * 倒数日状态 @@ -26,7 +26,7 @@ data class CountdownState( val editingCountdown: Countdown? = null, val filteredCountdowns: List = emptyList(), val selectedCategory: Category? = null, -): BaseUiState() +): BaseState() /** * 倒数日意图 diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/CountdownViewModel.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/list/CountdownViewModel.kt similarity index 89% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/CountdownViewModel.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/list/CountdownViewModel.kt index ca2c5d4..2087ac4 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/CountdownViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/countdown/list/CountdownViewModel.kt @@ -1,19 +1,12 @@ -package com.taskttl.data.viewmodel +package com.taskttl.presentation.features.countdown.list import androidx.lifecycle.viewModelScope -import com.taskttl.core.notification.NotificationManager -import com.taskttl.core.notification.NotificationPayload -import com.taskttl.core.notification.NotificationRepeatType -import com.taskttl.core.utils.DateUtils.toEpochMillis -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 -import com.taskttl.data.repository.CategoryRepository -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 com.taskttl.core.base.BaseViewModel +import com.taskttl.domain.model.Category +import com.taskttl.domain.model.CategoryType +import com.taskttl.domain.model.Countdown +import com.taskttl.domain.repository.CategoryRepository +import com.taskttl.domain.repository.CountdownRepository import kotlinx.coroutines.launch import org.jetbrains.compose.resources.getString import taskttl.composeapp.generated.resources.Res diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/onboarding/OnboardingScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/onboarding/OnboardingScreen.kt similarity index 85% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/onboarding/OnboardingScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/onboarding/OnboardingScreen.kt index 44d0019..2d709ea 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/onboarding/OnboardingScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/onboarding/OnboardingScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.onboarding +package com.taskttl.presentation.features.onboarding import androidx.compose.foundation.background @@ -27,19 +27,14 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.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.notification.NotificationPermissionCallback -import com.taskttl.core.notification.NotificationPermissionManager -import com.taskttl.core.routes.Routes -import com.taskttl.core.utils.LogUtils -import com.taskttl.data.local.model.OnboardingPage -import com.taskttl.data.state.OnboardingEffect -import com.taskttl.data.state.OnboardingIntent -import com.taskttl.data.viewmodel.OnboardingViewModel +import com.taskttl.core.permission.ExactAlarmPermissionManager +import com.taskttl.core.permission.NotificationPermissionManager +import com.taskttl.domain.model.OnboardingPage +import com.taskttl.navigation.Routes import kotlinx.coroutines.flow.collectLatest import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview @@ -68,12 +63,8 @@ fun OnboardingScreen( LaunchedEffect(Unit) { kotlinx.coroutines.delay(300) - NotificationPermissionManager.requestPermission( - object : NotificationPermissionCallback { - override fun onGranted() = LogUtils.e("DevTTL", "✅ Android 通知权限已授予") - override fun onDenied() = LogUtils.e("DevTTL", "❌ Android 通知权限被拒绝") - } - ) + NotificationPermissionManager.requestPermission() + ExactAlarmPermissionManager.requestPermission() } LaunchedEffect(Unit) { @@ -101,7 +92,7 @@ fun OnboardingScreen( Text( stringResource(Res.string.skip_text) + ">", fontSize = 16.sp, - color = Color.Black.copy(alpha = 0.6f) + color = MaterialTheme.colorScheme.onSurfaceVariant ) } @@ -143,12 +134,12 @@ fun OnboardingScreen( } }, modifier = Modifier.fillMaxWidth(), - colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF667EEA)), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), shape = MaterialTheme.shapes.medium ) { Text( stringResource(Res.string.continue_text), - color = Color.White, + color = MaterialTheme.colorScheme.onPrimary, fontSize = 18.sp ) } @@ -160,12 +151,12 @@ fun OnboardingScreen( Button( onClick = { viewModel.processIntent(OnboardingIntent.MarkOnboardingCompleted) }, modifier = Modifier.fillMaxWidth(), - colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF667EEA)), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), shape = MaterialTheme.shapes.medium ) { Text( stringResource(Res.string.get_start_text), - color = Color.White, + color = MaterialTheme.colorScheme.onPrimary, fontSize = 18.sp ) } @@ -195,7 +186,7 @@ fun OnboardingPage(pageData: OnboardingPage) { text = stringResource(pageData.titleRes), fontSize = 28.sp, fontWeight = FontWeight.Bold, - color = Color(0xFF333333), + color = MaterialTheme.colorScheme.onSurface, textAlign = TextAlign.Center, modifier = Modifier.padding(bottom = 20.dp) ) @@ -203,7 +194,7 @@ fun OnboardingPage(pageData: OnboardingPage) { Text( text = stringResource(pageData.descRes), fontSize = 16.sp, - color = Color(0xFF666666), + color = MaterialTheme.colorScheme.onSurfaceVariant, lineHeight = 24.sp, textAlign = TextAlign.Center, modifier = Modifier.padding(horizontal = 16.dp) @@ -217,7 +208,7 @@ fun IndicatorDot(active: Boolean) { modifier = Modifier .size(12.dp) .background( - color = if (active) Color(0xFF667EEA) else Color(0xFFDDDDDD), + color = if (active) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline, shape = CircleShape ) ) diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/state/OnboardingState.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/onboarding/OnboardingState.kt similarity index 85% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/state/OnboardingState.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/onboarding/OnboardingState.kt index 9016723..554e301 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/state/OnboardingState.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/onboarding/OnboardingState.kt @@ -1,7 +1,7 @@ -package com.taskttl.data.state +package com.taskttl.presentation.features.onboarding -import com.taskttl.core.viewmodel.BaseUiState -import com.taskttl.data.local.model.OnboardingPage +import com.taskttl.core.base.BaseState +import com.taskttl.domain.model.OnboardingPage /** * 引导状态 @@ -14,7 +14,7 @@ data class OnboardingState( override val isProcessing: Boolean = false, override val error: String? = null, val pages: List = OnboardingPage.entries, -) : BaseUiState() +) : BaseState() /** * 引导意图 diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/OnboardingViewModel.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/onboarding/OnboardingViewModel.kt similarity index 81% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/OnboardingViewModel.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/onboarding/OnboardingViewModel.kt index 959c899..93498fa 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/OnboardingViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/onboarding/OnboardingViewModel.kt @@ -1,12 +1,9 @@ -package com.taskttl.data.viewmodel +package com.taskttl.presentation.features.onboarding 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.OnboardingEffect -import com.taskttl.data.state.OnboardingIntent -import com.taskttl.data.state.OnboardingState +import com.taskttl.core.base.BaseViewModel +import com.taskttl.domain.repository.CategoryRepository +import com.taskttl.domain.repository.OnboardingRepository import kotlinx.coroutines.launch /** @@ -52,4 +49,4 @@ class OnboardingViewModel( } } } -} +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/AboutScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/about/AboutScreen.kt similarity index 93% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/AboutScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/about/AboutScreen.kt index 80fefda..8d8153f 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/AboutScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/about/AboutScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.settings +package com.taskttl.presentation.features.settings.about import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -20,6 +20,7 @@ import androidx.compose.material.icons.automirrored.filled.Assignment import androidx.compose.material.icons.filled.Email import androidx.compose.material.icons.filled.Language import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -27,14 +28,13 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.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 com.taskttl.presentation.common.components.AppHeader +import com.taskttl.presentation.features.settings.main.SettingsIntent +import com.taskttl.presentation.features.settings.main.SettingsViewModel import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewmodel.koinViewModel @@ -54,13 +54,6 @@ 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 -import taskttl.composeapp.generated.resources.tech_stack_koin -import taskttl.composeapp.generated.resources.tech_stack_ktor -import taskttl.composeapp.generated.resources.tech_stack_mvi -import taskttl.composeapp.generated.resources.tech_stack_room import taskttl.composeapp.generated.resources.title_about import taskttl.composeapp.generated.resources.version import taskttl.composeapp.generated.resources.web_text @@ -121,7 +114,8 @@ fun AboutScreen( // 版本信息 Card( - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), ) { Column( modifier = Modifier.padding(16.dp) @@ -140,7 +134,8 @@ fun AboutScreen( Spacer(modifier = Modifier.height(16.dp)) // 应用描述 Card( - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), ) { Column( modifier = Modifier.padding(16.dp) @@ -187,7 +182,8 @@ fun AboutScreen( // Spacer(modifier = Modifier.height(16.dp)) // 开发者信息 Card( - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), ) { Column( modifier = Modifier.padding(16.dp) @@ -209,7 +205,8 @@ fun AboutScreen( Spacer(modifier = Modifier.height(16.dp)) // 联系方式 Card( - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), ) { Column( modifier = Modifier.padding(16.dp) diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/DataManagementScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/dataManagement/DataManagementScreen.kt similarity index 98% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/DataManagementScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/dataManagement/DataManagementScreen.kt index 380d30c..e8839d5 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/DataManagementScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/dataManagement/DataManagementScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.settings +package com.taskttl.presentation.features.settings.dataManagement import androidx.compose.foundation.background @@ -40,10 +40,9 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.dp -import com.taskttl.ui.components.AppHeader +import com.taskttl.presentation.common.components.AppHeader import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.stringResource import taskttl.composeapp.generated.resources.Res @@ -86,7 +85,7 @@ fun DataManagementScreen( Box( modifier = Modifier .fillMaxSize() - .background(Color.White) + .background(MaterialTheme.colorScheme.surface) ) { Column( modifier = Modifier.fillMaxSize() diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/FeedbackScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/feedback/FeedbackScreen.kt similarity index 94% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/FeedbackScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/feedback/FeedbackScreen.kt index a90e46d..0c59c0c 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/FeedbackScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/feedback/FeedbackScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.settings +package com.taskttl.presentation.features.settings.feedback import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background @@ -41,16 +41,12 @@ import androidx.compose.ui.graphics.vector.ImageVector 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.LoadingOverlay import com.taskttl.core.utils.ToastUtils -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.TaskIntent -import com.taskttl.data.viewmodel.FeedbackViewModel -import com.taskttl.ui.components.AppHeader +import com.taskttl.data.source.remote.dto.request.FeedbackReq +import com.taskttl.presentation.common.components.AppHeader +import com.taskttl.presentation.common.components.ErrorDialog +import com.taskttl.presentation.common.components.LoadingOverlay import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewmodel.koinViewModel import taskttl.composeapp.generated.resources.Res @@ -119,7 +115,7 @@ fun FeedbackScreen( // 反馈类型 Card( modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = Color.White), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), shape = RoundedCornerShape(12.dp), elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) ) { @@ -148,7 +144,7 @@ fun FeedbackScreen( // 问题描述 Card( modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = Color.White), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), shape = RoundedCornerShape(12.dp), elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) ) { @@ -173,7 +169,7 @@ fun FeedbackScreen( // 联系方式 Card( modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = Color.White), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), shape = RoundedCornerShape(12.dp), elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) ) { @@ -231,8 +227,8 @@ private fun FeedbackTypeOption( icon: ImageVector, modifier: Modifier = Modifier ) { - val borderColor = if (selected) MaterialTheme.colorScheme.primary else Color.LightGray - val bgColor = if (selected) Color(0xFFF0F4FF) else Color.Transparent + val borderColor = if (selected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline + val bgColor = if (selected) MaterialTheme.colorScheme.primaryContainer else Color.Transparent val textColor = if (selected) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/state/FeedbackState.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/feedback/FeedbackState.kt similarity index 83% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/state/FeedbackState.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/feedback/FeedbackState.kt index c9ebd16..eb191a8 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/state/FeedbackState.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/feedback/FeedbackState.kt @@ -1,13 +1,13 @@ -package com.taskttl.data.state +package com.taskttl.presentation.features.settings.feedback -import com.taskttl.core.viewmodel.BaseUiState -import com.taskttl.data.network.domain.req.FeedbackReq +import com.taskttl.core.base.BaseState +import com.taskttl.data.source.remote.dto.request.FeedbackReq data class FeedbackState( override val isLoading: Boolean = false, override val isProcessing: Boolean = false, override val error: String? = null, -) : BaseUiState() +) : BaseState() sealed class FeedbackIntent { /** diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/FeedbackViewModel.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/feedback/FeedbackViewModel.kt similarity index 83% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/FeedbackViewModel.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/feedback/FeedbackViewModel.kt index b355245..47ec187 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/FeedbackViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/feedback/FeedbackViewModel.kt @@ -1,12 +1,9 @@ -package com.taskttl.data.viewmodel +package com.taskttl.presentation.features.settings.feedback 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 com.taskttl.core.base.BaseViewModel +import com.taskttl.data.source.remote.api.TaskTTLApi +import com.taskttl.data.source.remote.dto.request.FeedbackReq import kotlinx.coroutines.launch import org.jetbrains.compose.resources.getString import taskttl.composeapp.generated.resources.Res diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/main/SettingsScreen.kt similarity index 56% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/SettingsScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/main/SettingsScreen.kt index 06aa4e5..dfa5912 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/main/SettingsScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.settings +package com.taskttl.presentation.features.settings.main import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons @@ -30,19 +31,24 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState 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.graphics.Brush 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 androidx.compose.ui.unit.sp import androidx.navigation.NavHostController -import com.taskttl.core.notification.NotificationPermissionManager -import com.taskttl.core.routes.Routes -import com.taskttl.data.state.SettingsIntent -import com.taskttl.data.viewmodel.SettingsViewModel -import com.taskttl.ui.components.AppHeader +import com.taskttl.core.manager.ThemeMode +import com.taskttl.core.permission.NotificationPermissionManager +import com.taskttl.navigation.Routes +import com.taskttl.presentation.common.components.AppHeader +import com.taskttl.presentation.common.components.ThemeModeDialog +import kotlinx.coroutines.delay import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview @@ -55,6 +61,10 @@ import taskttl.composeapp.generated.resources.setting_about_app import taskttl.composeapp.generated.resources.setting_about_app_desc import taskttl.composeapp.generated.resources.setting_category_management import taskttl.composeapp.generated.resources.setting_category_management_desc +import taskttl.composeapp.generated.resources.setting_dark_mode +import taskttl.composeapp.generated.resources.setting_dark_mode_desc +import taskttl.composeapp.generated.resources.setting_data_management +import taskttl.composeapp.generated.resources.setting_data_management_desc import taskttl.composeapp.generated.resources.setting_feedback import taskttl.composeapp.generated.resources.setting_feedback_desc import taskttl.composeapp.generated.resources.setting_privacy_policy @@ -77,6 +87,8 @@ fun SettingsScreen( ) { val state by viewModel.state.collectAsState() + var showThemeDialog by remember { mutableStateOf(false) } + // 轮询监听权限状态变化 LaunchedEffect(Unit) { while (true) { @@ -84,14 +96,14 @@ fun SettingsScreen( if (state.isNotification != current) { viewModel.handleIntent(SettingsIntent.UpdateNotificationStatus(current)) } - kotlinx.coroutines.delay(2000) + delay(2000) } } Box( modifier = Modifier .fillMaxSize() - .background(Color.White) + .background(MaterialTheme.colorScheme.surface) ) { Column( modifier = Modifier.fillMaxSize() @@ -109,46 +121,87 @@ fun SettingsScreen( .padding(16.dp) ) { // 用户信息卡片 - // Column( - // modifier = Modifier - // .fillMaxWidth() - // .background( - // brush = Brush.linearGradient( - // colors = listOf(Color(0xFF667EEA), Color(0xFF764BA2)) - // ), - // shape = RoundedCornerShape(16.dp) - // ) - // .padding(20.dp), - // horizontalAlignment = Alignment.CenterHorizontally - // ) { - // Box( - // modifier = Modifier - // .size(60.dp) - // .background(Color.White.copy(alpha = 0.2f), shape = CircleShape), - // contentAlignment = Alignment.Center - // ) { - // Icon( - // imageVector = Icons.Default.Person, - // contentDescription = "用户头像", - // tint = Color.White - // ) - // } - // Spacer(modifier = Modifier.height(12.dp)) - // Text( - // "TaskMaster 用户", - // color = Color.White, - // fontWeight = FontWeight.Medium, - // fontSize = 18.sp - // ) - // Text( - // "已使用 30 天 · 完成 156 个任务", - // color = Color.White.copy(alpha = 0.9f), - // fontSize = 14.sp - // ) - // } - // - // Spacer(modifier = Modifier.height(24.dp)) + Column( + modifier = Modifier + .fillMaxWidth() + .background( + brush = Brush.linearGradient( + colors = listOf( + MaterialTheme.colorScheme.primary, + MaterialTheme.colorScheme.secondary + ) + ), + shape = RoundedCornerShape(16.dp) + ) + .padding(20.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Row( + modifier = Modifier.clickable { navController.navigate(Routes.Main.Settings.Login) }, + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(60.dp) + .background(Color.White.copy(alpha = 0.2f), shape = CircleShape), + contentAlignment = Alignment.Center + ) { + Icon( + imageVector = Icons.Default.Person, + contentDescription = "用户头像", + tint = Color.White, + modifier = Modifier.size(40.dp) + ) + // AsyncImage( + // model = Res.getUri("drawable/ic_launcher.png"), + // contentDescription = null, + // modifier = Modifier + // .size(120.dp), + // contentScale = ContentScale.Fit + // ) + } + Spacer(modifier = Modifier.width(12.dp)) + Column( + modifier = Modifier.weight(1f).height(60.dp), + horizontalAlignment = Alignment.End, + verticalArrangement = Arrangement.SpaceBetween + ) { + Text( + "登录或注册", + color = Color.White, + fontWeight = FontWeight.Medium, + fontSize = 18.sp + ) + Text( + "加入TaskTTL", + color = Color.White.copy(alpha = 0.9f), + fontSize = 14.sp + ) + } + + // Column( + // modifier = Modifier.weight(1f).height(60.dp), + // horizontalAlignment = Alignment.End, + // verticalArrangement = Arrangement.SpaceBetween + // ) { + // Text( + // "昵称", + // color = Color.White, + // fontWeight = FontWeight.Medium, + // fontSize = 18.sp + // ) + // Text( + // "已使用 30 天 · 完成 156 个任务", + // color = Color.White.copy(alpha = 0.9f), + // fontSize = 14.sp + // ) + // } + } + } + + Spacer(modifier = Modifier.height(24.dp)) // 通用设置 SectionTitle(Icons.Default.Settings, Res.string.section_general_settings) @@ -161,15 +214,18 @@ fun SettingsScreen( onSwitchChanged = { viewModel.handleIntent(SettingsIntent.RequestPermission) } ) - // var darkMode by remember { mutableStateOf(false) } - // - // SettingItem( - // titleRes = Res.string.setting_dark_mode, - // descriptionRes = Res.string.setting_dark_mode_desc, - // showSwitch = true, - // switchState = darkMode, - // onSwitchChanged = { darkMode = it } - // ) + + SettingItem( + titleRes = Res.string.setting_dark_mode, + descriptionRes = Res.string.setting_dark_mode_desc, + trailingText = when (state.themeMode) { + ThemeMode.LIGHT -> "明亮" + ThemeMode.DARK -> "暗黑" + ThemeMode.SYSTEM -> "跟随系统" + }, + showArrow = true, + onClick = { showThemeDialog = true } + ) // SettingItem( // titleRes = Res.string.setting_language, @@ -188,12 +244,12 @@ fun SettingsScreen( navController.navigate(Routes.Main.Settings.CategoryManagement) } ) - // SettingItem( - // titleRes = Res.string.setting_data_management, - // descriptionRes = Res.string.setting_data_management_desc, - // showArrow = true, - // onClick = { navController.navigate(Routes.Main.Settings.DataManagement) } - // ) + SettingItem( + titleRes = Res.string.setting_data_management, + descriptionRes = Res.string.setting_data_management_desc, + showArrow = true, + onClick = { navController.navigate(Routes.Main.Settings.DataManagement) } + ) Spacer(modifier = Modifier.height(16.dp)) // // 社交分享 @@ -240,6 +296,17 @@ fun SettingsScreen( Spacer(modifier = Modifier.height(24.dp)) } } + // 主题选择弹窗 + if (showThemeDialog) { + ThemeModeDialog( + currentMode = state.themeMode, + onModeSelected = { mode -> + viewModel.handleIntent(SettingsIntent.SwitchThemeMode(mode)) + showThemeDialog = false + }, + onDismiss = { showThemeDialog = false } + ) + } } } @@ -257,7 +324,7 @@ fun SectionTitle(icon: ImageVector, titleRes: StringResource) { Icon( icon, contentDescription = null, - tint = Color(0xFF667EEA), + tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(18.dp) ) Spacer(modifier = Modifier.width(8.dp)) @@ -265,7 +332,7 @@ fun SectionTitle(icon: ImageVector, titleRes: StringResource) { stringResource(titleRes), fontWeight = FontWeight.Bold, fontSize = 16.sp, - color = Color(0xFF333333) + color = MaterialTheme.colorScheme.onSurface ) } } @@ -285,6 +352,7 @@ fun SectionTitle(icon: ImageVector, titleRes: StringResource) { fun SettingItem( titleRes: StringResource, descriptionRes: StringResource, + trailingText: String? = null, showSwitch: Boolean = false, switchState: Boolean = false, onSwitchChanged: ((Boolean) -> Unit)? = null, @@ -296,7 +364,7 @@ fun SettingItem( modifier = modifier .fillMaxWidth() .clickable(enabled = onClick != null) { onClick?.invoke() } - .background(Color.White, shape = RoundedCornerShape(12.dp)) + .background(MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(12.dp)) .padding(horizontal = 16.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween @@ -306,22 +374,39 @@ fun SettingItem( text = stringResource(titleRes), fontWeight = FontWeight.Medium, fontSize = 16.sp, - color = Color(0xFF333333) + color = MaterialTheme.colorScheme.onSurface ) descriptionRes.let { Spacer(modifier = Modifier.height(2.dp)) - Text(stringResource(it), fontSize = 14.sp, color = Color(0xFF666666)) + Text(stringResource(it), fontSize = 14.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) } } - if (showSwitch && onSwitchChanged != null) { - Switch(checked = switchState, onCheckedChange = onSwitchChanged) - } else if (showArrow) { - Icon( - imageVector = Icons.Default.ChevronRight, - contentDescription = null, - tint = Color(0xFFCCCCCC) - ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End + ) { + // 显示右侧文字 + if (trailingText != null) { + Text( + text = trailingText, + fontSize = 14.sp, + color = MaterialTheme.colorScheme.primary, + fontWeight = FontWeight.Medium + ) + if (showArrow) { + Spacer(modifier = Modifier.width(4.dp)) + } + } + if (showSwitch && onSwitchChanged != null) { + Switch(checked = switchState, onCheckedChange = onSwitchChanged) + } else if (showArrow) { + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = null, + tint = MaterialTheme.colorScheme.outline + ) + } } } diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/state/SettingsState.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/main/SettingsState.kt similarity index 73% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/state/SettingsState.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/main/SettingsState.kt index 14255be..f12efd6 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/state/SettingsState.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/main/SettingsState.kt @@ -1,7 +1,8 @@ -package com.taskttl.data.state +package com.taskttl.presentation.features.settings.main -import com.taskttl.core.notification.NotificationPermissionManager -import com.taskttl.core.viewmodel.BaseUiState +import com.taskttl.core.base.BaseState +import com.taskttl.core.manager.ThemeMode +import com.taskttl.core.permission.NotificationPermissionManager /** * 设置状态 @@ -15,8 +16,10 @@ data class SettingsState( override val isLoading: Boolean = false, override val isProcessing: Boolean = false, override val error: String? = null, + val themeMode: ThemeMode = ThemeMode.SYSTEM, + val showThemeDialog: Boolean = false, val isNotification: Boolean = NotificationPermissionManager.verifyPermission(), -) : BaseUiState() +) : BaseState() /** * 设置意图 @@ -25,6 +28,8 @@ data class SettingsState( * @constructor 创建[SettingsIntent] */ sealed class SettingsIntent { + object LoadTheme : SettingsIntent() + /** * 打开应用评分 * @author DevTTL @@ -41,6 +46,15 @@ sealed class SettingsIntent { */ class OpenUrl(val url: String) : SettingsIntent() + /** + * 切换主题模式 + * @author admin + * @date 2025/10/27 + * @constructor 创建[SwitchThemeMode] + * @param [themeMode] 主题模式 + */ + class SwitchThemeMode(val themeMode: ThemeMode) : SettingsIntent() + object RequestPermission : SettingsIntent() /** diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/SettingsViewModel.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/main/SettingsViewModel.kt similarity index 52% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/SettingsViewModel.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/main/SettingsViewModel.kt index 5bf968b..8b6c631 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/SettingsViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/main/SettingsViewModel.kt @@ -1,14 +1,12 @@ -package com.taskttl.data.viewmodel +package com.taskttl.presentation.features.settings.main import androidx.lifecycle.viewModelScope -import com.taskttl.core.notification.NotificationPermissionCallback -import com.taskttl.core.notification.NotificationPermissionManager +import com.taskttl.core.base.BaseViewModel +import com.taskttl.core.manager.ThemeManager +import com.taskttl.core.manager.ThemeMode +import com.taskttl.core.permission.ExactAlarmPermissionManager +import com.taskttl.core.permission.NotificationPermissionManager import com.taskttl.core.utils.ExternalAppLauncher -import com.taskttl.core.utils.LogUtils -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.launch /** @@ -17,14 +15,23 @@ import kotlinx.coroutines.launch * @date 2025/10/12 * @constructor 创建[SettingsViewModel] */ -class SettingsViewModel() : +class SettingsViewModel(private val themeManager: ThemeManager) : BaseViewModel(SettingsState()) { + init { + // 监听主题模式变化 + viewModelScope.launch { + themeManager.themeMode.collect { mode -> updateState { copy(themeMode = mode) } } + } + } + public override fun handleIntent(intent: SettingsIntent) { when (intent) { + is SettingsIntent.LoadTheme -> loadTheme() is SettingsIntent.OpenAppRating -> openAppRating() is SettingsIntent.OpenUrl -> openUrl(intent.url) + is SettingsIntent.SwitchThemeMode -> switchThemeMode(intent.themeMode) is SettingsIntent.RequestPermission -> requestPermission() is SettingsIntent.UpdateNotificationStatus -> updateNotificationStatus(intent.status) } @@ -42,18 +49,38 @@ class SettingsViewModel() : } } + /** + * 加载主题 + */ + private fun loadTheme() { + updateState { copy(themeMode = themeManager.themeMode.value) } + } + + /** + * 切换主题模式 + * @param [themeMode] 主题模式 + */ + private fun switchThemeMode(themeMode: ThemeMode) { + viewModelScope.launch { + updateState { copy(themeMode = themeMode) } + themeManager.setDarkTheme(themeMode) + } + } + private fun requestPermission() { viewModelScope.launch { if (NotificationPermissionManager.verifyPermission()) { NotificationPermissionManager.disablePermission() } else { - NotificationPermissionManager.requestPermission( - object : NotificationPermissionCallback { - override fun onGranted() = updateState { copy(isNotification = true) } - override fun onDenied() = LogUtils.e("DevTTL", "❌ Android 通知权限被拒绝") - } - ) + NotificationPermissionManager.requestPermission() } + + if (ExactAlarmPermissionManager.verifyPermission()) { + ExactAlarmPermissionManager.disablePermission() + } else { + ExactAlarmPermissionManager.requestPermission() + } + } } diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/PrivacyScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/privacy/PrivacyScreen.kt similarity index 88% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/PrivacyScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/privacy/PrivacyScreen.kt index 5737786..a96162e 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/settings/PrivacyScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/settings/privacy/PrivacyScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.settings +package com.taskttl.presentation.features.settings.privacy import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -7,9 +7,8 @@ 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 -import com.taskttl.core.ui.DevTTLWebView -import com.taskttl.ui.components.AppHeader +import com.taskttl.core.common.DevTTLWebView +import com.taskttl.presentation.common.components.AppHeader import org.jetbrains.compose.resources.stringResource import taskttl.composeapp.generated.resources.Res import taskttl.composeapp.generated.resources.privacy_url diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/splash/SplashScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/splash/SplashScreen.kt similarity index 88% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/splash/SplashScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/splash/SplashScreen.kt index 6235400..9205fc7 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/splash/SplashScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/splash/SplashScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.splash +package com.taskttl.presentation.features.splash import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.RepeatMode @@ -18,6 +18,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ListAlt 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 @@ -31,9 +32,7 @@ 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.routes.Routes -import com.taskttl.data.state.SplashEffect -import com.taskttl.data.viewmodel.SplashViewModel +import com.taskttl.navigation.Routes import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview import org.koin.compose.viewmodel.koinViewModel @@ -72,7 +71,10 @@ fun SplashScreen( .fillMaxSize() .background( Brush.linearGradient( - colors = listOf(Color(0xFF667eea), Color(0xFF764ba2)), + colors = listOf( + MaterialTheme.colorScheme.primary, + MaterialTheme.colorScheme.secondary + ), start = Offset(0f, 0f), end = Offset.Infinite ) @@ -94,6 +96,13 @@ fun SplashScreen( .padding(bottom = 20.dp) ) + // AsyncImage( + // model = Res.getUri("drawable/ic_launcher.png"), + // contentDescription = null, + // modifier = Modifier.size(200.dp).padding(bottom = 20.dp), + // contentScale = ContentScale.Fit + // ) + // 标题 Text( text = stringResource(Res.string.app_name), diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/state/SplashState.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/splash/SplashState.kt similarity index 85% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/state/SplashState.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/splash/SplashState.kt index 8110fd3..b1b7cb8 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/state/SplashState.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/splash/SplashState.kt @@ -1,6 +1,7 @@ -package com.taskttl.data.state +package com.taskttl.presentation.features.splash + +import com.taskttl.core.base.BaseState -import com.taskttl.core.viewmodel.BaseUiState /** * 启动页状态 @@ -12,7 +13,7 @@ data class SplashState( override val isLoading: Boolean = false, override val isProcessing: Boolean = false, override val error: String? = null, -) : BaseUiState() +) : BaseState() sealed class SplashIntent { object LoadApp : SplashIntent() diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/SplashViewModel.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/splash/SplashViewModel.kt similarity index 82% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/SplashViewModel.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/splash/SplashViewModel.kt index 47b237b..e567bc9 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/SplashViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/splash/SplashViewModel.kt @@ -1,15 +1,12 @@ -package com.taskttl.data.viewmodel +package com.taskttl.presentation.features.splash import androidx.lifecycle.viewModelScope +import com.taskttl.core.base.BaseViewModel import com.taskttl.core.domain.constant.PointEvent import com.taskttl.core.utils.DeviceUtils 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 com.taskttl.data.source.remote.api.TaskTTLApi +import com.taskttl.domain.repository.OnboardingRepository import kotlinx.coroutines.launch /** @@ -52,4 +49,4 @@ class SplashViewModel( } } } -} +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/ui/components/CategoryStatisticsCard.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/statistics/CategoryStatisticsCard.kt similarity index 99% rename from composeApp/src/commonMain/kotlin/com/taskttl/ui/components/CategoryStatisticsCard.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/statistics/CategoryStatisticsCard.kt index f9f2691..c58baca 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/ui/components/CategoryStatisticsCard.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/statistics/CategoryStatisticsCard.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import com.taskttl.data.local.model.CategoryStatistics +import com.taskttl.domain.model.CategoryStatistics import org.jetbrains.compose.resources.stringResource import taskttl.composeapp.generated.resources.Res import taskttl.composeapp.generated.resources.active diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/statistics/StatisticsScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/statistics/StatisticsScreen.kt similarity index 93% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/statistics/StatisticsScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/statistics/StatisticsScreen.kt index 9367808..2e58d31 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/statistics/StatisticsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/statistics/StatisticsScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.statistics +package com.taskttl.presentation.features.statistics import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -42,15 +42,14 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight 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.data.local.model.Category -import com.taskttl.data.state.CountdownIntent -import com.taskttl.data.state.TaskIntent -import com.taskttl.data.viewmodel.CountdownViewModel -import com.taskttl.data.viewmodel.TaskViewModel -import com.taskttl.ui.components.AppHeader +import com.taskttl.domain.model.Category +import com.taskttl.navigation.Routes +import com.taskttl.presentation.common.components.AppHeader +import com.taskttl.presentation.common.components.Chip +import com.taskttl.presentation.features.countdown.list.CountdownIntent +import com.taskttl.presentation.features.countdown.list.CountdownViewModel +import com.taskttl.presentation.features.task.list.TaskIntent +import com.taskttl.presentation.features.task.list.TaskViewModel import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview @@ -85,7 +84,7 @@ fun StatisticsScreen( Column( modifier = Modifier .fillMaxSize() - .background(Color.White) + .background(MaterialTheme.colorScheme.surface) ) { AppHeader( title = Res.string.title_statistics, @@ -125,7 +124,7 @@ fun StatisticsScreen( titleRes = Res.string.completed, value = taskState.tasks.count { it.isCompleted }.toString(), icon = Icons.Default.CheckCircle, - color = Color(0xFF4CAF50), + color = MaterialTheme.colorScheme.primary, modifier = Modifier.weight(1f) ) } @@ -140,7 +139,7 @@ fun StatisticsScreen( titleRes = Res.string.category_countdown, value = countdownState.countdowns.size.toString(), icon = Icons.Default.Schedule, - color = Color(0xFFFF9800), + color = MaterialTheme.colorScheme.tertiary, modifier = Modifier.weight(1f) ) @@ -153,7 +152,7 @@ fun StatisticsScreen( titleRes = Res.string.completion_rate, value = "$completionRate%", icon = Icons.AutoMirrored.Filled.TrendingUp, - color = Color(0xFF9C27B0), + color = MaterialTheme.colorScheme.secondary, modifier = Modifier.weight(1f) ) } @@ -270,7 +269,7 @@ private fun CategoryStatisticItem( Card( modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = Color.White), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), ) { Row( modifier = Modifier diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/task/TaskEditorScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/detail/TaskEditorScreen.kt similarity index 69% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/task/TaskEditorScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/detail/TaskEditorScreen.kt index 8ff1d08..b651624 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/task/TaskEditorScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/detail/TaskEditorScreen.kt @@ -1,6 +1,7 @@ -package com.taskttl.presentation.task +package com.taskttl.presentation.features.task.detail import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -10,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items @@ -17,8 +19,10 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.DateRange +import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Edit +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChipDefaults @@ -34,34 +38,35 @@ 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.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.routes.Routes -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 -import com.taskttl.data.local.model.TaskPriority -import com.taskttl.data.state.TaskEffect -import com.taskttl.data.state.TaskIntent -import com.taskttl.data.viewmodel.TaskViewModel -import com.taskttl.ui.components.AppHeader +import com.taskttl.domain.model.Category +import com.taskttl.domain.model.Task +import com.taskttl.domain.model.TaskPriority +import com.taskttl.presentation.common.components.AppHeader +import com.taskttl.presentation.common.components.CompactDatePickerDialog +import com.taskttl.presentation.common.components.LoadingOverlay +import com.taskttl.presentation.features.task.list.TaskEffect +import com.taskttl.presentation.features.task.list.TaskIntent +import com.taskttl.presentation.features.task.list.TaskViewModel import com.taskttl.ui.components.CategoryCard -import com.taskttl.ui.components.CompactDatePickerDialog +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone +import kotlinx.datetime.number +import kotlinx.datetime.plus import kotlinx.datetime.toLocalDateTime 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.desc_select_date import taskttl.composeapp.generated.resources.hint_tags import taskttl.composeapp.generated.resources.title_add_task -import taskttl.composeapp.generated.resources.title_due_date import taskttl.composeapp.generated.resources.title_edit_task import taskttl.composeapp.generated.resources.title_priority import taskttl.composeapp.generated.resources.title_select_category @@ -101,6 +106,7 @@ fun TaskEditorScreen( val state by viewModel.state.collectAsState() val existingTask = state.editingTask + var showDatePicker by remember { mutableStateOf(false) } var title by remember { mutableStateOf("") } @@ -110,6 +116,13 @@ fun TaskEditorScreen( var dueDate by remember { mutableStateOf(existingTask?.dueDate) } var tags by remember { mutableStateOf("") } + LaunchedEffect(state.categories) { + if (state.categories.isNotEmpty() && state.selectedCategory == null) { + selectedCategory = state.categories.first() + } + } + + LaunchedEffect(Unit) { viewModel.effects.collect { effect -> when (effect) { @@ -259,21 +272,10 @@ fun TaskEditorScreen( Spacer(modifier = Modifier.height(24.dp)) - // 截止日期 - OutlinedTextField( - value = dueDate?.toString() ?: "", - onValueChange = { }, - label = { Text(stringResource(Res.string.title_due_date)) }, - modifier = Modifier.fillMaxWidth(), - readOnly = true, - trailingIcon = { - IconButton(onClick = { showDatePicker = true }) { - Icon( - Icons.Default.DateRange, - contentDescription = stringResource(Res.string.desc_select_date) - ) - } - } + DueDateSelector( + dueDate = dueDate, + onDateSelected = { date -> dueDate = date }, + onClick = { showDatePicker = true }, ) CompactDatePickerDialog( @@ -298,3 +300,95 @@ fun TaskEditorScreen( LoadingOverlay(state.isLoading) } } + +@OptIn(ExperimentalTime::class) +@Composable +fun DueDateSelector( + dueDate: LocalDateTime?, + onDateSelected: (LocalDateTime?) -> Unit, + onClick: () -> Unit +) { + val now = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()) + val today = now.date + val tomorrow = today.plus(1, DateTimeUnit.DAY) + + val dateFormatter = { date: LocalDateTime -> + "${date.year}-${date.month.number.toString().padStart(2, '0')}-${date.day.toString().padStart(2, '0')} " + + "${date.hour.toString().padStart(2, '0')}:${date.minute.toString().padStart(2, '0')}" + } + + Column(modifier = Modifier.fillMaxWidth()) { + Text( + text = "截止日期", + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Medium, + modifier = Modifier.padding(bottom = 8.dp) + ) + + if (dueDate == null) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.fillMaxWidth() + ) { + listOf("今天" to today, "明天" to tomorrow).forEach { (label, date) -> + Button( + onClick = { + onDateSelected( + LocalDateTime(date.year, date.month, date.dayOfMonth, now.hour, now.minute) + ) + }, + modifier = Modifier.weight(1f), + colors = ButtonDefaults.buttonColors( + containerColor = if (dueDate?.date == date) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.surfaceVariant + ) + ) { + Text(label) + } + } + + // 其他时间按钮占剩余空间 + Button( + onClick = onClick, + modifier = Modifier.fillMaxWidth(), // 占满剩余空间 + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant + ) + ) { + Text("其他时间") + } + } + + } else { + Box( + modifier = Modifier + .fillMaxWidth() + .border(1.dp, MaterialTheme.colorScheme.outlineVariant, MaterialTheme.shapes.small) + .padding(horizontal = 12.dp, vertical = 8.dp) + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = dateFormatter(dueDate), + style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold), + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier.weight(1f) + ) + + IconButton( + onClick = { onDateSelected(null) }, + modifier = Modifier.size(24.dp) + ) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "清除", + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } + } + } +} + diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/task/TaskDetailScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/editor/TaskDetailScreen.kt similarity index 85% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/task/TaskDetailScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/editor/TaskDetailScreen.kt index a81e8a2..5217b26 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/task/TaskDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/editor/TaskDetailScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.task +package com.taskttl.presentation.features.task.editor import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -33,16 +33,15 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.taskttl.core.ui.LoadingOverlay +import com.taskttl.presentation.common.components.AppHeader +import com.taskttl.presentation.common.components.LoadingOverlay +import com.taskttl.presentation.features.task.list.TaskEffect +import com.taskttl.presentation.features.task.list.TaskIntent +import com.taskttl.presentation.features.task.list.TaskViewModel -import com.taskttl.data.state.TaskEffect -import com.taskttl.data.state.TaskIntent -import com.taskttl.data.viewmodel.TaskViewModel -import com.taskttl.ui.components.AppHeader import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewmodel.koinViewModel import taskttl.composeapp.generated.resources.Res @@ -95,7 +94,7 @@ fun TaskDetailScreen( Box( modifier = Modifier .fillMaxSize() - .background(Color.White) + .background(MaterialTheme.colorScheme.surface) ) { Column( modifier = Modifier.fillMaxSize() @@ -139,7 +138,7 @@ fun TaskDetailScreen( text = task.title, modifier = Modifier.weight(1f), fontSize = 20.sp, - color = Color(0xFF333333), + color = MaterialTheme.colorScheme.onSurface, fontWeight = FontWeight.SemiBold ) @@ -169,7 +168,10 @@ fun TaskDetailScreen( modifier = Modifier .fillMaxWidth() .padding(bottom = 12.dp) - .background(Color(0xFFF8F9FA), RoundedCornerShape(12.dp)) + .background( + MaterialTheme.colorScheme.surfaceVariant, + RoundedCornerShape(12.dp) + ) .padding(12.dp) ) { Icon(Icons.AutoMirrored.Filled.Label, contentDescription = null) @@ -183,7 +185,7 @@ fun TaskDetailScreen( modifier = Modifier .fillMaxWidth() .padding(bottom = 12.dp) - .background(Color(0xFFF8F9FA), RoundedCornerShape(12.dp)) + .background(MaterialTheme.colorScheme.surfaceVariant, RoundedCornerShape(12.dp)) .padding(12.dp) ) { Icon(Icons.Default.CalendarToday, contentDescription = null) @@ -202,7 +204,10 @@ fun TaskDetailScreen( modifier = Modifier .fillMaxWidth() .padding(bottom = 12.dp) - .background(Color(0xFFF8F9FA), RoundedCornerShape(12.dp)) + .background( + MaterialTheme.colorScheme.surfaceVariant, + RoundedCornerShape(12.dp) + ) .padding(12.dp) ) { Icon(Icons.Default.Timer, contentDescription = null) @@ -216,17 +221,24 @@ fun TaskDetailScreen( Text( text = stringResource(Res.string.label_description), fontWeight = FontWeight.SemiBold, - color = Color(0xFF333333) + color = MaterialTheme.colorScheme.onSurface ) Box( modifier = Modifier .fillMaxWidth() .padding(top = 8.dp) - .background(Color(0xFFF8F9FA), RoundedCornerShape(8.dp)) + .background( + MaterialTheme.colorScheme.surfaceVariant, + RoundedCornerShape(8.dp) + ) .padding(15.dp) ) { - Text(text = task.description, color = Color(0xFF666666), lineHeight = 20.sp) + Text( + text = task.description, + color = MaterialTheme.colorScheme.onSurfaceVariant, + lineHeight = 20.sp + ) } } diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/task/TaskScreen.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/list/TaskScreen.kt similarity index 53% rename from composeApp/src/commonMain/kotlin/com/taskttl/presentation/task/TaskScreen.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/list/TaskScreen.kt index 61eea8f..08c01e5 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/task/TaskScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/list/TaskScreen.kt @@ -1,4 +1,4 @@ -package com.taskttl.presentation.task +package com.taskttl.presentation.features.task.list import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -8,32 +8,19 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Search -import androidx.compose.material.icons.outlined.Circle -import androidx.compose.material.icons.rounded.Delete -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults import androidx.compose.material3.Checkbox import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.FilledIconButton import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -45,30 +32,20 @@ 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.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp 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.LoadingOverlay import com.taskttl.core.utils.ToastUtils -import com.taskttl.data.local.model.Task -import com.taskttl.data.state.TaskEffect -import com.taskttl.data.state.TaskIntent -import com.taskttl.data.viewmodel.TaskViewModel -import com.taskttl.ui.components.AppHeader -import com.taskttl.ui.components.CategoryFilter -import com.taskttl.ui.components.SearchBar +import com.taskttl.navigation.Routes +import com.taskttl.presentation.common.components.AppHeader +import com.taskttl.presentation.common.components.CategoryFilter +import com.taskttl.presentation.common.components.ErrorDialog +import com.taskttl.presentation.common.components.LoadingOverlay +import com.taskttl.presentation.common.components.SearchBar +import com.taskttl.presentation.features.task.list.components.TaskCardItem 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.desc_completed -import taskttl.composeapp.generated.resources.desc_incomplete import taskttl.composeapp.generated.resources.label_show_completed import taskttl.composeapp.generated.resources.label_task_list import taskttl.composeapp.generated.resources.text_add_task_hint @@ -116,7 +93,7 @@ fun TaskScreen( Box( modifier = Modifier .fillMaxSize() - .background(Color.White) + .background(MaterialTheme.colorScheme.surface) ) { Column( modifier = Modifier.fillMaxSize() @@ -250,7 +227,7 @@ fun TaskScreen( modifier = Modifier .align(Alignment.BottomEnd) .padding(20.dp), - containerColor = Color(0xFF667EEA) + containerColor = MaterialTheme.colorScheme.primary ) { Icon( imageVector = Icons.Default.Add, @@ -261,162 +238,3 @@ fun TaskScreen( } } -/** - * 任务卡项目 - * @param [task] 任务 - * @param [isOpen] 是开放 - * @param [alignment] 对齐 - * @param [onOpenChange] 论开放式变革 - * @param [onClick] 单击 - * @param [onToggleComplete] 切换完成 - * @param [onDeleteTask] 关于删除任务 - * @param [modifier] 修饰符 - */ -@Composable -fun TaskCardItem( - task: Task, - isOpen: Boolean, - alignment: Alignment.Horizontal = Alignment.End, - onOpenChange: (Boolean) -> Unit, - onClick: () -> Unit, - onToggleComplete: () -> Unit, - onDeleteTask: () -> Unit, - modifier: Modifier = Modifier, -) { - ActionButtonListItem( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(12.dp)), - isOpen = isOpen, - actionAlignment = alignment, - onOpenChange = onOpenChange, - onClick = onClick - ) { - Card( - modifier = modifier - .fillMaxWidth() - .background(Color.Transparent, shape = RoundedCornerShape(12.dp)) - .clickable { onClick.invoke() }, - colors = CardDefaults.cardColors(containerColor = Color.White), - elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - // 完成状态按钮 - IconButton( - onClick = onToggleComplete, - modifier = Modifier.size(24.dp) - ) { - val isCompleted = - if (task.isCompleted) Res.string.desc_completed else Res.string.desc_incomplete - Icon( - imageVector = if (task.isCompleted) Icons.Filled.CheckCircle else Icons.Outlined.Circle, - contentDescription = stringResource(isCompleted), - tint = if (task.isCompleted) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant - ) - } - - Spacer(modifier = Modifier.width(12.dp)) - - // 任务内容 - Column( - modifier = Modifier.weight(1f) - ) { - Text( - text = task.title, - style = MaterialTheme.typography.titleMedium, - textDecoration = if (task.isCompleted) TextDecoration.LineThrough else null, - color = if (task.isCompleted) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurface, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - - if (task.description.isNotBlank()) { - Spacer(modifier = Modifier.height(4.dp)) - 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 - ) - } - } - - } - } - - Spacer(modifier = Modifier.width(12.dp)) - - Row( - verticalAlignment = Alignment.CenterVertically - ) { - // 优先级标签 - Box( - modifier = Modifier - .background( - color = task.priority.color.copy(alpha = 0.1f), - shape = RoundedCornerShape(12.dp) - ) - .padding(horizontal = 8.dp, vertical = 4.dp) - ) { - Text( - text = stringResource(task.priority.displayNameRes), - style = MaterialTheme.typography.labelSmall, - color = task.priority.color - ) - } - Spacer(modifier = Modifier.width(8.dp)) - // 分类标签 - Box( - modifier = Modifier - .background( - color = task.category.color.backgroundColor, - shape = RoundedCornerShape(12.dp) - ) - .padding(horizontal = 8.dp, vertical = 4.dp) - ) { - Text( - text = task.category.name, - style = MaterialTheme.typography.labelSmall, - color = task.category.color.textColor - ) - } - } - } - } - FilledIconButton( - onClick = { onDeleteTask.invoke() }, - shape = CircleShape, - modifier = Modifier - .size(32.dp) - .aspectRatio(1f), - colors = IconButtonDefaults.filledIconButtonColors( - containerColor = Color(0xffff1111), - contentColor = Color.White - ) - ) { - Icon( - imageVector = Icons.Rounded.Delete, - contentDescription = Icons.Rounded.Delete.name - ) - } - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/state/TaskState.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/list/TaskState.kt similarity index 95% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/state/TaskState.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/list/TaskState.kt index 687bfa0..3739a23 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/state/TaskState.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/list/TaskState.kt @@ -1,8 +1,8 @@ -package com.taskttl.data.state +package com.taskttl.presentation.features.task.list -import com.taskttl.core.viewmodel.BaseUiState -import com.taskttl.data.local.model.Category -import com.taskttl.data.local.model.Task +import com.taskttl.core.base.BaseState +import com.taskttl.domain.model.Category +import com.taskttl.domain.model.Task /** * 任务状态 @@ -30,7 +30,7 @@ data class TaskState( val isSearch: Boolean = false, val searchQuery: String = "", val showCompleted: Boolean = false -): BaseUiState() +): BaseState() /** * 任务意图 diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/TaskViewModel.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/list/TaskViewModel.kt similarity index 92% rename from composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/TaskViewModel.kt rename to composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/list/TaskViewModel.kt index 800d600..b18d784 100644 --- a/composeApp/src/commonMain/kotlin/com/taskttl/data/viewmodel/TaskViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/list/TaskViewModel.kt @@ -1,19 +1,16 @@ -package com.taskttl.data.viewmodel +package com.taskttl.presentation.features.task.list import androidx.lifecycle.viewModelScope -import com.taskttl.core.notification.NotificationManager +import com.taskttl.core.base.BaseViewModel +import com.taskttl.core.notification.AppNotificationManager import com.taskttl.core.notification.NotificationPayload import com.taskttl.core.notification.NotificationRepeatType import com.taskttl.core.utils.DateUtils.toEpochMillis -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 -import com.taskttl.data.repository.CategoryRepository -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 com.taskttl.domain.model.Category +import com.taskttl.domain.model.CategoryType +import com.taskttl.domain.model.Task +import com.taskttl.domain.repository.CategoryRepository +import com.taskttl.domain.repository.TaskRepository import kotlinx.coroutines.launch import org.jetbrains.compose.resources.getString import taskttl.composeapp.generated.resources.Res @@ -143,12 +140,13 @@ class TaskViewModel( // 2️⃣ 创建通知 task.dueDate?.let { - NotificationManager.scheduleNotification( + AppNotificationManager.scheduleNotification( NotificationPayload( id = task.id, title = task.title, message = task.description, - triggerTimeMillis = task.dueDate.toEpochMillis(), + triggerTimeMillis = Clock.System.now().toEpochMilliseconds() + 60_000, + // triggerTimeMillis = task.dueDate.toEpochMillis(), repeatType = NotificationRepeatType.NONE ) ) @@ -177,9 +175,9 @@ class TaskViewModel( updateState { copy(isLoading = false, isProcessing = true) } taskRepository.updateTask(task) - NotificationManager.cancelNotification(task.id) + AppNotificationManager.cancelNotification(task.id) task.dueDate?.let { - NotificationManager.scheduleNotification( + AppNotificationManager.scheduleNotification( NotificationPayload( id = task.id, title = task.title, @@ -211,7 +209,7 @@ class TaskViewModel( if (state.value.isProcessing) return@launch updateState { copy(isLoading = false, isProcessing = true) } taskRepository.deleteTask(taskId) - NotificationManager.cancelNotification(taskId) + AppNotificationManager.cancelNotification(taskId) sendEvent(TaskEffect.ShowMessage(getString(Res.string.task_delete_success))) } catch (e: Exception) { val errStr = getString(Res.string.task_delete_failed) @@ -306,4 +304,4 @@ class TaskViewModel( categoryMatch && searchMatch && completionMatch } } -} +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/list/components/TaskCard.kt b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/list/components/TaskCard.kt new file mode 100644 index 0000000..51aa561 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/taskttl/presentation/features/task/list/components/TaskCard.kt @@ -0,0 +1,202 @@ +package com.taskttl.presentation.features.task.list.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.outlined.Circle +import androidx.compose.material.icons.rounded.Delete +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.FilledIconButton +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.taskttl.domain.model.Task +import com.taskttl.presentation.common.components.ActionButtonListItem +import org.jetbrains.compose.resources.stringResource +import taskttl.composeapp.generated.resources.Res +import taskttl.composeapp.generated.resources.desc_completed +import taskttl.composeapp.generated.resources.desc_incomplete + +/** + * 任务卡项目 + * @param [task] 任务 + * @param [isOpen] 是开放 + * @param [alignment] 对齐 + * @param [onOpenChange] 论开放式变革 + * @param [onClick] 单击 + * @param [onToggleComplete] 切换完成 + * @param [onDeleteTask] 关于删除任务 + * @param [modifier] 修饰符 + */ +@Composable +fun TaskCardItem( + task: Task, + isOpen: Boolean, + alignment: Alignment.Horizontal = Alignment.End, + onOpenChange: (Boolean) -> Unit, + onClick: () -> Unit, + onToggleComplete: () -> Unit, + onDeleteTask: () -> Unit, + modifier: Modifier = Modifier, +) { + ActionButtonListItem( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)), + isOpen = isOpen, + actionAlignment = alignment, + onOpenChange = onOpenChange, + onClick = onClick + ) { + Card( + modifier = modifier + .fillMaxWidth() + .background(Color.Transparent, shape = RoundedCornerShape(12.dp)) + .clickable { onClick.invoke() }, + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // 完成状态按钮 + IconButton( + onClick = onToggleComplete, + modifier = Modifier.size(24.dp) + ) { + val isCompleted = + if (task.isCompleted) Res.string.desc_completed else Res.string.desc_incomplete + Icon( + imageVector = if (task.isCompleted) Icons.Filled.CheckCircle else Icons.Outlined.Circle, + contentDescription = stringResource(isCompleted), + tint = if (task.isCompleted) MaterialTheme.colorScheme.primary else task.priority.color + ) + } + + Spacer(modifier = Modifier.width(12.dp)) + + // 任务内容 + Column( + modifier = Modifier.weight(1f) + ) { + Text( + text = task.title, + style = MaterialTheme.typography.titleMedium, + textDecoration = if (task.isCompleted) TextDecoration.LineThrough else null, + color = if (task.isCompleted) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + + if (task.description.isNotBlank()) { + Spacer(modifier = Modifier.height(4.dp)) + 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 + ) + } + } + + } + } + + Spacer(modifier = Modifier.width(12.dp)) + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + // 优先级标签 + Box( + modifier = Modifier + .background( + color = task.priority.color.copy(alpha = 0.1f), + shape = RoundedCornerShape(12.dp) + ) + .padding(horizontal = 8.dp, vertical = 4.dp) + ) { + Text( + text = stringResource(task.priority.displayNameRes), + style = MaterialTheme.typography.labelSmall, + color = task.priority.color + ) + } + Spacer(modifier = Modifier.width(8.dp)) + // 分类标签 + Box( + modifier = Modifier + .background( + color = task.category.color.backgroundColor, + shape = RoundedCornerShape(12.dp) + ) + .padding(horizontal = 8.dp, vertical = 4.dp) + ) { + Text( + text = task.category.name, + style = MaterialTheme.typography.labelSmall, + color = task.category.color.textColor + ) + } + } + } + } + FilledIconButton( + onClick = { onDeleteTask.invoke() }, + shape = CircleShape, + modifier = Modifier + .size(32.dp) + .aspectRatio(1f), + colors = IconButtonDefaults.filledIconButtonColors( + containerColor = MaterialTheme.colorScheme.error, + contentColor = MaterialTheme.colorScheme.onError + ) + ) { + Icon( + imageVector = Icons.Rounded.Delete, + contentDescription = Icons.Rounded.Delete.name + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/taskttl/ui/theme/Theme.kt b/composeApp/src/commonMain/kotlin/com/taskttl/ui/theme/Theme.kt deleted file mode 100644 index b201f86..0000000 --- a/composeApp/src/commonMain/kotlin/com/taskttl/ui/theme/Theme.kt +++ /dev/null @@ -1,109 +0,0 @@ -package com.taskttl.ui.theme - -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.darkColorScheme -import androidx.compose.material3.lightColorScheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color - -/** 浅色方案 */ -private val LightColorScheme = lightColorScheme( - primary = Color(0xFF667EEA), - onPrimary = Color(0xFFFFFFFF), - primaryContainer = Color(0xFFEADDFF), - onPrimaryContainer = Color(0xFF21005D), - secondary = Color(0xFF764BA2), - onSecondary = Color(0xFFFFFFFF), - secondaryContainer = Color(0xFFE8DEF8), - onSecondaryContainer = Color(0xFF1D192B), - tertiary = Color(0xFF7D5260), - onTertiary = Color(0xFFFFFFFF), - tertiaryContainer = Color(0xFFFFD8E4), - onTertiaryContainer = Color(0xFF31111D), - error = Color(0xFFBA1A1A), - onError = Color(0xFFFFFFFF), - errorContainer = Color(0xFFFFDAD6), - onErrorContainer = Color(0xFF410002), - background = Color(0xFFF5F5F5), - onBackground = Color(0xFF1C1B1F), - surface = Color(0xFFFFFBFE), - onSurface = Color(0xFF1C1B1F), - surfaceVariant = Color(0xFFE7E0EC), - onSurfaceVariant = Color(0xFF49454F), - outline = Color(0xFF79747E), - outlineVariant = Color(0xFFCAC4D0), - scrim = Color(0xFF000000), - inverseSurface = Color(0xFF313033), - inverseOnSurface = Color(0xFFF4EFF4), - inversePrimary = Color(0xFFD0BCFF), - surfaceDim = Color(0xFFDDD8DD), - surfaceBright = Color(0xFFFFFBFE), - surfaceContainerLowest = Color(0xFFFFFFFF), - surfaceContainerLow = Color(0xFFF7F2FA), - surfaceContainer = Color(0xFFF1ECF4), - surfaceContainerHigh = Color(0xFFECE6F0), - surfaceContainerHighest = Color(0xFFE6E0E9) -) - -/** 深色配色方案 */ -private val DarkColorScheme = darkColorScheme( - primary = Color(0xFFD0BCFF), - onPrimary = Color(0xFF381E72), - primaryContainer = Color(0xFF4F378B), - onPrimaryContainer = Color(0xFFEADDFF), - secondary = Color(0xFFCCC2DC), - onSecondary = Color(0xFF332D41), - secondaryContainer = Color(0xFF4A4458), - onSecondaryContainer = Color(0xFFE8DEF8), - tertiary = Color(0xFFEFB8C8), - onTertiary = Color(0xFF492532), - tertiaryContainer = Color(0xFF633B48), - onTertiaryContainer = Color(0xFFFFD8E4), - error = Color(0xFFFFB4AB), - onError = Color(0xFF690005), - errorContainer = Color(0xFF93000A), - onErrorContainer = Color(0xFFFFDAD6), - background = Color(0xFF10131C), - onBackground = Color(0xFFE6E0E9), - surface = Color(0xFF10131C), - onSurface = Color(0xFFE6E0E9), - surfaceVariant = Color(0xFF49454F), - onSurfaceVariant = Color(0xFFCAC4D0), - outline = Color(0xFF938F99), - outlineVariant = Color(0xFF49454F), - scrim = Color(0xFF000000), - inverseSurface = Color(0xFFE6E0E9), - inverseOnSurface = Color(0xFF313033), - inversePrimary = Color(0xFF6750A4), - surfaceDim = Color(0xFF10131C), - surfaceBright = Color(0xFF383B42), - surfaceContainerLowest = Color(0xFF0B0E17), - surfaceContainerLow = Color(0xFF191C24), - surfaceContainer = Color(0xFF1D2028), - surfaceContainerHigh = Color(0xFF282A32), - surfaceContainerHighest = Color(0xFF33353D) -) - -/** - * 应用主题 - * @param [darkTheme] 黑暗主题 - * @param [content] 内容 - */ -@Composable -fun AppTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - content: @Composable () -> Unit -) { - val colorScheme = if (darkTheme) { - DarkColorScheme - } else { - LightColorScheme - } - - MaterialTheme( - colorScheme = colorScheme, - typography = mainTypography(), - content = content - ) -} \ No newline at end of file diff --git a/composeApp/src/iosMain/kotlin/com/taskttl/data/di/AppModule.ios.kt b/composeApp/src/iosMain/kotlin/com/taskttl/data/di/AppModule.ios.kt new file mode 100644 index 0000000..1802853 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/com/taskttl/data/di/AppModule.ios.kt @@ -0,0 +1,6 @@ +package com.taskttl.data.di + +import org.koin.dsl.module + +actual val serviceModule = module { +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/com/taskttl/core/analytics/FacebookEventTracker.jvm.kt b/composeApp/src/jvmMain/kotlin/com/taskttl/core/analytics/FacebookEventTracker.jvm.kt new file mode 100644 index 0000000..7422afc --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/com/taskttl/core/analytics/FacebookEventTracker.jvm.kt @@ -0,0 +1,9 @@ +package com.taskttl.core.analytics + +actual object FacebookEventTracker { + actual fun logEvent( + event: FacebookEvent, + params: Map, + ) { + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/com/taskttl/core/network/KtorClient.jvm.kt b/composeApp/src/jvmMain/kotlin/com/taskttl/core/network/KtorClient.jvm.kt new file mode 100644 index 0000000..f7ceb59 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/com/taskttl/core/network/KtorClient.jvm.kt @@ -0,0 +1,11 @@ +package com.taskttl.core.network + +import com.taskttl.core.utils.LogUtils +import io.ktor.client.plugins.logging.Logger + + +actual val defaultLogger: Logger = object : Logger { + override fun log(message: String) { + LogUtils.e("DevTTL_NetWork", message) + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/com/taskttl/core/notification/AppNotificationManager.jvm.kt b/composeApp/src/jvmMain/kotlin/com/taskttl/core/notification/AppNotificationManager.jvm.kt new file mode 100644 index 0000000..f98cde3 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/com/taskttl/core/notification/AppNotificationManager.jvm.kt @@ -0,0 +1,12 @@ +package com.taskttl.core.notification + +actual object AppNotificationManager { + actual suspend fun scheduleNotification(payload: NotificationPayload) { + } + + actual suspend fun cancelNotification(id: String) { + } + + actual suspend fun cancelAll() { + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/com/taskttl/core/permission/ExactAlarmPermissionManager.jvm.kt b/composeApp/src/jvmMain/kotlin/com/taskttl/core/permission/ExactAlarmPermissionManager.jvm.kt new file mode 100644 index 0000000..94d8409 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/com/taskttl/core/permission/ExactAlarmPermissionManager.jvm.kt @@ -0,0 +1,14 @@ +package com.taskttl.core.permission + +actual object ExactAlarmPermissionManager { + actual fun requestPermission(): Boolean { + TODO("Not yet implemented") + } + + actual fun verifyPermission(): Boolean { + TODO("Not yet implemented") + } + + actual fun disablePermission() { + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/com/taskttl/core/permission/NotificationPermissionManager.jvm.kt b/composeApp/src/jvmMain/kotlin/com/taskttl/core/permission/NotificationPermissionManager.jvm.kt new file mode 100644 index 0000000..11fb093 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/com/taskttl/core/permission/NotificationPermissionManager.jvm.kt @@ -0,0 +1,14 @@ +package com.taskttl.core.permission + +actual object NotificationPermissionManager { + actual fun requestPermission(): Boolean { + TODO("Not yet implemented") + } + + actual fun verifyPermission(): Boolean { + TODO("Not yet implemented") + } + + actual fun disablePermission() { + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/com/taskttl/core/ui/DevTTLWebView.jvm.kt b/composeApp/src/jvmMain/kotlin/com/taskttl/core/ui/DevTTLWebView.jvm.kt new file mode 100644 index 0000000..5e8db9f --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/com/taskttl/core/ui/DevTTLWebView.jvm.kt @@ -0,0 +1,8 @@ +package com.taskttl.core.ui + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +actual fun DevTTLWebView(modifier: Modifier, url: String) { +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/DeviceUtils.jvm.kt b/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/DeviceUtils.jvm.kt new file mode 100644 index 0000000..fc57997 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/DeviceUtils.jvm.kt @@ -0,0 +1,13 @@ +package com.taskttl.core.utils + +import com.taskttl.core.domain.BaseReq + +actual object DeviceUtils { + actual suspend fun getUniqueId(): String { + TODO("Not yet implemented") + } + + actual suspend fun getDeviceInfo(): BaseReq { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/ExternalAppLauncher.jvm.kt b/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/ExternalAppLauncher.jvm.kt new file mode 100644 index 0000000..0be6b9d --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/ExternalAppLauncher.jvm.kt @@ -0,0 +1,9 @@ +package com.taskttl.core.utils + +actual object ExternalAppLauncher { + actual suspend fun openAppRating() { + } + + actual suspend fun openUrl(url: String) { + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/Localization.jvm.kt b/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/Localization.jvm.kt new file mode 100644 index 0000000..e8c67b9 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/Localization.jvm.kt @@ -0,0 +1,10 @@ +package com.taskttl.core.utils + +actual object Localization { + actual fun applyLanguage(iso: String) { + } + + actual fun getDeviceLanguage(): String { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/StorageUtils.jvm.kt b/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/StorageUtils.jvm.kt new file mode 100644 index 0000000..f976e7c --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/StorageUtils.jvm.kt @@ -0,0 +1,48 @@ +package com.taskttl.core.utils + +actual object StorageUtils { + actual fun saveString(key: String, value: String) { + } + + actual fun getString(key: String, defaultValue: String): String { + TODO("Not yet implemented") + } + + actual fun saveInt(key: String, value: Int) { + } + + actual fun getInt(key: String, defaultValue: Int): Int { + TODO("Not yet implemented") + } + + actual fun saveLong(key: String, value: Long) { + } + + actual fun getLong(key: String, defaultValue: Long): Long { + TODO("Not yet implemented") + } + + actual fun saveBoolean(key: String, value: Boolean) { + } + + actual fun getBoolean(key: String, defaultValue: Boolean): Boolean { + TODO("Not yet implemented") + } + + actual inline fun saveObject(key: String, value: T) { + } + + actual inline fun getObject(key: String): T? { + TODO("Not yet implemented") + } + + actual fun contains(key: String): Boolean { + TODO("Not yet implemented") + } + + actual fun remove(key: String) { + } + + actual fun clear() { + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/ToastUtils.jvm.kt b/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/ToastUtils.jvm.kt new file mode 100644 index 0000000..1f40157 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/com/taskttl/core/utils/ToastUtils.jvm.kt @@ -0,0 +1,6 @@ +package com.taskttl.core.utils + +actual object ToastUtils { + actual fun show(message: String) { + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/com/taskttl/data/di/KoinModels.desktop.kt b/composeApp/src/jvmMain/kotlin/com/taskttl/data/di/AppModule.jvm.kt similarity index 85% rename from composeApp/src/jvmMain/kotlin/com/taskttl/data/di/KoinModels.desktop.kt rename to composeApp/src/jvmMain/kotlin/com/taskttl/data/di/AppModule.jvm.kt index 9c77e92..fc176e8 100644 --- a/composeApp/src/jvmMain/kotlin/com/taskttl/data/di/KoinModels.desktop.kt +++ b/composeApp/src/jvmMain/kotlin/com/taskttl/data/di/AppModule.jvm.kt @@ -4,6 +4,6 @@ import com.taskttl.data.local.database.TaskTTLDatabase import com.taskttl.data.local.database.getDatabaseBuilder import org.koin.dsl.module -actual fun platformModule() = module { +actual val serviceModule = module { single { getDatabaseBuilder() } } \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/com/taskttl/data/repository/AuthRepository.jvm.kt b/composeApp/src/jvmMain/kotlin/com/taskttl/data/repository/AuthRepository.jvm.kt new file mode 100644 index 0000000..6247182 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/com/taskttl/data/repository/AuthRepository.jvm.kt @@ -0,0 +1,13 @@ +package com.taskttl.data.repository + +import com.taskttl.data.source.remote.dto.response.AuthResult + +actual class AuthRepository actual constructor() { + actual suspend fun loginWithGoogle(): AuthResult { + TODO("Not yet implemented") + } + + actual suspend fun loginWithFacebook(): AuthResult { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/composeApp/src/jvmMain/kotlin/com/taskttl/main.kt b/composeApp/src/jvmMain/kotlin/com/taskttl/main.kt index dfad023..4953bfa 100644 --- a/composeApp/src/jvmMain/kotlin/com/taskttl/main.kt +++ b/composeApp/src/jvmMain/kotlin/com/taskttl/main.kt @@ -6,6 +6,7 @@ import androidx.compose.ui.window.application fun main() = application { Window( onCloseRequest = ::exitApplication, + alwaysOnTop = true, title = "TaskTTL", ) { App() diff --git a/composeApp/webpack.config.d/watch.js b/composeApp/webpack.config.d/watch.js deleted file mode 100644 index 04713d2..0000000 --- a/composeApp/webpack.config.d/watch.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Temporary workaround for [KT-80582](https://youtrack.jetbrains.com/issue/KT-80582) - * - * This file should be safe to be removed once the ticket is closed and the project is updated to Kotlin version which solves that issue. - */ -config.watchOptions = config.watchOptions || { - ignored: ["**/*.kt", "**/node_modules"] -} - -if (config.devServer) { - config.devServer.static = config.devServer.static.map(file => { - if (typeof file === "string") { - return { - directory: file, - watch: false, - } - } else { - return file - } - }) -} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 0021f9f..4d047bd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,6 +17,4 @@ kotlin.native.ignoreDisabledTargets=true ksp.verbose=true kotlin.kmp.eagerUnresolvedDependenciesDiagnostic=false -kotlin.kmp.unresolvedDependenciesDiagnostic=false - -android.overridePathCheck=true \ No newline at end of file +kotlin.kmp.unresolvedDependenciesDiagnostic=false \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 315c759..587bc43 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ androidx-testExt = "1.3.0" composeHotReload = "1.0.0-rc02" composeMultiplatform = "1.9.1" junit = "4.13.2" -kotlin = "2.2.20" +kotlin = "2.2.21" kotlinx-coroutines = "1.10.2" navigationCompose = "2.9.1" @@ -21,20 +21,26 @@ kotlinx-datetime = "0.7.1" icons = "1.7.3" google = "4.4.4" +credentials = "1.6.0-beta03" +googleid = "1.1.1" firebase = "34.4.0" -facebookAndroidSdkVersion = "18.1.3" +facebook = "18.1.3" playServicesAds = "18.2.0" kotlinx-serialization = "1.9.0" mmkv = "2.2.4" sqlite = "2.6.1" -room = "2.8.2" -ksp = "2.2.20-2.0.2" +room = "2.8.3" +ksp = "2.3.0" -work = "2.10.5" +work = "2.11.0" # 环境 +config-appName = "TaskTTL" +config-versionCode = "100002" +config-versionName = "1.0.2" + android-compileSdk = "36" android-minSdk = "24" android-targetSdk = "36" @@ -76,7 +82,7 @@ ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" } #implementation("io.ktor:ktor-client-cio:2.3.12") # 桌面 ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" } -ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor"} +ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } # coil3 coil3-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3" } @@ -96,7 +102,7 @@ firebase-analytics = { module = "com.google.firebase:firebase-analytics" } firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebase" } # facebook -android-facebook-android-sdk = { module = "com.facebook.android:facebook-android-sdk", version.ref = "facebookAndroidSdkVersion" } +android-facebook-android-sdk = { module = "com.facebook.android:facebook-android-sdk", version.ref = "facebook" } # JSON kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } @@ -114,7 +120,14 @@ androidx-room-sqlite-wrapper = { module = "androidx.room:room-sqlite-wrapper", v android-play-services-ads-identifier = { module = "com.google.android.gms:play-services-ads-identifier", version.ref = "playServicesAds" } # 安卓任务 -androidx-work = {module="androidx.work:work-runtime-ktx", version.ref="work"} +androidx-work = { module = "androidx.work:work-runtime-ktx", version.ref = "work" } + +# 三方登录 +#androidx-login-google = { module = "com.google.android.gms:play-services-auth", version.ref = "google-login" } +androidx-login-credentials = { module = "androidx.credentials:credentials", version.ref = "credentials" } +androidx-login-credentials-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "credentials" } +androidx-login-googleid = {module = "com.google.android.libraries.identity.googleid:googleid",version.ref="googleid"} +androidx-login-facebook = { module = "com.facebook.android:facebook-login", version.ref = "facebook" } [plugins]