更新
This commit is contained in:
@@ -62,7 +62,7 @@ kotlin {
|
|||||||
implementation(project.dependencies.platform(libs.firebase.bom))
|
implementation(project.dependencies.platform(libs.firebase.bom))
|
||||||
implementation(libs.firebase.analytics)
|
implementation(libs.firebase.analytics)
|
||||||
// facebook
|
// facebook
|
||||||
// implementation(libs.android.facebook.android.sdk)
|
implementation(libs.android.facebook.android.sdk)
|
||||||
|
|
||||||
// mmkv
|
// mmkv
|
||||||
implementation(libs.android.mmkv)
|
implementation(libs.android.mmkv)
|
||||||
@@ -72,6 +72,9 @@ kotlin {
|
|||||||
|
|
||||||
// admob
|
// admob
|
||||||
implementation(libs.android.play.services.ads.identifier)
|
implementation(libs.android.play.services.ads.identifier)
|
||||||
|
|
||||||
|
// work
|
||||||
|
implementation(libs.androidx.work)
|
||||||
}
|
}
|
||||||
commonMain.dependencies {
|
commonMain.dependencies {
|
||||||
implementation(compose.runtime)
|
implementation(compose.runtime)
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:tools="http://schemas.android.com/tools"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<!-- 添加网络权限 -->
|
<!-- 添加网络权限 -->
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<!-- 添加广告ID权限 -->
|
<!-- 添加广告ID权限 -->
|
||||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID"
|
<uses-permission
|
||||||
|
android:name="com.google.android.gms.permission.AD_ID"
|
||||||
tools:ignore="AdvertisingIdPolicy" />
|
tools:ignore="AdvertisingIdPolicy" />
|
||||||
|
<!--通知权限-->
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<!--Android 12+ 精确闹钟-->
|
||||||
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".MainApplication"
|
android:name=".MainApplication"
|
||||||
@@ -17,9 +23,9 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
||||||
<activity
|
<activity
|
||||||
android:exported="true"
|
android:name=".MainActivity"
|
||||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode"
|
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|mnc|colorMode|density|fontScale|fontWeightAdjustment|keyboard|layoutDirection|locale|mcc|navigation|smallestScreenSize|touchscreen|uiMode"
|
||||||
android:name=".MainActivity">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
@@ -39,6 +45,50 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.facebook.FacebookActivity"
|
||||||
|
android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation" />
|
||||||
|
<activity
|
||||||
|
android:name="com.facebook.CustomTabActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="fb${facebookAppId}" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<!-- AlarmReceiver -->
|
||||||
|
<receiver
|
||||||
|
android:name=".core.alarm.AlarmReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true"
|
||||||
|
tools:ignore="ExportedReceiver">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.taskttl.ALARM_TRIGGER" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".core.alarm.BootReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
<!-- Facebook -->
|
||||||
|
<meta-data
|
||||||
|
android:name="com.facebook.sdk.ApplicationId"
|
||||||
|
android:value="${facebookAppId}" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.facebook.sdk.ClientToken"
|
||||||
|
android:value="${facebookClientToken}" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
|
|||||||
@@ -1,17 +1,34 @@
|
|||||||
package com.taskttl
|
package com.taskttl
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
|
// 设置全屏显示
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window, true)
|
||||||
|
|
||||||
|
// 设置状态栏图标颜色
|
||||||
|
val windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
|
||||||
|
// windowInsetsController.hide(WindowInsetsCompat.Type.statusBars() or WindowInsetsCompat.Type.navigationBars())
|
||||||
|
// windowInsetsController.systemBarsBehavior =
|
||||||
|
// WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||||
|
|
||||||
|
// 使用系统原生方法检测暗色主题
|
||||||
|
val isDarkTheme = resources.configuration.uiMode and
|
||||||
|
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||||
|
windowInsetsController.isAppearanceLightStatusBars = !isDarkTheme
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
App()
|
App()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.taskttl
|
package com.taskttl
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -12,15 +13,14 @@ import org.koin.android.ext.koin.androidLogger
|
|||||||
class MainApplication : Application() {
|
class MainApplication : Application() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
lateinit var instance: Application
|
@SuppressLint("StaticFieldLeak")
|
||||||
}
|
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
var currentActivity: Activity? = null
|
private var _currentActivity: Activity? = null
|
||||||
private set
|
|
||||||
|
|
||||||
|
|
||||||
|
lateinit var instance: Application
|
||||||
|
|
||||||
|
fun currentActivity(): Activity? = _currentActivity
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
instance = this
|
instance = this
|
||||||
@@ -32,12 +32,12 @@ class MainApplication : Application() {
|
|||||||
|
|
||||||
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
|
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
|
||||||
override fun onActivityResumed(activity: Activity) {
|
override fun onActivityResumed(activity: Activity) {
|
||||||
currentActivity = activity
|
_currentActivity = activity
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityPaused(activity: Activity) {
|
override fun onActivityPaused(activity: Activity) {
|
||||||
if (currentActivity == activity) {
|
if (_currentActivity == activity) {
|
||||||
currentActivity = null
|
_currentActivity = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.taskttl.core.analytics
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.facebook.FacebookSdk
|
||||||
|
import com.facebook.appevents.AppEventsLogger
|
||||||
|
|
||||||
|
actual object FacebookEventTracker {
|
||||||
|
|
||||||
|
private val logger: AppEventsLogger by lazy {
|
||||||
|
AppEventsLogger.newLogger(FacebookSdk.getApplicationContext())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun logEvent(event: FacebookEvent, params: Map<String, Any?>) {
|
||||||
|
val bundle = Bundle()
|
||||||
|
params.forEach { (key, value) ->
|
||||||
|
when (value) {
|
||||||
|
is String -> bundle.putString(key, value)
|
||||||
|
is Double -> bundle.putDouble(key, value)
|
||||||
|
is Int -> bundle.putInt(key, value)
|
||||||
|
is Boolean -> bundle.putBoolean(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.logEvent(event.eventName, bundle)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -215,9 +215,9 @@
|
|||||||
<string name="setting_privacy_rate">应用评价</string>
|
<string name="setting_privacy_rate">应用评价</string>
|
||||||
<string name="setting_privacy_rate_desc">如果喜欢,欢迎在商店留下五星好评</string>
|
<string name="setting_privacy_rate_desc">如果喜欢,欢迎在商店留下五星好评</string>
|
||||||
<string name="setting_about_app">关于应用</string>
|
<string name="setting_about_app">关于应用</string>
|
||||||
<string name="app_version_code">100001</string>
|
<string name="app_version_code">100002</string>
|
||||||
<string name="app_version_name">1.0.1</string>
|
<string name="app_version_name">1.0.2</string>
|
||||||
<string name="setting_about_app_desc">版本 1.0.1</string>
|
<string name="setting_about_app_desc">版本 1.0.2</string>
|
||||||
|
|
||||||
<!-- 反馈与帮助 -->
|
<!-- 反馈与帮助 -->
|
||||||
<string name="title_feedback">意见反馈</string>
|
<string name="title_feedback">意见反馈</string>
|
||||||
|
|||||||
@@ -216,9 +216,9 @@
|
|||||||
<string name="setting_privacy_rate">App Review</string>
|
<string name="setting_privacy_rate">App Review</string>
|
||||||
<string name="setting_privacy_rate_desc">If you like it, please leave a five-star review in the store</string>
|
<string name="setting_privacy_rate_desc">If you like it, please leave a five-star review in the store</string>
|
||||||
<string name="setting_about_app">About the App</string>
|
<string name="setting_about_app">About the App</string>
|
||||||
<string name="app_version_code">100001</string>
|
<string name="app_version_code">100002</string>
|
||||||
<string name="app_version_name">1.0.1</string>
|
<string name="app_version_name">1.0.2</string>
|
||||||
<string name="setting_about_app_desc">Version 1.0.1</string>
|
<string name="setting_about_app_desc">Version 1.0.2</string>
|
||||||
|
|
||||||
<!-- Feedback & Help -->
|
<!-- Feedback & Help -->
|
||||||
<string name="title_feedback">Feedback</string>
|
<string name="title_feedback">Feedback</string>
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package com.taskttl.core.analytics
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facebook 标准事件定义(官方事件名 + 必填字段)
|
||||||
|
* https://developers.facebook.com/docs/app-events/reference
|
||||||
|
*/
|
||||||
|
sealed class FacebookEvent(
|
||||||
|
val eventName: String,
|
||||||
|
val requiredKeys: List<String> = emptyList(),
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* 参数
|
||||||
|
* @return [Map<String, Any?>]
|
||||||
|
*/
|
||||||
|
abstract fun params(): Map<String, Any?>
|
||||||
|
|
||||||
|
data class Login(
|
||||||
|
val method: String? = null
|
||||||
|
) : FacebookEvent("fb_mobile_login", listOf("method")) {
|
||||||
|
override fun params() = mapOf("method" to method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内容类 / 电商类
|
||||||
|
data class ViewContent(
|
||||||
|
val contentId: String,
|
||||||
|
val contentType: String? = null,
|
||||||
|
val value: Double? = null,
|
||||||
|
val currency: String? = null
|
||||||
|
) : FacebookEvent("fb_mobile_content_view", listOf("content_id")) {
|
||||||
|
override fun params() = mapOf(
|
||||||
|
"content_id" to contentId,
|
||||||
|
"content_type" to contentType,
|
||||||
|
"value" to value,
|
||||||
|
"currency" to currency
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 游戏 / 广告类
|
||||||
|
data class AchieveLevel(val level: Int) :
|
||||||
|
FacebookEvent("fb_mobile_level_achieved", listOf("level")) {
|
||||||
|
override fun params() = mapOf("level" to level)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 广告次数
|
||||||
|
* @author admin
|
||||||
|
* @date 2025/10/19
|
||||||
|
* @constructor 创建[AdImpression]
|
||||||
|
* @param [adPlatform] 广告平台 如 "AdMob", "ironSource", "AppLovin" 等
|
||||||
|
* @param [adSource] 广告来源或网络,如 "admob_banner", "admob_interstitial"
|
||||||
|
* @param [adFormat] 广告类型,如 "banner", "interstitial", "rewarded_video"
|
||||||
|
* @param [adPlacementId] 广告位 ID
|
||||||
|
* @param [value] 收益金额
|
||||||
|
* @param [currency] 收益货币(ISO 代码,如 "USD")
|
||||||
|
* @param [placement] 自定义广告位名称
|
||||||
|
*/
|
||||||
|
data class AdImpression(
|
||||||
|
val adPlatform: String,
|
||||||
|
val adSource: String,
|
||||||
|
val adFormat: String,
|
||||||
|
val adPlacementId: String,
|
||||||
|
val currency: String,
|
||||||
|
val value: Double,
|
||||||
|
val placement: String? = null
|
||||||
|
) : FacebookEvent(
|
||||||
|
"ad_impression",
|
||||||
|
listOf("ad_platform", "ad_format", "ad_placement_id", "currency", "value")
|
||||||
|
) {
|
||||||
|
override fun params() = mapOf(
|
||||||
|
"ad_platform" to adPlatform,
|
||||||
|
"ad_source" to adSource,
|
||||||
|
"ad_format" to adFormat,
|
||||||
|
"ad_placement_id" to adPlacementId,
|
||||||
|
"currency" to currency,
|
||||||
|
"value" to value,
|
||||||
|
"placement" to placement
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AddToCart(
|
||||||
|
val contentId: String,
|
||||||
|
val value: Double,
|
||||||
|
val currency: String
|
||||||
|
) : FacebookEvent("fb_mobile_add_to_cart", listOf("content_id", "value", "currency")) {
|
||||||
|
override fun params() = mapOf(
|
||||||
|
"content_id" to contentId,
|
||||||
|
"value" to value,
|
||||||
|
"currency" to currency
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Purchase(
|
||||||
|
val value: Double,
|
||||||
|
val currency: String,
|
||||||
|
val contentId: String? = null
|
||||||
|
) : FacebookEvent("fb_mobile_purchase", listOf("value", "currency")) {
|
||||||
|
override fun params() = mapOf(
|
||||||
|
"value" to value,
|
||||||
|
"currency" to currency,
|
||||||
|
"content_id" to contentId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅 / 试用类
|
||||||
|
* @author admin
|
||||||
|
* @date 2025/10/19
|
||||||
|
* @constructor 创建[StartTrial]
|
||||||
|
* @param [trialName] 试验名称
|
||||||
|
*/
|
||||||
|
data class StartTrial(val trialName: String? = null) :
|
||||||
|
FacebookEvent("start_trial") {
|
||||||
|
override fun params() = mapOf("trial_name" to trialName)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅
|
||||||
|
* @author admin
|
||||||
|
* @date 2025/10/19
|
||||||
|
* @constructor 创建[Subscribe]
|
||||||
|
* @param [plan] 计划
|
||||||
|
*/
|
||||||
|
data class Subscribe(val plan: String? = null) :
|
||||||
|
FacebookEvent("subscribe") {
|
||||||
|
override fun params() = mapOf("plan" to plan)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.taskttl.core.analytics
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一事件接口
|
||||||
|
*/
|
||||||
|
expect object FacebookEventTracker {
|
||||||
|
fun logEvent(event: FacebookEvent, params: Map<String, Any?> = emptyMap())
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.taskttl.core.analytics
|
||||||
|
|
||||||
|
/**
|
||||||
|
* facebook事件验证器
|
||||||
|
* @author admin
|
||||||
|
* @date 2025/10/19
|
||||||
|
*/
|
||||||
|
object FacebookEventValidator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证
|
||||||
|
* @param [event] 事件
|
||||||
|
* @return [String?]
|
||||||
|
*/
|
||||||
|
fun validate(event: FacebookEvent): String? {
|
||||||
|
val params = event.params()
|
||||||
|
val missing = event.requiredKeys.filter { key ->
|
||||||
|
val v = params[key]
|
||||||
|
v == null || (v is String && v.isBlank())
|
||||||
|
}
|
||||||
|
return if (missing.isNotEmpty()) {
|
||||||
|
"⚠️ Missing required fields for ${event.eventName}: ${missing.joinToString()}"
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,4 +81,12 @@ object DateUtils {
|
|||||||
CountdownTime(days, hours, minutes, seconds, isExpired = false)
|
CountdownTime(days, hours, minutes, seconds, isExpired = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun LocalDateTime.toEpochMillis(): Long {
|
||||||
|
return this.toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Long.toLocalDateTime(): LocalDateTime {
|
||||||
|
return Instant.fromEpochMilliseconds(this).toLocalDateTime(TimeZone.currentSystemDefault())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.taskttl.data.di
|
package com.taskttl.data.di
|
||||||
|
|
||||||
|
import com.taskttl.core.notification.NotificationManager
|
||||||
import com.taskttl.data.repository.OnboardingRepository
|
import com.taskttl.data.repository.OnboardingRepository
|
||||||
import com.taskttl.data.repository.SettingsRepository
|
import com.taskttl.data.repository.SettingsRepository
|
||||||
import com.taskttl.data.repository.impl.OnboardingRepositoryImpl
|
import com.taskttl.data.repository.impl.OnboardingRepositoryImpl
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.taskttl.data.state
|
package com.taskttl.data.state
|
||||||
|
|
||||||
|
import com.taskttl.core.notification.NotificationPermissionManager
|
||||||
import com.taskttl.core.viewmodel.BaseUiState
|
import com.taskttl.core.viewmodel.BaseUiState
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -14,6 +15,7 @@ data class SettingsState(
|
|||||||
override val isLoading: Boolean = false,
|
override val isLoading: Boolean = false,
|
||||||
override val isProcessing: Boolean = false,
|
override val isProcessing: Boolean = false,
|
||||||
override val error: String? = null,
|
override val error: String? = null,
|
||||||
|
val isNotification: Boolean = NotificationPermissionManager.verifyPermission(),
|
||||||
) : BaseUiState()
|
) : BaseUiState()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,6 +40,17 @@ sealed class SettingsIntent {
|
|||||||
* @param [url] 网址
|
* @param [url] 网址
|
||||||
*/
|
*/
|
||||||
class OpenUrl(val url: String) : SettingsIntent()
|
class OpenUrl(val url: String) : SettingsIntent()
|
||||||
|
|
||||||
|
object RequestPermission : SettingsIntent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新通知状态
|
||||||
|
* @author DevTTL
|
||||||
|
* @date 2025/10/17
|
||||||
|
* @constructor 创建[UpdateNotificationStatus]
|
||||||
|
* @param [status] 状态
|
||||||
|
*/
|
||||||
|
class UpdateNotificationStatus(val status: Boolean) : SettingsIntent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ data class TaskState(
|
|||||||
* @constructor 创建[TaskIntent]
|
* @constructor 创建[TaskIntent]
|
||||||
*/
|
*/
|
||||||
sealed class TaskIntent {
|
sealed class TaskIntent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载任务
|
* 加载任务
|
||||||
* @author admin
|
* @author admin
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package com.taskttl.data.viewmodel
|
package com.taskttl.data.viewmodel
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
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.core.viewmodel.BaseViewModel
|
||||||
import com.taskttl.data.local.model.Category
|
import com.taskttl.data.local.model.Category
|
||||||
import com.taskttl.data.local.model.CategoryType
|
import com.taskttl.data.local.model.CategoryType
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.taskttl.data.viewmodel
|
package com.taskttl.data.viewmodel
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.taskttl.core.notification.NotificationPermissionCallback
|
||||||
|
import com.taskttl.core.notification.NotificationPermissionManager
|
||||||
import com.taskttl.core.utils.ExternalAppLauncher
|
import com.taskttl.core.utils.ExternalAppLauncher
|
||||||
|
import com.taskttl.core.utils.LogUtils
|
||||||
import com.taskttl.core.viewmodel.BaseViewModel
|
import com.taskttl.core.viewmodel.BaseViewModel
|
||||||
import com.taskttl.data.state.SettingsEffect
|
import com.taskttl.data.state.SettingsEffect
|
||||||
import com.taskttl.data.state.SettingsIntent
|
import com.taskttl.data.state.SettingsIntent
|
||||||
@@ -22,6 +25,8 @@ class SettingsViewModel() :
|
|||||||
when (intent) {
|
when (intent) {
|
||||||
is SettingsIntent.OpenAppRating -> openAppRating()
|
is SettingsIntent.OpenAppRating -> openAppRating()
|
||||||
is SettingsIntent.OpenUrl -> openUrl(intent.url)
|
is SettingsIntent.OpenUrl -> openUrl(intent.url)
|
||||||
|
is SettingsIntent.RequestPermission -> requestPermission()
|
||||||
|
is SettingsIntent.UpdateNotificationStatus -> updateNotificationStatus(intent.status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +42,25 @@ class SettingsViewModel() :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 通知权限被拒绝")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateNotificationStatus(status: Boolean) {
|
||||||
|
updateState { copy(isNotification = status) }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除错误
|
* 清除错误
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.taskttl.data.viewmodel
|
package com.taskttl.data.viewmodel
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.taskttl.core.utils.LogUtils
|
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.core.viewmodel.BaseViewModel
|
||||||
import com.taskttl.data.local.model.Category
|
import com.taskttl.data.local.model.Category
|
||||||
import com.taskttl.data.local.model.CategoryType
|
import com.taskttl.data.local.model.CategoryType
|
||||||
@@ -24,6 +27,8 @@ import taskttl.composeapp.generated.resources.task_status_update_failed
|
|||||||
import taskttl.composeapp.generated.resources.task_status_update_success
|
import taskttl.composeapp.generated.resources.task_status_update_success
|
||||||
import taskttl.composeapp.generated.resources.task_update_failed
|
import taskttl.composeapp.generated.resources.task_update_failed
|
||||||
import taskttl.composeapp.generated.resources.task_update_success
|
import taskttl.composeapp.generated.resources.task_update_success
|
||||||
|
import kotlin.time.Clock
|
||||||
|
import kotlin.time.ExperimentalTime
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 任务视图模型
|
* 任务视图模型
|
||||||
@@ -87,10 +92,7 @@ class TaskViewModel(
|
|||||||
try {
|
try {
|
||||||
launch {
|
launch {
|
||||||
categoryRepository.getCategoriesByType(CategoryType.TASK)
|
categoryRepository.getCategoriesByType(CategoryType.TASK)
|
||||||
.collect { categories ->
|
.collect { categories -> updateState { copy(categories = categories) } }
|
||||||
LogUtils.e("DevTTL", categories.toString())
|
|
||||||
updateState { copy(categories = categories) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
launch {
|
launch {
|
||||||
taskRepository.getAllTasks().collect { tasks ->
|
taskRepository.getAllTasks().collect { tasks ->
|
||||||
@@ -104,7 +106,6 @@ class TaskViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
LogUtils.e("DevTTL", e.message.toString())
|
|
||||||
val errStr = getString(Res.string.task_load_failed)
|
val errStr = getString(Res.string.task_load_failed)
|
||||||
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
updateState { copy(isLoading = false, error = e.message ?: errStr) }
|
||||||
}
|
}
|
||||||
@@ -131,19 +132,36 @@ class TaskViewModel(
|
|||||||
* 添加任务
|
* 添加任务
|
||||||
* @param [task] 任务
|
* @param [task] 任务
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
private fun addTask(task: Task) {
|
private fun addTask(task: Task) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
if (state.value.isProcessing) return@launch
|
if (state.value.isProcessing) return@launch
|
||||||
updateState { copy(isLoading = false, isProcessing = true) }
|
updateState { copy(isLoading = false, isProcessing = true) }
|
||||||
|
// 1️⃣ 插入任务
|
||||||
taskRepository.insertTask(task)
|
taskRepository.insertTask(task)
|
||||||
|
|
||||||
|
// 2️⃣ 创建通知
|
||||||
|
task.dueDate?.let {
|
||||||
|
NotificationManager.scheduleNotification(
|
||||||
|
NotificationPayload(
|
||||||
|
id = task.id,
|
||||||
|
title = task.title,
|
||||||
|
message = task.description,
|
||||||
|
triggerTimeMillis = task.dueDate.toEpochMillis(),
|
||||||
|
repeatType = NotificationRepeatType.NONE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3️⃣ UI 提示
|
||||||
sendEvent(TaskEffect.ShowMessage(getString(Res.string.task_add_success)))
|
sendEvent(TaskEffect.ShowMessage(getString(Res.string.task_add_success)))
|
||||||
sendEvent(TaskEffect.NavigateBack)
|
sendEvent(TaskEffect.NavigateBack)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val errStr = getString(Res.string.task_add_failed)
|
val errStr = getString(Res.string.task_add_failed)
|
||||||
updateState { copy(error = e.message ?: errStr) }
|
updateState { copy(error = e.message ?: errStr) }
|
||||||
} finally {
|
} finally {
|
||||||
updateState { copy(isLoading = false,isProcessing = false) }
|
updateState { copy(isLoading = false, isProcessing = false) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,13 +176,27 @@ class TaskViewModel(
|
|||||||
if (state.value.isProcessing) return@launch
|
if (state.value.isProcessing) return@launch
|
||||||
updateState { copy(isLoading = false, isProcessing = true) }
|
updateState { copy(isLoading = false, isProcessing = true) }
|
||||||
taskRepository.updateTask(task)
|
taskRepository.updateTask(task)
|
||||||
|
|
||||||
|
NotificationManager.cancelNotification(task.id)
|
||||||
|
task.dueDate?.let {
|
||||||
|
NotificationManager.scheduleNotification(
|
||||||
|
NotificationPayload(
|
||||||
|
id = task.id,
|
||||||
|
title = task.title,
|
||||||
|
message = task.description,
|
||||||
|
triggerTimeMillis = task.dueDate.toEpochMillis(),
|
||||||
|
repeatType = NotificationRepeatType.NONE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
sendEvent(TaskEffect.ShowMessage(getString(Res.string.task_update_success)))
|
sendEvent(TaskEffect.ShowMessage(getString(Res.string.task_update_success)))
|
||||||
sendEvent(TaskEffect.NavigateBack)
|
sendEvent(TaskEffect.NavigateBack)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val errStr = getString(Res.string.task_update_failed)
|
val errStr = getString(Res.string.task_update_failed)
|
||||||
updateState { copy(error = e.message ?: errStr) }
|
updateState { copy(error = e.message ?: errStr) }
|
||||||
} finally {
|
} finally {
|
||||||
updateState { copy(isLoading = false,isProcessing = false) }
|
updateState { copy(isLoading = false, isProcessing = false) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,12 +211,13 @@ class TaskViewModel(
|
|||||||
if (state.value.isProcessing) return@launch
|
if (state.value.isProcessing) return@launch
|
||||||
updateState { copy(isLoading = false, isProcessing = true) }
|
updateState { copy(isLoading = false, isProcessing = true) }
|
||||||
taskRepository.deleteTask(taskId)
|
taskRepository.deleteTask(taskId)
|
||||||
|
NotificationManager.cancelNotification(taskId)
|
||||||
sendEvent(TaskEffect.ShowMessage(getString(Res.string.task_delete_success)))
|
sendEvent(TaskEffect.ShowMessage(getString(Res.string.task_delete_success)))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val errStr = getString(Res.string.task_delete_failed)
|
val errStr = getString(Res.string.task_delete_failed)
|
||||||
updateState { copy(error = e.message ?: errStr) }
|
updateState { copy(error = e.message ?: errStr) }
|
||||||
} finally {
|
} finally {
|
||||||
updateState { copy(isLoading = false,isProcessing = false) }
|
updateState { copy(isLoading = false, isProcessing = false) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
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.routes.Routes
|
||||||
|
import com.taskttl.core.utils.LogUtils
|
||||||
import com.taskttl.data.local.model.OnboardingPage
|
import com.taskttl.data.local.model.OnboardingPage
|
||||||
import com.taskttl.data.state.OnboardingEffect
|
import com.taskttl.data.state.OnboardingEffect
|
||||||
import com.taskttl.data.state.OnboardingIntent
|
import com.taskttl.data.state.OnboardingIntent
|
||||||
@@ -63,6 +66,16 @@ fun OnboardingScreen(
|
|||||||
val pagerState =
|
val pagerState =
|
||||||
rememberPagerState(0, initialPageOffsetFraction = 0f, pageCount = { onboardingPages.size })
|
rememberPagerState(0, initialPageOffsetFraction = 0f, pageCount = { onboardingPages.size })
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
kotlinx.coroutines.delay(300)
|
||||||
|
NotificationPermissionManager.requestPermission(
|
||||||
|
object : NotificationPermissionCallback {
|
||||||
|
override fun onGranted() = LogUtils.e("DevTTL", "✅ Android 通知权限已授予")
|
||||||
|
override fun onDenied() = LogUtils.e("DevTTL", "❌ Android 通知权限被拒绝")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.effects.collectLatest { event ->
|
viewModel.effects.collectLatest { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
|
|||||||
@@ -21,17 +21,15 @@ import androidx.compose.material.icons.automirrored.filled.Help
|
|||||||
import androidx.compose.material.icons.filled.ChevronRight
|
import androidx.compose.material.icons.filled.ChevronRight
|
||||||
import androidx.compose.material.icons.filled.Person
|
import androidx.compose.material.icons.filled.Person
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material.icons.filled.Share
|
|
||||||
import androidx.compose.material.icons.filled.Storage
|
import androidx.compose.material.icons.filled.Storage
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@@ -40,8 +38,8 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
import com.taskttl.core.notification.NotificationPermissionManager
|
||||||
import com.taskttl.core.routes.Routes
|
import com.taskttl.core.routes.Routes
|
||||||
import com.taskttl.core.utils.ExternalAppLauncher
|
|
||||||
import com.taskttl.data.state.SettingsIntent
|
import com.taskttl.data.state.SettingsIntent
|
||||||
import com.taskttl.data.viewmodel.SettingsViewModel
|
import com.taskttl.data.viewmodel.SettingsViewModel
|
||||||
import com.taskttl.ui.components.AppHeader
|
import com.taskttl.ui.components.AppHeader
|
||||||
@@ -53,29 +51,18 @@ import taskttl.composeapp.generated.resources.Res
|
|||||||
import taskttl.composeapp.generated.resources.section_data_management
|
import taskttl.composeapp.generated.resources.section_data_management
|
||||||
import taskttl.composeapp.generated.resources.section_general_settings
|
import taskttl.composeapp.generated.resources.section_general_settings
|
||||||
import taskttl.composeapp.generated.resources.section_help_feedback
|
import taskttl.composeapp.generated.resources.section_help_feedback
|
||||||
import taskttl.composeapp.generated.resources.section_social_share
|
|
||||||
import taskttl.composeapp.generated.resources.setting_about_app
|
import taskttl.composeapp.generated.resources.setting_about_app
|
||||||
import taskttl.composeapp.generated.resources.setting_about_app_desc
|
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
|
||||||
import taskttl.composeapp.generated.resources.setting_category_management_desc
|
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
|
||||||
import taskttl.composeapp.generated.resources.setting_feedback_desc
|
import taskttl.composeapp.generated.resources.setting_feedback_desc
|
||||||
import taskttl.composeapp.generated.resources.setting_invite_friend
|
|
||||||
import taskttl.composeapp.generated.resources.setting_invite_friend_desc
|
|
||||||
import taskttl.composeapp.generated.resources.setting_language
|
|
||||||
import taskttl.composeapp.generated.resources.setting_language_desc
|
|
||||||
import taskttl.composeapp.generated.resources.setting_privacy_policy
|
import taskttl.composeapp.generated.resources.setting_privacy_policy
|
||||||
import taskttl.composeapp.generated.resources.setting_privacy_policy_desc
|
import taskttl.composeapp.generated.resources.setting_privacy_policy_desc
|
||||||
import taskttl.composeapp.generated.resources.setting_privacy_rate
|
import taskttl.composeapp.generated.resources.setting_privacy_rate
|
||||||
import taskttl.composeapp.generated.resources.setting_privacy_rate_desc
|
import taskttl.composeapp.generated.resources.setting_privacy_rate_desc
|
||||||
import taskttl.composeapp.generated.resources.setting_push_notification
|
import taskttl.composeapp.generated.resources.setting_push_notification
|
||||||
import taskttl.composeapp.generated.resources.setting_push_notification_desc
|
import taskttl.composeapp.generated.resources.setting_push_notification_desc
|
||||||
import taskttl.composeapp.generated.resources.setting_share_achievement
|
|
||||||
import taskttl.composeapp.generated.resources.setting_share_achievement_desc
|
|
||||||
import taskttl.composeapp.generated.resources.title_app_settings
|
import taskttl.composeapp.generated.resources.title_app_settings
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,8 +73,21 @@ import taskttl.composeapp.generated.resources.title_app_settings
|
|||||||
@Preview
|
@Preview
|
||||||
fun SettingsScreen(
|
fun SettingsScreen(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
viewModel: SettingsViewModel = koinViewModel()
|
viewModel: SettingsViewModel = koinViewModel(),
|
||||||
) {
|
) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
|
||||||
|
// 轮询监听权限状态变化
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
while (true) {
|
||||||
|
val current = NotificationPermissionManager.verifyPermission()
|
||||||
|
if (state.isNotification != current) {
|
||||||
|
viewModel.handleIntent(SettingsIntent.UpdateNotificationStatus(current))
|
||||||
|
}
|
||||||
|
kotlinx.coroutines.delay(2000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -151,16 +151,15 @@ fun SettingsScreen(
|
|||||||
|
|
||||||
|
|
||||||
// 通用设置
|
// 通用设置
|
||||||
// SectionTitle(Icons.Default.Settings, Res.string.section_general_settings)
|
SectionTitle(Icons.Default.Settings, Res.string.section_general_settings)
|
||||||
|
|
||||||
// var notificationEnabled by remember { mutableStateOf(true) }
|
SettingItem(
|
||||||
// SettingItem(
|
titleRes = Res.string.setting_push_notification,
|
||||||
// titleRes = Res.string.setting_push_notification,
|
descriptionRes = Res.string.setting_push_notification_desc,
|
||||||
// descriptionRes = Res.string.setting_push_notification_desc,
|
showSwitch = true,
|
||||||
// showSwitch = true,
|
switchState = state.isNotification,
|
||||||
// switchState = notificationEnabled,
|
onSwitchChanged = { viewModel.handleIntent(SettingsIntent.RequestPermission) }
|
||||||
// onSwitchChanged = { notificationEnabled = it }
|
)
|
||||||
// )
|
|
||||||
|
|
||||||
// var darkMode by remember { mutableStateOf(false) }
|
// var darkMode by remember { mutableStateOf(false) }
|
||||||
//
|
//
|
||||||
@@ -291,7 +290,7 @@ fun SettingItem(
|
|||||||
onSwitchChanged: ((Boolean) -> Unit)? = null,
|
onSwitchChanged: ((Boolean) -> Unit)? = null,
|
||||||
showArrow: Boolean = false,
|
showArrow: Boolean = false,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClick: (() -> Unit)? = null
|
onClick: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ import com.taskttl.core.routes.Routes
|
|||||||
import com.taskttl.core.ui.ActionButtonListItem
|
import com.taskttl.core.ui.ActionButtonListItem
|
||||||
import com.taskttl.core.ui.ErrorDialog
|
import com.taskttl.core.ui.ErrorDialog
|
||||||
import com.taskttl.core.ui.LoadingOverlay
|
import com.taskttl.core.ui.LoadingOverlay
|
||||||
|
|
||||||
import com.taskttl.core.utils.ToastUtils
|
import com.taskttl.core.utils.ToastUtils
|
||||||
import com.taskttl.data.local.model.Task
|
import com.taskttl.data.local.model.Task
|
||||||
import com.taskttl.data.state.TaskEffect
|
import com.taskttl.data.state.TaskEffect
|
||||||
@@ -245,7 +244,6 @@ fun TaskScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 悬浮按钮
|
// 悬浮按钮
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = { navController.navigate(Routes.Main.Task.AddTask) },
|
onClick = { navController.navigate(Routes.Main.Task.AddTask) },
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.taskttl.core.analytics
|
||||||
|
|
||||||
|
import platform.Foundation.NSNumber
|
||||||
|
import platform.Foundation.NSString
|
||||||
|
import platform.Foundation.create
|
||||||
|
import platform.FBSDKCoreKit.*
|
||||||
|
|
||||||
|
actual object FacebookEventTracker {
|
||||||
|
actual fun logEvent(event: FacebookEvent, params: Map<String, Any?>) {
|
||||||
|
val mutableDict = mutableMapOf<Any?, Any?>()
|
||||||
|
params.forEach { (key, value) ->
|
||||||
|
mutableDict[key] = when (value) {
|
||||||
|
is String -> value as NSString
|
||||||
|
is Double -> NSNumber(value)
|
||||||
|
is Int -> NSNumber(value)
|
||||||
|
is Boolean -> NSNumber(value)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val eventParams = if (mutableDict.isEmpty()) null else mutableDict as Map<Any?, Any?>
|
||||||
|
AppEvents.shared.logEvent(event.eventName, parameters = eventParams)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,15 +5,15 @@ androidx-appcompat = "1.7.1"
|
|||||||
androidx-constraintlayout = "2.2.1"
|
androidx-constraintlayout = "2.2.1"
|
||||||
androidx-core = "1.17.0"
|
androidx-core = "1.17.0"
|
||||||
androidx-espresso = "3.7.0"
|
androidx-espresso = "3.7.0"
|
||||||
androidx-lifecycle = "2.9.4"
|
androidx-lifecycle = "2.9.5"
|
||||||
androidx-testExt = "1.3.0"
|
androidx-testExt = "1.3.0"
|
||||||
composeHotReload = "1.0.0-rc02"
|
composeHotReload = "1.0.0-rc02"
|
||||||
composeMultiplatform = "1.9.0"
|
composeMultiplatform = "1.9.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
kotlin = "2.2.20"
|
kotlin = "2.2.20"
|
||||||
kotlinx-coroutines = "1.10.2"
|
kotlinx-coroutines = "1.10.2"
|
||||||
|
|
||||||
navigationCompose = "2.9.0"
|
navigationCompose = "2.9.1"
|
||||||
koin = "4.1.1"
|
koin = "4.1.1"
|
||||||
ktor = "3.3.1"
|
ktor = "3.3.1"
|
||||||
coil3 = "3.3.0"
|
coil3 = "3.3.0"
|
||||||
@@ -32,16 +32,18 @@ sqlite = "2.6.1"
|
|||||||
room = "2.8.2"
|
room = "2.8.2"
|
||||||
ksp = "2.2.20-2.0.2"
|
ksp = "2.2.20-2.0.2"
|
||||||
|
|
||||||
|
work = "2.10.5"
|
||||||
|
|
||||||
# 环境
|
# 环境
|
||||||
android-compileSdk = "36"
|
android-compileSdk = "36"
|
||||||
android-minSdk = "24"
|
android-minSdk = "24"
|
||||||
android-targetSdk = "36"
|
android-targetSdk = "36"
|
||||||
|
|
||||||
android-versionCode = "100001"
|
android-versionCode = "100002"
|
||||||
android-versionName = "1.0.1"
|
android-versionName = "1.0.2"
|
||||||
|
|
||||||
android-facebookAppId = "1203530117944408"
|
android-facebookAppId = "801567145847535"
|
||||||
android-facebookClientToken = "1ee2da9430c1a589e8aa623bfaaaa586"
|
android-facebookClientToken = "15db47cd9d8d35ccaa49f43e30beefaf"
|
||||||
|
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
@@ -111,6 +113,10 @@ androidx-room-sqlite-wrapper = { module = "androidx.room:room-sqlite-wrapper", v
|
|||||||
# 谷歌Ads
|
# 谷歌Ads
|
||||||
android-play-services-ads-identifier = { module = "com.google.android.gms:play-services-ads-identifier", version.ref = "playServicesAds" }
|
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"}
|
||||||
|
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
androidLibrary = { id = "com.android.library", version.ref = "agp" }
|
androidLibrary = { id = "com.android.library", version.ref = "agp" }
|
||||||
|
|||||||
Reference in New Issue
Block a user