diff --git a/README.md b/README.md index 619e590..a03fd61 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# Activity Analyzer +# Chronosight ![Some Badge - Optional](https://img.shields.io/badge/Status-Development-blue.svg) [![Kotlin Version - Optional](https://img.shields.io/badge/Kotlin-2.1.20-blueviolet.svg)](https://kotlinlang.org) **English | [简体中文](README_zh.md)** -**Understand your digital habits with Activity Analyzer, a cross-platform screen time tracking and analysis application.** +**Understand your digital habits with Chronosight, a cross-platform screen time tracking and analysis application.** -Gain valuable insights into how you spend your time on your devices. Activity Analyzer helps you track your screen time, analyze your app usage, and ultimately promotes better digital well-being across your phone, tablet, and computer. +Gain valuable insights into how you spend your time on your devices. Chronosight helps you track your screen time, analyze your app usage, and ultimately promotes better digital well-being across your phone, tablet, and computer. ## Key Features @@ -19,7 +19,7 @@ Gain valuable insights into how you spend your time on your devices. Activity An ## Technology Stack -Activity Analyzer is built with the following technologies: +Chronosight is built with the following technologies: * **Kotlin Multiplatform Mobile (KMM):** Powers the core business logic, data analysis, and shared functionality across all platforms. * **Android:** Native UI development using Kotlin and Jetpack Compose. @@ -29,7 +29,7 @@ Activity Analyzer is built with the following technologies: ## Platform Support -Activity Analyzer is currently targeting the following platforms: +Chronosight is currently targeting the following platforms: * 📱 **Mobile:** Android, iOS * 💻 **Desktop:** macOS, Windows, Linux @@ -39,7 +39,7 @@ Activity Analyzer is currently targeting the following platforms: If you're interested in contributing or building the project yourself, here's a quick guide: 1. **Prerequisites:** Ensure you have the necessary SDKs and development environments set up for Android, iOS, and Desktop Kotlin development. -2. **Clone the Repository:** `git clone https://github.com/grtsinry43/ActivityAnalyzer.git` +2. **Clone the Repository:** `git clone https://github.com/grtsinry43/Chronosight.git` 3. **Open project:** Open the project in IntelliJ IDEA (or Android Studio). 4. **Build and Run:** * **Android:** Run the `androidApp` module. diff --git a/README_zh.md b/README_zh.md index f32a98e..f41bf66 100644 --- a/README_zh.md +++ b/README_zh.md @@ -1,13 +1,13 @@ -# Activity Analyzer +# Chronosight ![一些徽章 - 可选](https://img.shields.io/badge/状态-开发中-blue.svg) [![Kotlin 版本 - 可选](https://img.shields.io/badge/Kotlin-2.1.20-blueviolet.svg)](https://kotlinlang.org) **[English](README.md) | 简体中文** -**时间都去哪儿了?通过 Activity Analyzer 了解您的数字习惯,跨平台跟踪和分析您的屏幕时间。** +**时间都去哪儿了?通过 Chronosight 了解您的数字习惯,跨平台跟踪和分析您的屏幕时间。** -深入了解您在设备上花费时间的方式。Activity Analyzer 帮助您跟踪屏幕使用时间、分析您的应用使用情况,并最终促进您在手机、平板电脑和电脑上实现更好的数字健康。 +深入了解您在设备上花费时间的方式。Chronosight 帮助您跟踪屏幕使用时间、分析您的应用使用情况,并最终促进您在手机、平板电脑和电脑上实现更好的数字健康。 ## 主要功能 @@ -35,7 +35,7 @@ 如果您有兴趣贡献代码或自行构建项目,请参考以下简要指南: 1. **前提条件:** 确保您已设置好 Android、iOS 和桌面 Kotlin 开发所需的 SDK 和开发环境。 -2. **克隆代码仓库:** `git clone https://github.com/grtsinry43/ActivityAnalyzer.git` +2. **克隆代码仓库:** `git clone https://github.com/grtsinry43/Chronosight.git` 3. **打开:** 在 IntelliJ IDEA 或 Android Studio 中打开项目。 4. **构建并运行:** * **Android:** 运行 `androidApp` 模块。 diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 04ef0cc..fce7116 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -37,6 +37,7 @@ kotlin { implementation(libs.androidx.lifecycle.runtime.compose) implementation(projects.shared) implementation(compose.materialIconsExtended) + implementation(libs.moko.resources.compose) } desktopMain.dependencies { implementation(compose.desktop.currentOs) @@ -46,11 +47,11 @@ kotlin { } android { - namespace = "com.grtsinry43.activityanalyzer" + namespace = "com.grtsinry43.chronosight" compileSdk = libs.versions.android.compileSdk.get().toInt() defaultConfig { - applicationId = "com.grtsinry43.activityanalyzer" + applicationId = "com.grtsinry43.chronosight" minSdk = libs.versions.android.minSdk.get().toInt() targetSdk = libs.versions.android.targetSdk.get().toInt() versionCode = 1 @@ -78,11 +79,11 @@ dependencies { compose.desktop { application { - mainClass = "com.grtsinry43.activityanalyzer.MainKt" + mainClass = "com.grtsinry43.chronosight.MainKt" nativeDistributions { targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) - packageName = "com.grtsinry43.activityanalyzer" + packageName = "com.grtsinry43.chronosight" packageVersion = "1.0.0" } } diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml index d56ee80..6eb1870 100644 --- a/composeApp/src/androidMain/AndroidManifest.xml +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -1,12 +1,16 @@ - + + + - \ No newline at end of file + + diff --git a/composeApp/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/MainActivity.kt b/composeApp/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/MainActivity.kt deleted file mode 100644 index 6926e4b..0000000 --- a/composeApp/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/MainActivity.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.grtsinry43.activityanalyzer - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.Preview - -class MainActivity : ComponentActivity() { - @OptIn(ExperimentalMaterial3Api::class) - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - App() - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Preview -@Composable -fun AppAndroidPreview() { - App() -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/grtsinry43/chronosight/MainActivity.kt b/composeApp/src/androidMain/kotlin/com/grtsinry43/chronosight/MainActivity.kt new file mode 100644 index 0000000..a2c3ea6 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/grtsinry43/chronosight/MainActivity.kt @@ -0,0 +1,45 @@ +package com.grtsinry43.chronosight + +import android.content.Intent +import android.os.Bundle +import android.provider.Settings +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AppWithUsageAccessHandler() { + val context = LocalContext.current + App( + onRequestUsageAccess = { + try { + val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + } catch (_: Exception) {} + } + ) +} + +class MainActivity : ComponentActivity() { + @OptIn(ExperimentalMaterial3Api::class) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + AppWithUsageAccessHandler() + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +fun AppAndroidPreview() { + App() +} + diff --git a/composeApp/src/androidMain/kotlin/com/grtsinry43/chronosight/MainApplication.kt b/composeApp/src/androidMain/kotlin/com/grtsinry43/chronosight/MainApplication.kt new file mode 100644 index 0000000..3663463 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/grtsinry43/chronosight/MainApplication.kt @@ -0,0 +1,12 @@ +package com.grtsinry43.chronosight + +import android.app.Application + +class MainApplication : Application() { + override fun onCreate() { + super.onCreate() + // 初始化全局屏幕时长统计 + initGlobalScreenTimeManager(this) + } +} + diff --git a/composeApp/src/androidMain/res/values/strings.xml b/composeApp/src/androidMain/res/values/strings.xml index fb3227b..b116f35 100644 --- a/composeApp/src/androidMain/res/values/strings.xml +++ b/composeApp/src/androidMain/res/values/strings.xml @@ -1,3 +1,3 @@ - Activity Analyzer + Chronosight \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/App.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/App.kt deleted file mode 100644 index 97f6c2d..0000000 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/App.kt +++ /dev/null @@ -1,192 +0,0 @@ -package com.grtsinry43.activityanalyzer - -import androidx.compose.foundation.* -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.* -import androidx.compose.material3.* -import androidx.compose.runtime.* -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.graphics.vector.ImageVector -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.grtsinry43.activityanalyzer.screens.MobileAboutScreen -import com.grtsinry43.activityanalyzer.screens.MobileAnalyticsScreen -import com.grtsinry43.activityanalyzer.screens.MobileHomeScreen -import com.grtsinry43.activityanalyzer.screens.MobileProfileScreen -import com.grtsinry43.activityanalyzer.screens.MobileReportsScreen -import com.grtsinry43.activityanalyzer.screens.MobileSettingsScreen -import com.grtsinry43.activityanalyzer.theme.AppThemes -import kotlinx.coroutines.launch - -// --- Main App Composable --- -@Composable -@ExperimentalMaterial3Api -fun App() { - var isDarkTheme by remember { mutableStateOf(false) } - val currentColors = if (isDarkTheme) AppThemes.DarkThemeColors else AppThemes.LightThemeColors - - // Navigation state - var selectedNavItemIndex by remember { mutableStateOf(0) } - val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) - val scope = rememberCoroutineScope() - - val navItems = listOf( - "Home" to Icons.Default.Home, - "Analytics" to Icons.Default.BarChart, - "Reports" to Icons.Default.Assessment, - "Profile" to Icons.Default.AccountCircle, - "Settings" to Icons.Default.Settings, - "About" to Icons.Default.Info - ) - - val currentScreenTitle = navItems[selectedNavItemIndex].first - - MaterialTheme( - colorScheme = if (isDarkTheme) darkColorScheme( - primary = currentColors.accent, - secondary = currentColors.accentVariant, - background = currentColors.background, - surface = currentColors.surface, - onPrimary = currentColors.onAccent, - onSecondary = currentColors.onAccent, - onBackground = currentColors.onBackground, - onSurface = currentColors.onSurface, - ) else lightColorScheme( - primary = currentColors.accent, - secondary = currentColors.accentVariant, - background = currentColors.background, - surface = currentColors.surface, - onPrimary = currentColors.onAccent, - onSecondary = currentColors.onAccent, - onBackground = currentColors.onBackground, - onSurface = currentColors.onSurface, - ) - ) { - ModalNavigationDrawer( - drawerState = drawerState, - drawerContent = { - ModalDrawerSheet( - drawerContainerColor = currentColors.surface, - drawerContentColor = currentColors.onSurface - ) { - // Drawer Header with User Info - Column( - modifier = Modifier - .fillMaxWidth() - .background(currentColors.accent.copy(alpha = 0.1f)) - .padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Icon( - imageVector = Icons.Filled.AccountCircle, - contentDescription = "User Avatar", - tint = currentColors.accent, - modifier = Modifier - .size(64.dp) - .clip(CircleShape) - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "grtsinry43", // Placeholder Nickname - color = currentColors.onSurface, - fontSize = 16.sp, - fontWeight = FontWeight.Medium - ) - Text( - text = "grtsinry43@outlook.com", // Placeholder Email - color = currentColors.secondaryText, - fontSize = 12.sp - ) - } - Spacer(Modifier.height(12.dp)) - // Navigation Items - navItems.forEachIndexed { index, item -> - NavigationDrawerItem( - icon = { - Icon( - item.second, - contentDescription = item.first, - tint = if (selectedNavItemIndex == index) currentColors.accent else currentColors.onSurface.copy( - alpha = 0.7f - ) - ) - }, - label = { - Text( - item.first, - color = if (selectedNavItemIndex == index) currentColors.accent else currentColors.onSurface.copy( - alpha = 0.7f - ) - ) - }, - selected = selectedNavItemIndex == index, - onClick = { - selectedNavItemIndex = index - scope.launch { drawerState.close() } - }, - modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding), - colors = NavigationDrawerItemDefaults.colors( - selectedContainerColor = currentColors.accent.copy(alpha = 0.1f), - unselectedContainerColor = Color.Transparent, - selectedTextColor = currentColors.accent, - unselectedTextColor = currentColors.onSurface.copy(alpha = 0.7f), - selectedIconColor = currentColors.accent, - unselectedIconColor = currentColors.onSurface.copy(alpha = 0.7f) - ) - ) - } - } - } - ) { - Scaffold( - containerColor = currentColors.background, - topBar = { - TopAppBar( - title = { Text(currentScreenTitle, color = currentColors.onSurface) }, - navigationIcon = { - IconButton(onClick = { scope.launch { drawerState.open() } }) { - Icon( - Icons.Filled.Menu, - contentDescription = "Open Navigation Drawer", - tint = currentColors.onSurface - ) - } - }, - colors = TopAppBarDefaults.topAppBarColors( - containerColor = currentColors.surface, - titleContentColor = currentColors.onSurface - ) - ) - } - ) { paddingValues -> - Box( - modifier = Modifier.padding(paddingValues).fillMaxSize() - .background(currentColors.background) - ) { - when (selectedNavItemIndex) { - 0 -> MobileHomeScreen(colors = currentColors) - 1 -> MobileAnalyticsScreen(colors = currentColors) - 2 -> MobileReportsScreen(colors = currentColors) - 3 -> MobileProfileScreen(colors = currentColors) - 4 -> MobileSettingsScreen( - colors = currentColors, - isDarkTheme = isDarkTheme, - onThemeChange = { isDarkTheme = it } - ) - - 5 -> MobileAboutScreen(colors = currentColors) - } - } - } - } - } -} diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/App.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/App.kt new file mode 100644 index 0000000..45b5f67 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/App.kt @@ -0,0 +1,119 @@ +package com.grtsinry43.chronosight + +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.grtsinry43.chronosight.screens.MobileAboutScreen +import com.grtsinry43.chronosight.screens.MobileAnalyticsScreen +import com.grtsinry43.chronosight.screens.MobileHomeScreen +import com.grtsinry43.chronosight.screens.MobileProfileScreen +import com.grtsinry43.chronosight.screens.MobileReportsScreen +import com.grtsinry43.chronosight.screens.MobileSettingsScreen +import com.grtsinry43.chronosight.theme.AppThemes +import dev.icerock.moko.resources.compose.* +import com.grtsinry43.chronosight.MR + +@Composable +@ExperimentalMaterial3Api +fun App(onRequestUsageAccess: (() -> Unit)? = null) { + var isDarkTheme by remember { mutableStateOf(false) } + val currentColors = if (isDarkTheme) AppThemes.DarkThemeColors else AppThemes.LightThemeColors + + // Navigation state + var selectedNavItemIndex by remember { mutableStateOf(0) } + val scope = rememberCoroutineScope() + + val navItems = listOf( + stringResource(MR.strings.home) to Icons.Default.Home, + stringResource(MR.strings.analytics) to Icons.Default.BarChart, + stringResource(MR.strings.reports) to Icons.Default.Assessment, + stringResource(MR.strings.profile) to Icons.Default.AccountCircle, + stringResource(MR.strings.settings) to Icons.Default.Settings, + stringResource(MR.strings.about) to Icons.Default.Info + ) + + val currentScreenTitle = navItems[selectedNavItemIndex].first + val isAndroid = Platform.isAndroid + + MaterialTheme( + colorScheme = if (isDarkTheme) darkColorScheme( + primary = currentColors.accent, + secondary = currentColors.accentVariant, + background = currentColors.background, + surface = currentColors.surface, + onPrimary = currentColors.onAccent, + onSecondary = currentColors.onAccent, + onBackground = currentColors.onBackground, + onSurface = currentColors.onSurface, + ) else lightColorScheme( + primary = currentColors.accent, + secondary = currentColors.accentVariant, + background = currentColors.background, + surface = currentColors.surface, + onPrimary = currentColors.onAccent, + onSecondary = currentColors.onAccent, + onBackground = currentColors.onBackground, + onSurface = currentColors.onSurface, + ) + ) { + if (isAndroid) { + Scaffold( + containerColor = currentColors.background, + topBar = { + TopAppBar( + title = { Text(currentScreenTitle, color = currentColors.onSurface) }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = currentColors.surface, + titleContentColor = currentColors.onSurface + ) + ) + }, + bottomBar = { + NavigationBar(containerColor = currentColors.surface) { + navItems.forEachIndexed { idx, item -> + NavigationBarItem( + selected = selectedNavItemIndex == idx, + onClick = { selectedNavItemIndex = idx }, + icon = { Icon(item.second, contentDescription = item.first) }, + label = { Text(item.first) }, + colors = NavigationBarItemDefaults.colors( + selectedIconColor = currentColors.accent, + selectedTextColor = currentColors.accent, + indicatorColor = currentColors.accent.copy(alpha = 0.12f) + ) + ) + } + } + } + ) { paddingValues -> + Box( + modifier = Modifier.padding(paddingValues).fillMaxSize() + .background(currentColors.background) + ) { + when (selectedNavItemIndex) { + 0 -> MobileHomeScreen(colors = currentColors, onRequestUsageAccess = onRequestUsageAccess) + 1 -> MobileAnalyticsScreen(colors = currentColors) + 2 -> MobileReportsScreen(colors = currentColors) + 3 -> MobileProfileScreen(colors = currentColors) + 4 -> MobileSettingsScreen( + colors = currentColors, + isDarkTheme = isDarkTheme, + onThemeChange = { isDarkTheme = it }, + currentLocale = "en", // 仅用于设置页面UI + onLocaleChange = { /* 仅UI切换,不影响App语言 */ } + ) + 5 -> MobileAboutScreen(colors = currentColors) + } + } + } + } else { + // TODO: 非Android平台的实现 + } + } +} diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/ActivityItemC.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/ActivityItemC.kt similarity index 96% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/ActivityItemC.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/ActivityItemC.kt index 78aef5e..70dfc83 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/ActivityItemC.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/ActivityItemC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.components +package com.grtsinry43.chronosight.components import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -17,7 +17,7 @@ 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 com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/ChartPlaceholderC.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/ChartPlaceholderC.kt similarity index 94% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/ChartPlaceholderC.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/ChartPlaceholderC.kt index 29cf698..b20d65b 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/ChartPlaceholderC.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/ChartPlaceholderC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.components +package com.grtsinry43.chronosight.components import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -14,7 +14,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/InfoItemC.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/InfoItemC.kt similarity index 96% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/InfoItemC.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/InfoItemC.kt index 1999093..e870de1 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/InfoItemC.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/InfoItemC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.components +package com.grtsinry43.chronosight.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -16,7 +16,7 @@ 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 com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/MetricCardC.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/MetricCardC.kt similarity index 95% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/MetricCardC.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/MetricCardC.kt index aace9a2..7180159 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/MetricCardC.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/MetricCardC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.components +package com.grtsinry43.chronosight.components import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons @@ -13,7 +13,7 @@ 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.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/ReportItemC.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/ReportItemC.kt similarity index 96% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/ReportItemC.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/ReportItemC.kt index fb5cb9a..c4e5764 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/ReportItemC.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/ReportItemC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.components +package com.grtsinry43.chronosight.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -16,7 +16,7 @@ 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 com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/SettingItemC.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/SettingItemC.kt similarity index 97% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/SettingItemC.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/SettingItemC.kt index 5affdd2..16e7507 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/SettingItemC.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/SettingItemC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.components +package com.grtsinry43.chronosight.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -13,7 +13,7 @@ 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 com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/SimpleListItemC.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/SimpleListItemC.kt similarity index 94% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/SimpleListItemC.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/SimpleListItemC.kt index e42ae1f..51fe925 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/SimpleListItemC.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/SimpleListItemC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.components +package com.grtsinry43.chronosight.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -15,7 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/StyledButtonC.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/StyledButtonC.kt similarity index 95% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/StyledButtonC.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/StyledButtonC.kt index 4b19506..910300f 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/StyledButtonC.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/StyledButtonC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.components +package com.grtsinry43.chronosight.components import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.* @@ -13,7 +13,7 @@ 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 com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/StyledCard.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/StyledCard.kt similarity index 92% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/StyledCard.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/StyledCard.kt index 518ffa2..456e05a 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/components/StyledCard.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/components/StyledCard.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.components +package com.grtsinry43.chronosight.components import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.ColumnScope @@ -11,7 +11,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileAboutScreenC.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileAboutScreenC.kt similarity index 95% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileAboutScreenC.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileAboutScreenC.kt index 251e626..9f0a54b 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileAboutScreenC.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileAboutScreenC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.screens +package com.grtsinry43.chronosight.screens import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -14,9 +14,9 @@ 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.grtsinry43.activityanalyzer.components.InfoItem -import com.grtsinry43.activityanalyzer.components.SettingItem -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.components.InfoItem +import com.grtsinry43.chronosight.components.SettingItem +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable @@ -36,7 +36,7 @@ fun MobileAboutScreen(colors: AppThemes.Colors) { modifier = Modifier.size(72.dp) // Slightly smaller for mobile about screen ) Text( - "Activity Analyzer", + "Chronosight", style = MaterialTheme.typography.headlineMedium.copy( color = colors.onBackground, fontWeight = FontWeight.Bold, diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileAnalyticsScreenC.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileAnalyticsScreenC.kt similarity index 51% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileAnalyticsScreenC.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileAnalyticsScreenC.kt index 9c082ed..a81348d 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileAnalyticsScreenC.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileAnalyticsScreenC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.screens +package com.grtsinry43.chronosight.screens import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -11,18 +11,40 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.grtsinry43.activityanalyzer.components.ChartPlaceholder -import com.grtsinry43.activityanalyzer.components.MetricCard -import com.grtsinry43.activityanalyzer.components.StyledButton -import com.grtsinry43.activityanalyzer.components.StyledCard -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.components.ChartPlaceholder +import com.grtsinry43.chronosight.components.MetricCard +import com.grtsinry43.chronosight.components.StyledButton +import com.grtsinry43.chronosight.components.StyledCard +import com.grtsinry43.chronosight.formatMillisToHourMin +import com.grtsinry43.chronosight.theme.AppThemes +import com.grtsinry43.chronosight.getScreenTime +import com.grtsinry43.chronosight.getAppUsageStats +import com.grtsinry43.chronosight.AppUsageInfo +import kotlinx.coroutines.delay import org.jetbrains.compose.ui.tooling.preview.Preview +import java.util.Calendar +import java.util.Locale +import dev.icerock.moko.resources.compose.stringResource +import com.grtsinry43.chronosight.MR @Composable fun MobileAnalyticsScreen(colors: AppThemes.Colors) { - var selectedTimeRange by remember { mutableStateOf("Last 7 Days") } - val timeRanges = - listOf("Today", "Yesterday", "Last 7 Days", "Last 30 Days") // Simplified for mobile + // Get string values inside the composable scope + val todayStr = stringResource(MR.strings.today) + val yesterdayStr = stringResource(MR.strings.yesterday) + val last7DaysStr = stringResource(MR.strings.last_7_days) + val last30DaysStr = stringResource(MR.strings.last_30_days) + var selectedTimeRange by remember { mutableStateOf(last7DaysStr) } + val timeRanges = listOf(todayStr, yesterdayStr, last7DaysStr, last30DaysStr) + + // 动态获取全局屏幕使用时长(每秒刷新一次) + val screenTimeMillis = remember { mutableStateOf(getScreenTime().getUsageMillis()) } + LaunchedEffect(Unit) { + while (true) { + screenTimeMillis.value = getScreenTime().getUsageMillis() + delay(1000) + } + } Column( modifier = Modifier @@ -32,7 +54,7 @@ fun MobileAnalyticsScreen(colors: AppThemes.Colors) { verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text( - "Screen Time Analytics", + stringResource(MR.strings.screen_time_analytics), style = MaterialTheme.typography.headlineSmall.copy( color = colors.onBackground, fontWeight = FontWeight.Bold, @@ -77,45 +99,59 @@ fun MobileAnalyticsScreen(colors: AppThemes.Colors) { // Key Metrics - Use a Grid for better mobile layout if more than 2, or stacked Rows Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { MetricCard( - title = "Total Time", - value = "25h 10m", + title = stringResource(MR.strings.total_screen_on_time), + value = formatMillisToHourMin(screenTimeMillis.value), icon = Icons.Default.Smartphone, colors = colors, modifier = Modifier.weight(1f) ) MetricCard( - title = "Avg Daily", - value = "3h 35m", + title = stringResource(MR.strings.avg_daily), + value = formatMillisToHourMin(screenTimeMillis.value), icon = Icons.Default.AvTimer, colors = colors, modifier = Modifier.weight(1f) ) } Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + val now = remember { System.currentTimeMillis() } + val todayStart = remember { + Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + } + val appStats = remember { mutableStateListOf() } + LaunchedEffect(selectedTimeRange) { + appStats.clear() + appStats.addAll(getAppUsageStats(todayStart, now)) + } + val mostUsed = appStats.maxByOrNull { it.usageMillis } MetricCard( - title = "Most Used", - value = "App A", + title = stringResource(MR.strings.most_used), + value = mostUsed?.appName ?: "-", icon = Icons.Default.StarOutline, colors = colors, modifier = Modifier.weight(1f) ) MetricCard( - title = "Pickups", - value = "75", + title = stringResource(MR.strings.pickups), + value = "-", icon = Icons.Default.TouchApp, colors = colors, modifier = Modifier.weight(1f) ) } - StyledCard(colors = colors) { Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text( - "Usage Patterns", + stringResource(MR.strings.usage_patterns), style = MaterialTheme.typography.titleMedium.copy( color = colors.onSurface, fontWeight = FontWeight.SemiBold, @@ -123,25 +159,71 @@ fun MobileAnalyticsScreen(colors: AppThemes.Colors) { ) ) ChartPlaceholder( - text = "Daily Screen Time (Bar Chart)", + text = stringResource(MR.strings.daily_screen_time_chart), colors = colors, modifier = Modifier.fillMaxWidth().height(180.dp) ) // Slightly smaller charts for mobile ChartPlaceholder( - text = "App Usage (Pie Chart)", + text = stringResource(MR.strings.app_usage_pie_chart), colors = colors, modifier = Modifier.fillMaxWidth().height(180.dp) ) } } + // --- 应用使用时长排行 --- + Spacer(modifier = Modifier.height(16.dp)) + StyledCard(colors = colors) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = stringResource(MR.strings.app_usage_breakdown), + style = MaterialTheme.typography.titleMedium.copy( + color = colors.onSurface, + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp + ) + ) + // 获取今天0点到现在的各应用时长 + val now = remember { System.currentTimeMillis() } + val todayStart = remember { + Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + } + val appStats = remember { mutableStateListOf() } + LaunchedEffect(selectedTimeRange) { + // 这里只做今日,后续可按时间段切换 + appStats.clear() + appStats.addAll(getAppUsageStats(todayStart, now)) + } + if (appStats.isEmpty()) { + Text(stringResource(MR.strings.no_usage_data), color = colors.secondaryText) + } else { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + appStats.take(10).forEachIndexed { idx, info -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text("${idx + 1}. ${info.appName}", color = colors.onSurface) + Text(formatMillisToHourMin(info.usageMillis), color = colors.accent) + } + } + } + } + } + } + StyledCard(colors = colors) { Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { Text( - "Analysis Tools", + stringResource(MR.strings.analysis_tools), style = MaterialTheme.typography.titleMedium.copy( color = colors.onSurface, fontWeight = FontWeight.SemiBold, @@ -151,21 +233,21 @@ fun MobileAnalyticsScreen(colors: AppThemes.Colors) { StyledButton( modifier = Modifier.fillMaxWidth(), icon = Icons.Default.PieChartOutline, - text = "App Usage Breakdown", + text = stringResource(MR.strings.app_usage_breakdown_btn), onClick = { /* TODO */ }, colors = colors ) StyledButton( modifier = Modifier.fillMaxWidth(), icon = Icons.Default.AccessTime, - text = "Time of Day Analysis", + text = stringResource(MR.strings.time_of_day_analysis), onClick = { /* TODO */ }, colors = colors ) StyledButton( modifier = Modifier.fillMaxWidth(), icon = Icons.Default.TrackChanges, - text = "Set Usage Goals", + text = stringResource(MR.strings.set_usage_goals), onClick = { /* TODO */ }, colors = colors ) diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileHomeScreenC.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileHomeScreenC.kt similarity index 57% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileHomeScreenC.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileHomeScreenC.kt index 5892949..d87aa22 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileHomeScreenC.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileHomeScreenC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.screens +package com.grtsinry43.chronosight.screens import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -6,21 +6,39 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material3.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* 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.unit.sp -import com.grtsinry43.activityanalyzer.components.ActivityItem -import com.grtsinry43.activityanalyzer.components.SimpleListItem -import com.grtsinry43.activityanalyzer.components.StyledButton -import com.grtsinry43.activityanalyzer.components.StyledCard -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.components.ActivityItem +import com.grtsinry43.chronosight.components.SimpleListItem +import com.grtsinry43.chronosight.components.StyledButton +import com.grtsinry43.chronosight.components.StyledCard +import com.grtsinry43.chronosight.formatMillisToHourMin +import com.grtsinry43.chronosight.theme.AppThemes +import com.grtsinry43.chronosight.getScreenTime +import com.grtsinry43.chronosight.getAppUsageStats +import com.grtsinry43.chronosight.AppUsageInfo +import kotlinx.coroutines.delay import org.jetbrains.compose.ui.tooling.preview.Preview +import java.util.Calendar +import java.util.Locale +import dev.icerock.moko.resources.compose.stringResource +import com.grtsinry43.chronosight.MR @Composable -fun MobileHomeScreen(colors: AppThemes.Colors) { +fun MobileHomeScreen(colors: AppThemes.Colors, onRequestUsageAccess: (() -> Unit)? = null) { + // 动态获取全局屏幕使用时长(每秒刷新一次) + val screenTimeMillis = remember { mutableStateOf(getScreenTime().getUsageMillis()) } + LaunchedEffect(Unit) { + while (true) { + screenTimeMillis.value = getScreenTime().getUsageMillis() + delay(1000) + } + } + Column( modifier = Modifier .fillMaxSize() @@ -29,7 +47,7 @@ fun MobileHomeScreen(colors: AppThemes.Colors) { verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text( - text = "Welcome Back, grtsinry43!", + text = stringResource(MR.strings.welcome, "grtsinry43"), style = MaterialTheme.typography.headlineSmall.copy( color = colors.onBackground, fontWeight = FontWeight.Bold, @@ -43,7 +61,7 @@ fun MobileHomeScreen(colors: AppThemes.Colors) { verticalArrangement = Arrangement.spacedBy(8.dp) ) { Text( - text = "Today's Screen Time", + text = stringResource(MR.strings.today_screen_time), style = MaterialTheme.typography.titleLarge.copy( // Larger title for emphasis color = colors.onSurface, fontWeight = FontWeight.SemiBold, @@ -51,7 +69,7 @@ fun MobileHomeScreen(colors: AppThemes.Colors) { ) ) Text( - text = "3h 45m", // Placeholder + text = formatMillisToHourMin(screenTimeMillis.value), style = MaterialTheme.typography.displayMedium.copy( // Prominent display color = colors.accent, fontWeight = FontWeight.Bold, @@ -60,7 +78,7 @@ fun MobileHomeScreen(colors: AppThemes.Colors) { modifier = Modifier.align(Alignment.CenterHorizontally) ) Text( - text = "You're on track with your daily goal!", // Placeholder + text = stringResource(MR.strings.on_track), // Placeholder style = MaterialTheme.typography.bodyMedium.copy( color = colors.secondaryText, fontSize = 14.sp @@ -76,24 +94,49 @@ fun MobileHomeScreen(colors: AppThemes.Colors) { verticalArrangement = Arrangement.spacedBy(12.dp) ) { Text( - text = "Quick Glance: Top Apps", + text = stringResource(MR.strings.quick_glance), style = MaterialTheme.typography.titleMedium.copy( color = colors.onSurface, fontWeight = FontWeight.SemiBold, fontSize = 16.sp ) ) - SimpleListItem( - icon = Icons.Filled.SmartDisplay, - text = "App A: 1h 15m", - colors = colors - ) - SimpleListItem( - icon = Icons.Filled.PhotoCamera, - text = "App B: 45m", - colors = colors - ) - SimpleListItem(icon = Icons.Filled.Chat, text = "App C: 30m", colors = colors) + // 动态获取今日前3应用 + val now = remember { System.currentTimeMillis() } + val todayStart = remember { + Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + } + val appStats = remember { mutableStateListOf() } + LaunchedEffect(Unit) { + appStats.clear() + appStats.addAll(getAppUsageStats(todayStart, now)) + } + if (appStats.isEmpty()) { + Text(stringResource(MR.strings.no_usage_data), color = colors.secondaryText) + Spacer(Modifier.height(8.dp)) + Button( + onClick = { + // 这里不能直接调用 android 相关代码,改为调用回调 + onRequestUsageAccess?.invoke() + }, + colors = ButtonDefaults.buttonColors(containerColor = colors.accent) + ) { + Text(stringResource(MR.strings.grant_usage_access), color = colors.onAccent) + } + } else { + appStats.take(3).forEach { info -> + SimpleListItem( + icon = Icons.Filled.SmartDisplay, // 可根据包名/进程名自定义图标 + text = "${info.appName}: ${formatMillisToHourMin(info.usageMillis)}", + colors = colors + ) + } + } } } @@ -103,7 +146,7 @@ fun MobileHomeScreen(colors: AppThemes.Colors) { verticalArrangement = Arrangement.spacedBy(12.dp) // Spacing for buttons ) { Text( - text = "Quick Actions", + text = stringResource(MR.strings.quick_actions), style = MaterialTheme.typography.titleMedium.copy( color = colors.onSurface, fontWeight = FontWeight.SemiBold, @@ -113,14 +156,14 @@ fun MobileHomeScreen(colors: AppThemes.Colors) { StyledButton( // Full width buttons for mobile quick actions modifier = Modifier.fillMaxWidth(), icon = Icons.Default.HourglassTop, - text = "Start Focus Session", + text = stringResource(MR.strings.start_focus), onClick = { /* TODO */ }, colors = colors ) StyledButton( modifier = Modifier.fillMaxWidth(), icon = Icons.Default.CalendarViewDay, // Changed icon - text = "View Today's Details", + text = stringResource(MR.strings.view_today), onClick = { /* TODO */ }, colors = colors ) @@ -133,7 +176,7 @@ fun MobileHomeScreen(colors: AppThemes.Colors) { verticalArrangement = Arrangement.spacedBy(12.dp) ) { Text( - text = "Recent Insights", + text = stringResource(MR.strings.recent_insights), style = MaterialTheme.typography.titleMedium.copy( color = colors.onSurface, fontWeight = FontWeight.SemiBold, @@ -141,13 +184,13 @@ fun MobileHomeScreen(colors: AppThemes.Colors) { ) ) ActivityItem( - title = "Exceeded daily goal for App X.", + title = stringResource(MR.strings.exceeded_goal), time = "Today, 2:30 PM", icon = Icons.Default.WarningAmber, colors = colors ) ActivityItem( - title = "Screen time 20% higher yesterday.", + title = stringResource(MR.strings.higher_yesterday), time = "Insight from yesterday", icon = Icons.Default.TrendingUp, colors = colors diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileProfileScreenC.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileProfileScreenC.kt similarity index 95% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileProfileScreenC.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileProfileScreenC.kt index a502a84..d3c8ed7 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileProfileScreenC.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileProfileScreenC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.screens +package com.grtsinry43.chronosight.screens import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background @@ -16,10 +16,10 @@ 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.grtsinry43.activityanalyzer.components.SimpleListItem -import com.grtsinry43.activityanalyzer.components.StyledButton -import com.grtsinry43.activityanalyzer.components.StyledCard -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.components.SimpleListItem +import com.grtsinry43.chronosight.components.StyledButton +import com.grtsinry43.chronosight.components.StyledCard +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileReportsScreenC.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileReportsScreenC.kt similarity index 95% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileReportsScreenC.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileReportsScreenC.kt index d911a50..edf46fc 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileReportsScreenC.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileReportsScreenC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.screens +package com.grtsinry43.chronosight.screens import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -12,8 +12,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.grtsinry43.activityanalyzer.components.ReportItem -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.components.ReportItem +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileSettingsScreenC.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileSettingsScreenC.kt similarity index 76% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileSettingsScreenC.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileSettingsScreenC.kt index 70911d4..70de498 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/screens/MobileSettingsScreenC.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/screens/MobileSettingsScreenC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.screens +package com.grtsinry43.chronosight.screens import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -12,18 +12,23 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.grtsinry43.activityanalyzer.components.SettingItem -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.components.SettingItem +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable fun MobileSettingsScreen( colors: AppThemes.Colors, isDarkTheme: Boolean, - onThemeChange: (Boolean) -> Unit + onThemeChange: (Boolean) -> Unit, + currentLocale: String, + onLocaleChange: (String) -> Unit ) { var autoBackupEnabled by remember { mutableStateOf(true) } var notificationsEnabled by remember { mutableStateOf(true) } + val languageOptions = listOf("简体中文" to "zh", "English" to "en") + var languageMenuExpanded by remember { mutableStateOf(false) } + val currentLanguageName = languageOptions.find { it.second == currentLocale }?.first ?: "English" Column( modifier = Modifier @@ -34,6 +39,31 @@ fun MobileSettingsScreen( ) { // General Group SettingsGroupHeader(title = "General", colors = colors) + SettingItem( + title = "Language", + subtitle = currentLanguageName, + icon = Icons.Default.Language, + colors = colors, + onClick = { languageMenuExpanded = true } + ) + DropdownMenu( + expanded = languageMenuExpanded, + onDismissRequest = { languageMenuExpanded = false } + ) { + languageOptions.forEach { (name, code) -> + DropdownMenuItem( + text = { Text(name) }, + onClick = { + onLocaleChange(code) + languageMenuExpanded = false + } + ) + } + } + Divider( + color = colors.border.copy(alpha = 0.2f), + modifier = Modifier.padding(horizontal = 16.dp) + ) SettingItem( title = "Dark Theme", subtitle = if (isDarkTheme) "Enabled" else "Disabled", @@ -169,7 +199,13 @@ fun SettingsGroupHeader(title: String, colors: AppThemes.Colors) { @Composable fun MobileSettingsScreenPreview() { MaterialTheme { - MobileSettingsScreen(colors = AppThemes.LightThemeColors, isDarkTheme = false, onThemeChange = {}) + MobileSettingsScreen( + colors = AppThemes.LightThemeColors, + isDarkTheme = false, + onThemeChange = {}, + currentLocale = "en", + onLocaleChange = {} + ) } } @@ -177,6 +213,12 @@ fun MobileSettingsScreenPreview() { @Composable fun MobileSettingsScreenDarkPreview() { MaterialTheme { - MobileSettingsScreen(colors = AppThemes.DarkThemeColors, isDarkTheme = true, onThemeChange = {}) + MobileSettingsScreen( + colors = AppThemes.DarkThemeColors, + isDarkTheme = true, + onThemeChange = {}, + currentLocale = "en", + onLocaleChange = {} + ) } } diff --git a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/theme/AppThemes.kt b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/theme/AppThemes.kt similarity index 97% rename from composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/theme/AppThemes.kt rename to composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/theme/AppThemes.kt index 5fc7136..0a67262 100644 --- a/composeApp/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/theme/AppThemes.kt +++ b/composeApp/src/commonMain/kotlin/com/grtsinry43/chronosight/theme/AppThemes.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.theme +package com.grtsinry43.chronosight.theme import androidx.compose.ui.graphics.Color diff --git a/composeApp/src/commonMain/resources/MR/base/strings.xml b/composeApp/src/commonMain/resources/MR/base/strings.xml deleted file mode 100644 index ba4f33b..0000000 --- a/composeApp/src/commonMain/resources/MR/base/strings.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - Activity Analyzer - Home - Analytics - Reports - Settings - About - Quick Actions - New Analysis - Import Data - Recent Activities - Analysis Report - Data Import - Analysis Tools - Time Analysis - Distribution Analysis - My Reports - Monthly Activity Report - Weekly Activity Report - Download - Data Storage Location - Default Location - Change - Auto Backup - Daily - Notifications - Enabled - Version - A powerful activity analysis tool to help you better understand and optimize your activity data. - Check Updates - \ No newline at end of file diff --git a/composeApp/src/commonMain/resources/MR/zh/strings.xml b/composeApp/src/commonMain/resources/MR/zh/strings.xml deleted file mode 100644 index cd7cb67..0000000 --- a/composeApp/src/commonMain/resources/MR/zh/strings.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - 活动分析器 - 首页 - 数据分析 - 报告 - 设置 - 关于 - 快速操作 - 新建分析 - 导入数据 - 最近活动 - 数据分析报告 - 活动数据导入 - 分析工具 - 时间分析 - 分布分析 - 我的报告 - 月度活动报告 - 周活动分析 - 下载 - 数据存储位置 - 默认位置 - 更改 - 自动备份 - 每天 - 通知设置 - 开启 - 版本 - 一个强大的活动分析工具,帮助您更好地理解和优化您的活动数据。 - 检查更新 - \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/DesktopApp.kt b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/DesktopApp.kt similarity index 93% rename from composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/DesktopApp.kt rename to composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/DesktopApp.kt index 1b0ffa2..744ec2e 100644 --- a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/DesktopApp.kt +++ b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/DesktopApp.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer +package com.grtsinry43.chronosight import androidx.compose.foundation.* import androidx.compose.foundation.layout.* @@ -17,14 +17,14 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import org.jetbrains.compose.ui.tooling.preview.Preview import androidx.compose.foundation.text.selection.SelectionContainer -import com.grtsinry43.activityanalyzer.components.* -import com.grtsinry43.activityanalyzer.screens.AboutScreen -import com.grtsinry43.activityanalyzer.screens.AnalyticsScreen -import com.grtsinry43.activityanalyzer.screens.HomeScreen -import com.grtsinry43.activityanalyzer.screens.ProfileScreen -import com.grtsinry43.activityanalyzer.screens.ReportsScreen -import com.grtsinry43.activityanalyzer.screens.SettingsScreen -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.components.* +import com.grtsinry43.chronosight.screens.AboutScreen +import com.grtsinry43.chronosight.screens.AnalyticsScreen +import com.grtsinry43.chronosight.screens.HomeScreen +import com.grtsinry43.chronosight.screens.ProfileScreen +import com.grtsinry43.chronosight.screens.ReportsScreen +import com.grtsinry43.chronosight.screens.SettingsScreen +import com.grtsinry43.chronosight.theme.AppThemes @Composable @Preview @@ -64,7 +64,7 @@ fun DesktopApp() { ) { if (!isSidebarCollapsed) { Text( - "Activity Analyzer", // 应用标题 + "Chronosight", // 应用标题 fontSize = 18.sp, // 字体大小 fontWeight = FontWeight.SemiBold, // 字体粗细 color = currentColors.onSurface, // 文本颜色 diff --git a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/components/NavItemC.kt b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/components/NavItemC.kt similarity index 97% rename from composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/components/NavItemC.kt rename to composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/components/NavItemC.kt index 6d322ef..acde9ae 100644 --- a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/components/NavItemC.kt +++ b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/components/NavItemC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.components +package com.grtsinry43.chronosight.components import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -20,7 +20,7 @@ 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 com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/main.kt b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/main.kt similarity index 95% rename from composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/main.kt rename to composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/main.kt index 5e27ecf..2980d37 100644 --- a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/main.kt +++ b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/main.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer +package com.grtsinry43.chronosight import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons @@ -50,7 +50,7 @@ fun main() = application { Tray( state = trayState, icon = trayIconPainter ?: BitmapPainter(createDefaultTrayIcon()), // 如果自定义图标加载失败,使用一个备用图标 - tooltip = "Activity Analyzer", // 鼠标悬停在托盘图标上时显示的提示文字 + tooltip = "Chronosight", // 鼠标悬停在托盘图标上时显示的提示文字 menu = { // 托盘菜单项 Item( @@ -82,7 +82,7 @@ fun main() = application { // exitApplication() }, state = windowState, // 应用窗口状态 - title = "Activity Analyzer", + title = "Chronosight", resizable = true, // 允许用户调整窗口大小 (默认为 true) // icon = painterResource("app_icon.png") // 可选:设置窗口左上角的图标和任务栏图标 ) { @@ -111,7 +111,7 @@ private fun createDefaultTrayIcon(): ImageBitmap { // Change return type to Imag g.color = java.awt.Color.BLUE g.fillRect(0, 0, width, height) g.color = java.awt.Color.WHITE - g.drawString("AA", 20, 40) // "Activity Analyzer" 缩写 + g.drawString("AA", 20, 40) // "Chronosight" 缩写 } finally { g.dispose() } diff --git a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/AboutScreenC.kt b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/AboutScreenC.kt similarity index 95% rename from composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/AboutScreenC.kt rename to composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/AboutScreenC.kt index 6da697c..bb26207 100644 --- a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/AboutScreenC.kt +++ b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/AboutScreenC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.screens +package com.grtsinry43.chronosight.screens import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -12,10 +12,10 @@ 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 com.grtsinry43.activityanalyzer.components.InfoItem -import com.grtsinry43.activityanalyzer.components.SettingItem -import com.grtsinry43.activityanalyzer.components.StyledCard -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.components.InfoItem +import com.grtsinry43.chronosight.components.SettingItem +import com.grtsinry43.chronosight.components.StyledCard +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable @@ -35,7 +35,7 @@ fun AboutScreen(colors: AppThemes.Colors) { modifier = Modifier.size(80.dp) // 图标大小 ) Text( - "Activity Analyzer", // 应用名称 + "Chronosight", // 应用名称 style = MaterialTheme.typography.headlineMedium.copy( color = colors.onBackground, fontWeight = FontWeight.Bold diff --git a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/AnalyticsScreenC.kt b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/AnalyticsScreenC.kt similarity index 66% rename from composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/AnalyticsScreenC.kt rename to composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/AnalyticsScreenC.kt index 4c111e9..5ca15f3 100644 --- a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/AnalyticsScreenC.kt +++ b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/AnalyticsScreenC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.screens +package com.grtsinry43.chronosight.screens import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -12,11 +12,17 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import com.grtsinry43.activityanalyzer.components.ChartPlaceholder -import com.grtsinry43.activityanalyzer.components.MetricCard -import com.grtsinry43.activityanalyzer.components.StyledButton -import com.grtsinry43.activityanalyzer.components.StyledCard -import com.grtsinry43.activityanalyzer.theme.AppThemes +import androidx.compose.ui.unit.sp +import com.grtsinry43.chronosight.components.ChartPlaceholder +import com.grtsinry43.chronosight.components.MetricCard +import com.grtsinry43.chronosight.components.StyledButton +import com.grtsinry43.chronosight.components.StyledCard +import com.grtsinry43.chronosight.theme.AppThemes +import com.grtsinry43.chronosight.getAppUsageStats +import com.grtsinry43.chronosight.AppUsageInfo +import com.grtsinry43.chronosight.formatMillisToHourMin +import com.grtsinry43.chronosight.getScreenTime +import java.util.Calendar import org.jetbrains.compose.ui.tooling.preview.Preview @Composable @@ -84,36 +90,60 @@ fun AnalyticsScreen(colors: AppThemes.Colors) { // Key Metrics Cards // 关键指标卡片 Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + // 动态获取全局屏幕使用时长(每秒刷新一次) + val screenTimeMillis = remember { mutableStateOf(getScreenTime().getUsageMillis()) } + LaunchedEffect(Unit) { + while (true) { + screenTimeMillis.value = getScreenTime().getUsageMillis() + kotlinx.coroutines.delay(1000) + } + } MetricCard( title = "Total Screen Time", - value = "25h 10m", // 示例数据 + value = formatMillisToHourMin(screenTimeMillis.value), icon = Icons.Default.Smartphone, colors = colors, modifier = Modifier.weight(1f) - ) // 总屏幕时间 + ) MetricCard( title = "Avg Daily Time", - value = "3h 35m", // 示例数据 + value = formatMillisToHourMin(screenTimeMillis.value), icon = Icons.Default.AvTimer, colors = colors, modifier = Modifier.weight(1f) - ) // 平均每日时间 + ) } Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) { + // 今日最常用应用 + val now = remember { System.currentTimeMillis() } + val todayStart = remember { + Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + } + val appStats = remember { mutableStateListOf() } + LaunchedEffect(selectedTimeRange) { + appStats.clear() + appStats.addAll(getAppUsageStats(todayStart, now)) + } + val mostUsed = appStats.maxByOrNull { it.usageMillis } MetricCard( title = "Most Used App", - value = "App A (8h)", // 示例数据 + value = mostUsed?.appName ?: "-", icon = Icons.Default.StarOutline, colors = colors, modifier = Modifier.weight(1f) - ) // 最常用应用 + ) MetricCard( title = "Pickups", - value = "75 today", // 示例数据 + value = "-", icon = Icons.Default.TouchApp, colors = colors, modifier = Modifier.weight(1f) - ) // 拿起次数 + ) } // Charts Section @@ -148,6 +178,52 @@ fun AnalyticsScreen(colors: AppThemes.Colors) { } } + // --- 应用使用时长排行 --- + Spacer(modifier = Modifier.height(16.dp)) + StyledCard(colors = colors) { + Column(modifier = Modifier.padding(16.dp)) { + Text( + text = "App Usage Breakdown", + style = MaterialTheme.typography.titleMedium.copy( + color = colors.onSurface, + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp + ) + ) + // 获取今天0点到现在的各应用时长 + val now = remember { System.currentTimeMillis() } + val todayStart = remember { + Calendar.getInstance().apply { + set(Calendar.HOUR_OF_DAY, 0) + set(Calendar.MINUTE, 0) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + }.timeInMillis + } + val appStats = remember { mutableStateListOf() } + LaunchedEffect(selectedTimeRange) { + // 这里只做今日,后续可按时间段切换 + appStats.clear() + appStats.addAll(getAppUsageStats(todayStart, now)) + } + if (appStats.isEmpty()) { + Text("No usage data.", color = colors.secondaryText) + } else { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + appStats.take(10).forEachIndexed { idx, info -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text("${idx + 1}. ${info.appName}", color = colors.onSurface) + Text(formatMillisToHourMin(info.usageMillis), color = colors.accent) + } + } + } + } + } + } + // Analysis Tools // 分析工具 StyledCard(colors = colors) { diff --git a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/HomeScreenC.kt b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/HomeScreenC.kt similarity index 87% rename from composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/HomeScreenC.kt rename to composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/HomeScreenC.kt index b6ddfd9..82a590f 100644 --- a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/HomeScreenC.kt +++ b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/HomeScreenC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.screens +package com.grtsinry43.chronosight.screens import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -7,16 +7,18 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material3.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import com.grtsinry43.activityanalyzer.components.ActivityItem -import com.grtsinry43.activityanalyzer.components.SimpleListItem -import com.grtsinry43.activityanalyzer.components.StyledButton -import com.grtsinry43.activityanalyzer.components.StyledCard -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.components.ActivityItem +import com.grtsinry43.chronosight.components.SimpleListItem +import com.grtsinry43.chronosight.components.StyledButton +import com.grtsinry43.chronosight.components.StyledCard +import com.grtsinry43.chronosight.getScreenTime +import com.grtsinry43.chronosight.formatMillisToHourMin +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable @@ -41,6 +43,14 @@ fun HomeScreen(colors: AppThemes.Colors) { // Overview of today's screen time // 今日屏幕时间概览 StyledCard(colors = colors) { + // 动态获取屏幕时长,每秒刷新 + val screenTimeMillis = remember { mutableStateOf(getScreenTime().getUsageMillis()) } + LaunchedEffect(Unit) { + while (true) { + screenTimeMillis.value = getScreenTime().getUsageMillis() + kotlinx.coroutines.delay(1000) + } + } Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp) @@ -53,7 +63,7 @@ fun HomeScreen(colors: AppThemes.Colors) { ) ) Text( - text = "3h 45m", // 示例数据 + text = formatMillisToHourMin(screenTimeMillis.value), style = MaterialTheme.typography.displaySmall.copy( color = colors.accent, fontWeight = FontWeight.Bold diff --git a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/ProfileScreenC.kt b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/ProfileScreenC.kt similarity index 96% rename from composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/ProfileScreenC.kt rename to composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/ProfileScreenC.kt index 047b090..27d4c05 100644 --- a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/ProfileScreenC.kt +++ b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/ProfileScreenC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.screens +package com.grtsinry43.chronosight.screens import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -12,10 +12,10 @@ 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 com.grtsinry43.activityanalyzer.components.SimpleListItem -import com.grtsinry43.activityanalyzer.components.StyledButton -import com.grtsinry43.activityanalyzer.components.StyledCard -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.components.SimpleListItem +import com.grtsinry43.chronosight.components.StyledButton +import com.grtsinry43.chronosight.components.StyledCard +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/ReportsScreenC.kt b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/ReportsScreenC.kt similarity index 94% rename from composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/ReportsScreenC.kt rename to composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/ReportsScreenC.kt index 34800ca..37d16ab 100644 --- a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/ReportsScreenC.kt +++ b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/ReportsScreenC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.screens +package com.grtsinry43.chronosight.screens import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -11,10 +11,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import com.grtsinry43.activityanalyzer.components.ReportItem -import com.grtsinry43.activityanalyzer.components.StyledButton -import com.grtsinry43.activityanalyzer.components.StyledCard -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.components.ReportItem +import com.grtsinry43.chronosight.components.StyledButton +import com.grtsinry43.chronosight.components.StyledCard +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable diff --git a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/SettingsScreenC.kt b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/SettingsScreenC.kt similarity index 96% rename from composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/SettingsScreenC.kt rename to composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/SettingsScreenC.kt index d0959a2..9414c22 100644 --- a/composeApp/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/screens/SettingsScreenC.kt +++ b/composeApp/src/desktopMain/kotlin/com/grtsinry43/chronosight/screens/SettingsScreenC.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer.screens +package com.grtsinry43.chronosight.screens import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -10,9 +10,9 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import com.grtsinry43.activityanalyzer.components.SettingItem -import com.grtsinry43.activityanalyzer.components.StyledCard -import com.grtsinry43.activityanalyzer.theme.AppThemes +import com.grtsinry43.chronosight.components.SettingItem +import com.grtsinry43.chronosight.components.StyledCard +import com.grtsinry43.chronosight.theme.AppThemes import org.jetbrains.compose.ui.tooling.preview.Preview @Composable @@ -68,7 +68,7 @@ fun SettingsScreen( Divider(color = colors.border.copy(alpha = 0.3f)) // 分隔线 SettingItem( // 数据存储位置 title = "Data Storage Location", // 数据存储位置 - subtitle = "/Users/grtsinry43/Documents/ActivityAnalyzer", // 示例路径 + subtitle = "/Users/grtsinry43/Documents/Chronosight", // 示例路径 icon = Icons.Default.FolderOpen, // 打开文件夹图标 colors = colors, onClick = { /* TODO: Open file dialog or path editor */ } // 打开文件对话框或路径编辑器 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 859945e..569507e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.5.2" +agp = "8.10.0" android-compileSdk = "35" android-minSdk = "24" android-targetSdk = "35" @@ -15,8 +15,9 @@ compose-multiplatform = "1.7.3" junit = "4.13.2" kotlin = "2.1.20" kotlinx-coroutines = "1.10.1" -kotlinxSerializationJson = "1.8.0" -ktorClientCore = "3.1.1" +kotlinxSerializationJson = "1.8.1" +ktorClientCore = "3.1.3" +mokoResources = "0.24.5" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -38,10 +39,14 @@ ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negoti ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorClientCore" } ktor-client-ios = { module = "io.ktor:ktor-client-ios", version.ref = "ktorClientCore" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktorClientCore" } +moko-resources = { group = "dev.icerock.moko", name = "resources", version.ref = "mokoResources" } +moko-resources-compose = { group = "dev.icerock.moko", name = "resources-compose", version.ref = "mokoResources" } +resources-test = { module = "dev.icerock.moko:resources-test", version.ref = "mokoResources" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } androidLibrary = { id = "com.android.library", version.ref = "agp" } composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } \ No newline at end of file +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +mokoMultiplatformResources = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "mokoResources" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 09523c0..e2847c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/iosApp/Configuration/Config.xcconfig b/iosApp/Configuration/Config.xcconfig index c228792..f42b47a 100644 --- a/iosApp/Configuration/Config.xcconfig +++ b/iosApp/Configuration/Config.xcconfig @@ -1,3 +1,3 @@ TEAM_ID= -BUNDLE_ID=com.grtsinry43.activityanalyzer.ActivityAnalyzer -APP_NAME=Activity Analyzer \ No newline at end of file +BUNDLE_ID=com.grtsinry43.chronosight.Chronosight +APP_NAME=Chronosight \ No newline at end of file diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index cb516e9..fc8e420 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -18,7 +18,7 @@ 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; - 7555FF7B242A565900829871 /* Activity Analyzer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Activity Analyzer.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7555FF7B242A565900829871 /* Chronosight.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Chronosight.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; @@ -64,7 +64,7 @@ 7555FF7C242A565900829871 /* Products */ = { isa = PBXGroup; children = ( - 7555FF7B242A565900829871 /* Activity Analyzer.app */, + 7555FF7B242A565900829871 /* Chronosight.app */, ); name = Products; sourceTree = ""; @@ -110,7 +110,7 @@ packageProductDependencies = ( ); productName = iosApp; - productReference = 7555FF7B242A565900829871 /* Activity Analyzer.app */; + productReference = 7555FF7B242A565900829871 /* Chronosight.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ diff --git a/settings.gradle.kts b/settings.gradle.kts index 594e53a..971653a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,4 @@ -rootProject.name = "ActivityAnalyzer" +rootProject.name = "Chronosight" enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") pluginManagement { diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 25f6dc7..46e2e91 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -1,11 +1,13 @@ +import dev.icerock.gradle.MRVisibility import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import dev.icerock.gradle.MultiplatformResourcesPluginExtension // Required for resourcesPackage configuration plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidLibrary) - kotlin("plugin.serialization") version libs.versions.kotlin.get() + alias(libs.plugins.mokoMultiplatformResources) // Use alias from version catalog } kotlin { @@ -15,7 +17,7 @@ kotlin { jvmTarget.set(JvmTarget.JVM_11) } } - + listOf( iosX64(), iosArm64(), @@ -26,16 +28,19 @@ kotlin { isStatic = true } } - + jvm() - + sourceSets { - commonMain.dependencies { - // put your Multiplatform dependencies here - implementation(libs.ktor.client.core) - implementation(libs.ktor.client.content.negotiation) - implementation(libs.ktor.serialization.kotlinx.json) - implementation(libs.kotlinx.serialization.json) + val commonMain by getting { + resources.srcDirs("src/commonMain/resources") + dependencies { + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.kotlinx.serialization.json) + implementation(libs.moko.resources) + } } androidMain.dependencies { implementation(libs.ktor.client.android) @@ -47,7 +52,7 @@ kotlin { } android { - namespace = "com.grtsinry43.activityanalyzer.shared" + namespace = "com.grtsinry43.chronosight.shared" compileSdk = libs.versions.android.compileSdk.get().toInt() compileOptions { sourceCompatibility = JavaVersion.VERSION_11 @@ -57,3 +62,20 @@ android { minSdk = libs.versions.android.minSdk.get().toInt() } } + + +dependencies { + commonMainApi(libs.moko.resources) + commonMainApi(libs.moko.resources.compose) // for compose multiplatform + + commonTestImplementation(libs.resources.test) +} + +multiplatformResources { + resourcesPackage.set("com.grtsinry43.chronosight") // required + resourcesClassName.set("MR") // optional, default MR + resourcesVisibility.set(MRVisibility.Public) // optional, default Public + iosBaseLocalizationRegion.set("en") // optional, default "en" + iosMinimalDeploymentTarget.set("11.0") // optional, default "9.0" +} + diff --git a/shared/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/Platform.android.kt b/shared/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/Platform.android.kt deleted file mode 100644 index 27cdeef..0000000 --- a/shared/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/Platform.android.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.grtsinry43.activityanalyzer - -import android.os.Build - -class AndroidPlatform : Platform { - override val name: String = "Android ${Build.VERSION.SDK_INT}" -} - -actual fun getPlatform(): Platform = AndroidPlatform() \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/ScreenTime.kt b/shared/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/ScreenTime.kt deleted file mode 100644 index 8321ed4..0000000 --- a/shared/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/ScreenTime.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.grtsinry43.activityanalyzer - -actual fun getScreenTime(): ScreenTime { - return object : ScreenTime { - override val screenTime: Long - get() = 0L // TODO: Implement the actual logic to get screen time on Android - } -} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt b/shared/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt deleted file mode 100644 index 2ea8f58..0000000 --- a/shared/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.grtsinry43.activityanalyzer.i18n - -import android.content.Context -import android.content.res.Resources - -actual class StringResource(private val context: Context) { - actual fun getString(key: String): String { - val resourceId = context.resources.getIdentifier(key, "string", context.packageName) - return if (resourceId != 0) { - context.getString(resourceId) - } else { - key - } - } - - actual fun getString(key: String, vararg args: Any): String { - val resourceId = context.resources.getIdentifier(key, "string", context.packageName) - return if (resourceId != 0) { - context.getString(resourceId, *args) - } else { - key - } - } -} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt b/shared/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt deleted file mode 100644 index f54a5d8..0000000 --- a/shared/src/androidMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.grtsinry43.activityanalyzer.i18n - -import android.content.Context - -actual object StringResourceFactory { - private var context: Context? = null - - fun initialize(context: Context) { - this.context = context.applicationContext - } - - actual fun create(): StringResource { - return StringResource(context ?: throw IllegalStateException("Context not initialized")) - } -} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/grtsinry43/chronosight/AppUsageAnalyzer.kt b/shared/src/androidMain/kotlin/com/grtsinry43/chronosight/AppUsageAnalyzer.kt new file mode 100644 index 0000000..b8a26f9 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/grtsinry43/chronosight/AppUsageAnalyzer.kt @@ -0,0 +1,53 @@ +package com.grtsinry43.chronosight + +import android.app.usage.UsageStatsManager +import android.content.Context +import android.content.pm.PackageManager +import android.content.SharedPreferences +import android.os.Build + +actual fun getAppUsageStats(startMillis: Long, endMillis: Long): List { + val context = appContext ?: return emptyList() + val usageStatsManager = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager + val pm = context.packageManager + val prefs = context.getSharedPreferences("app_usage_stats", Context.MODE_PRIVATE) + val stats = usageStatsManager.queryUsageStats( + UsageStatsManager.INTERVAL_DAILY, startMillis, endMillis + ) ?: return loadPersistedStats(prefs) + val result = stats + .filter { it.totalTimeInForeground > 0 } + .map { + val appName = try { + pm.getApplicationLabel(pm.getApplicationInfo(it.packageName, 0)).toString() + } catch (e: Exception) { + it.packageName + } + AppUsageInfo( + packageName = it.packageName, + appName = appName, + usageMillis = if (Build.VERSION.SDK_INT >= 29) it.totalTimeVisible else it.totalTimeInForeground + ) + } + .sortedByDescending { it.usageMillis } + persistStats(prefs, result) + return result +} + +private fun persistStats(prefs: SharedPreferences, stats: List) { + val today = java.text.SimpleDateFormat("yyyy-MM-dd").format(java.util.Date()) + val value = stats.joinToString("|") { "${it.packageName},${it.appName},${it.usageMillis}" } + prefs.edit().putString(today, value).apply() +} + +private fun loadPersistedStats(prefs: SharedPreferences): List { + val today = java.text.SimpleDateFormat("yyyy-MM-dd").format(java.util.Date()) + val value = prefs.getString(today, null) ?: return emptyList() + return value.split("|").mapNotNull { + val parts = it.split(",") + if (parts.size == 3) AppUsageInfo(parts[0], parts[1], parts[2].toLongOrNull() ?: 0L) else null + } +} + +// 需在 Application 初始化时赋值 +var appContext: Context? = null + diff --git a/shared/src/androidMain/kotlin/com/grtsinry43/chronosight/Platform.android.kt b/shared/src/androidMain/kotlin/com/grtsinry43/chronosight/Platform.android.kt new file mode 100644 index 0000000..9b897cb --- /dev/null +++ b/shared/src/androidMain/kotlin/com/grtsinry43/chronosight/Platform.android.kt @@ -0,0 +1,11 @@ +package com.grtsinry43.chronosight + +import android.os.Build + +class AndroidPlatform : Platform { + override val name: String = "Android ${Build.VERSION.SDK_INT}" + override val isAndroid: Boolean = true +} + +actual fun getPlatform(): Platform = AndroidPlatform() + diff --git a/shared/src/androidMain/kotlin/com/grtsinry43/chronosight/ScreenTime.kt b/shared/src/androidMain/kotlin/com/grtsinry43/chronosight/ScreenTime.kt new file mode 100644 index 0000000..3169f1b --- /dev/null +++ b/shared/src/androidMain/kotlin/com/grtsinry43/chronosight/ScreenTime.kt @@ -0,0 +1,116 @@ +package com.grtsinry43.chronosight + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.PowerManager +import android.os.SystemClock + +class AppUsageTimerAndroid : ScreenTime { + private var totalUsage: Long = 0 + private var lastResume: Long = 0 + private var isResumed = false + + fun onAppResume() { + if (!isResumed) { + lastResume = SystemClock.elapsedRealtime() + isResumed = true + } + } + + fun onAppPause() { + if (isResumed) { + totalUsage += SystemClock.elapsedRealtime() - lastResume + isResumed = false + } + } + + override fun getUsageMillis(): Long { + return if (isResumed) { + totalUsage + (SystemClock.elapsedRealtime() - lastResume) + } else { + totalUsage + } + } +} + +class GlobalScreenTimeManager(private val context: Context) : ScreenTime { + private var screenOnTime: Long = 0L + private var lastScreenOnTimestamp: Long = 0L + private var isScreenOn: Boolean = false + private var isRegistered = false + + private val screenReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + when (intent?.action) { + Intent.ACTION_SCREEN_ON -> { + lastScreenOnTimestamp = System.currentTimeMillis() + isScreenOn = true + } + Intent.ACTION_SCREEN_OFF -> { + if (isScreenOn) { + screenOnTime += System.currentTimeMillis() - lastScreenOnTimestamp + isScreenOn = false + } + } + } + } + } + + fun startTracking() { + if (!isRegistered) { + val filter = IntentFilter().apply { + addAction(Intent.ACTION_SCREEN_ON) + addAction(Intent.ACTION_SCREEN_OFF) + } + context.registerReceiver(screenReceiver, filter) + val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager + isScreenOn = pm.isInteractive + if (isScreenOn) { + lastScreenOnTimestamp = System.currentTimeMillis() + } + isRegistered = true + } + } + + fun stopTracking() { + if (isRegistered) { + context.unregisterReceiver(screenReceiver) + if (isScreenOn) { + screenOnTime += System.currentTimeMillis() - lastScreenOnTimestamp + isScreenOn = false + } + isRegistered = false + } + } + + override fun getUsageMillis(): Long { + var total = screenOnTime + if (isScreenOn) { + total += System.currentTimeMillis() - lastScreenOnTimestamp + } + return total + } +} + +private var globalScreenTimeManager: GlobalScreenTimeManager? = null + +actual fun getScreenTime(): ScreenTime { + if (globalScreenTimeManager == null) { + // 需要在 Application 初始化时传入 context + throw IllegalStateException("GlobalScreenTimeManager not initialized. Call initGlobalScreenTimeManager(context) in Application.onCreate().") + } + return globalScreenTimeManager!! +} + +fun initGlobalScreenTimeManager(context: Context) { + if (globalScreenTimeManager == null) { + globalScreenTimeManager = GlobalScreenTimeManager(context.applicationContext) + globalScreenTimeManager?.startTracking() + } +} + +fun stopGlobalScreenTimeManager() { + globalScreenTimeManager?.stopTracking() +} diff --git a/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/Platform.kt b/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/Platform.kt deleted file mode 100644 index 9d2d616..0000000 --- a/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/Platform.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.grtsinry43.activityanalyzer - -interface Platform { - val name: String -} - -expect fun getPlatform(): Platform \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt b/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt deleted file mode 100644 index 9bd4d3e..0000000 --- a/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.grtsinry43.activityanalyzer.i18n - -expect class StringResource { - fun getString(key: String): String - fun getString(key: String, vararg args: Any): String -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt b/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt deleted file mode 100644 index 694a937..0000000 --- a/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.grtsinry43.activityanalyzer.i18n - -expect object StringResourceFactory { - fun create(): StringResource -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/AppUsageAnalyzer.kt b/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/AppUsageAnalyzer.kt new file mode 100644 index 0000000..8e728b8 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/AppUsageAnalyzer.kt @@ -0,0 +1,18 @@ +package com.grtsinry43.chronosight + +/** + * 单个应用的使用时长统计 + */ +data class AppUsageInfo( + val packageName: String, // Android: 包名,Linux: 进程名 + val appName: String, // 应用名 + val usageMillis: Long // 使用时长(毫秒) +) + +/** + * 获取一段时间内各应用的使用时长统计 + * @param startMillis 开始时间(时间戳,毫秒) + * @param endMillis 结束时间(时间戳,毫秒) + */ +expect fun getAppUsageStats(startMillis: Long, endMillis: Long): List + diff --git a/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/Greeting.kt b/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/Greeting.kt similarity index 94% rename from shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/Greeting.kt rename to shared/src/commonMain/kotlin/com/grtsinry43/chronosight/Greeting.kt index c3963a5..2e2a004 100644 --- a/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/Greeting.kt +++ b/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/Greeting.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer +package com.grtsinry43.chronosight import io.ktor.client.call.body import io.ktor.client.request.get diff --git a/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/NetworkModule.kt b/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/NetworkModule.kt similarity index 93% rename from shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/NetworkModule.kt rename to shared/src/commonMain/kotlin/com/grtsinry43/chronosight/NetworkModule.kt index f0a64f9..c14245f 100644 --- a/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/NetworkModule.kt +++ b/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/NetworkModule.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer +package com.grtsinry43.chronosight import io.ktor.client.HttpClient import io.ktor.client.plugins.contentnegotiation.ContentNegotiation diff --git a/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/Platform.kt b/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/Platform.kt new file mode 100644 index 0000000..192083a --- /dev/null +++ b/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/Platform.kt @@ -0,0 +1,20 @@ +package com.grtsinry43.chronosight + +interface Platform { + val name: String + val isAndroid: Boolean + + companion object { + val current: Platform by lazy { getPlatform() } + + // Convenience properties to access platform information + val isAndroid: Boolean + get() = current.isAndroid + + val name: String + get() = current.name + } +} + +expect fun getPlatform(): Platform + diff --git a/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/ScreenTime.kt b/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/ScreenTime.kt similarity index 53% rename from shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/ScreenTime.kt rename to shared/src/commonMain/kotlin/com/grtsinry43/chronosight/ScreenTime.kt index 002ee74..cb366ba 100644 --- a/shared/src/commonMain/kotlin/com/grtsinry43/activityanalyzer/ScreenTime.kt +++ b/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/ScreenTime.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer +package com.grtsinry43.chronosight /** * @author grtsinry43 @@ -6,7 +6,8 @@ package com.grtsinry43.activityanalyzer * @description 热爱可抵岁月漫长 */ interface ScreenTime { - val screenTime: Long + fun getUsageMillis(): Long } -expect fun getScreenTime(): ScreenTime \ No newline at end of file +expect fun getScreenTime(): ScreenTime + diff --git a/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/ScreenTimeUtil.kt b/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/ScreenTimeUtil.kt new file mode 100644 index 0000000..a2b5e78 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/grtsinry43/chronosight/ScreenTimeUtil.kt @@ -0,0 +1,12 @@ +package com.grtsinry43.chronosight + +/** + * 工具函数:毫秒转xh ym + */ +fun formatMillisToHourMin(millis: Long): String { + val totalMinutes = millis / 60000 + val hours = totalMinutes / 60 + val minutes = totalMinutes % 60 + return "${hours}h ${minutes}m" +} + diff --git a/shared/src/commonMain/moko-resources/base/strings.xml b/shared/src/commonMain/moko-resources/base/strings.xml new file mode 100644 index 0000000..a3e2a0e --- /dev/null +++ b/shared/src/commonMain/moko-resources/base/strings.xml @@ -0,0 +1,42 @@ + + + Chronosight + Welcome Back, %s! + Today's Screen Time + You're on track with your daily goal! + Quick Glance: Top Apps + Go to grant Usage Access + Quick Actions + Start Focus Session + View Today's Details + Recent Insights + Exceeded daily goal for App X. + Screen time 20%% higher yesterday. + Home + Analytics + Reports + Profile + Settings + About + grtsinry43@outlook.com + Last 7 Days + Today + Yesterday + Last 30 Days + Screen Time Analytics + Total Screen Time + Avg Daily Time + Most Used App + Pickups + Usage Patterns + Daily Screen Time (Bar Chart) + App Usage (Pie Chart) + App Usage Breakdown + No usage data. + Analysis Tools + App Breakdown + Time of Day + Usage Goals + View Today's Details + + diff --git a/shared/src/commonMain/moko-resources/zh/strings.xml b/shared/src/commonMain/moko-resources/zh/strings.xml new file mode 100644 index 0000000..d7388bf --- /dev/null +++ b/shared/src/commonMain/moko-resources/zh/strings.xml @@ -0,0 +1,42 @@ + + + 活动分析器 + 欢迎回来,%s! + 今日屏幕时间 + 你已接近今日目标! + 速览:高频应用 + 前往授权使用情况访问 + 快捷操作 + 开始专注模式 + 查看今日详情 + 近期洞察 + App X 超出每日目标。 + 昨日屏幕时间高出 20%。 + 屏幕时间分析 + 总亮屏时长 + 日均时长 + 最常用应用 + 点亮次数 + 使用模式 + 每日屏幕时间(柱状图) + 应用使用(饼图) + 应用使用明细 + 无使用数据或未授权。 + 分析工具 + 应用明细 + 时段分析 + 设定目标 + 查看今日详情 + 首页 + 分析 + 报表 + 我的 + 设置 + 关于 + grtsinry43@outlook.com + 最近7天 + 今天 + 昨天 + 最近30天 + + diff --git a/shared/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt b/shared/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt deleted file mode 100644 index 74eb1d4..0000000 --- a/shared/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.grtsinry43.activityanalyzer.i18n - -import java.util.Properties -import java.io.InputStream -import java.text.MessageFormat - -actual class StringResource { - private val properties = Properties() - private var currentLocale: String = "en" - - init { - loadProperties("en") - } - - fun setLocale(locale: String) { - if (currentLocale != locale) { - currentLocale = locale - loadProperties(locale) - } - } - - private fun loadProperties(locale: String) { - properties.clear() - val baseName = "strings" - val resourceName = if (locale == "en") "$baseName.properties" else "${baseName}_$locale.properties" - - val inputStream = javaClass.classLoader.getResourceAsStream(resourceName) - if (inputStream != null) { - properties.load(inputStream) - inputStream.close() - } - } - - actual fun getString(key: String): String { - return properties.getProperty(key, key) - } - - actual fun getString(key: String, vararg args: Any): String { - val pattern = properties.getProperty(key, key) - return MessageFormat.format(pattern, *args) - } -} \ No newline at end of file diff --git a/shared/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt b/shared/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt deleted file mode 100644 index 473f7d4..0000000 --- a/shared/src/desktopMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.grtsinry43.activityanalyzer.i18n - -actual object StringResourceFactory { - private val stringResource = StringResource() - - actual fun create(): StringResource { - return stringResource - } - - fun setLocale(locale: String) { - stringResource.setLocale(locale) - } -} \ No newline at end of file diff --git a/shared/src/desktopMain/resources/strings.properties b/shared/src/desktopMain/resources/strings.properties index 5b5542f..305dfce 100644 --- a/shared/src/desktopMain/resources/strings.properties +++ b/shared/src/desktopMain/resources/strings.properties @@ -1,5 +1,5 @@ -app.title=Activity Analyzer -home.welcome=Welcome to Activity Analyzer +app.title=Chronosight +home.welcome=Welcome to Chronosight home.quickActions=Quick Actions home.newAnalysis=New Analysis home.importData=Import Data @@ -16,7 +16,7 @@ settings.title=Settings settings.dataStorage=Data Storage Location settings.autoBackup=Auto Backup settings.notifications=Notifications -about.title=About Activity Analyzer +about.title=About Chronosight about.version=Version {0} about.description=A powerful activity analysis tool to help you better understand and optimize your activity data. about.checkUpdate=Check for Updates \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt b/shared/src/iosMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt deleted file mode 100644 index c40baf6..0000000 --- a/shared/src/iosMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.grtsinry43.activityanalyzer.i18n - -import platform.Foundation.NSBundle -import platform.Foundation.NSString -import platform.Foundation.stringWithFormat - -actual class StringResource { - private val bundle = NSBundle.mainBundle - - actual fun getString(key: String): String { - return bundle.localizedStringForKey(key, "", null) ?: key - } - - actual fun getString(key: String, vararg args: Any): String { - val format = bundle.localizedStringForKey(key, "", null) ?: key - return when (args.size) { - 0 -> format - 1 -> NSString.stringWithFormat(format, args[0]) - 2 -> NSString.stringWithFormat(format, args[0], args[1]) - 3 -> NSString.stringWithFormat(format, args[0], args[1], args[2]) - 4 -> NSString.stringWithFormat(format, args[0], args[1], args[2], args[3]) - else -> { - // 处理更多参数情况 - // 简单方案:只使用前4个参数 - NSString.stringWithFormat(format, args[0], args[1], args[2], args[3]) - } - } - } -} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt b/shared/src/iosMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt deleted file mode 100644 index ff355e5..0000000 --- a/shared/src/iosMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.grtsinry43.activityanalyzer.i18n - -actual object StringResourceFactory { - actual fun create(): StringResource { - return StringResource() - } -} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/com/grtsinry43/chronosight/AppUsageAnalyzer.kt b/shared/src/iosMain/kotlin/com/grtsinry43/chronosight/AppUsageAnalyzer.kt new file mode 100644 index 0000000..c24634d --- /dev/null +++ b/shared/src/iosMain/kotlin/com/grtsinry43/chronosight/AppUsageAnalyzer.kt @@ -0,0 +1,4 @@ +package com.grtsinry43.chronosight + +actual fun getAppUsageStats(startMillis: Long, endMillis: Long): List = emptyList() + diff --git a/shared/src/iosMain/kotlin/com/grtsinry43/activityanalyzer/Platform.ios.kt b/shared/src/iosMain/kotlin/com/grtsinry43/chronosight/Platform.ios.kt similarity index 57% rename from shared/src/iosMain/kotlin/com/grtsinry43/activityanalyzer/Platform.ios.kt rename to shared/src/iosMain/kotlin/com/grtsinry43/chronosight/Platform.ios.kt index 72a742a..9f8e684 100644 --- a/shared/src/iosMain/kotlin/com/grtsinry43/activityanalyzer/Platform.ios.kt +++ b/shared/src/iosMain/kotlin/com/grtsinry43/chronosight/Platform.ios.kt @@ -1,9 +1,11 @@ -package com.grtsinry43.activityanalyzer +package com.grtsinry43.chronosight import platform.UIKit.UIDevice class IOSPlatform: Platform { override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion + override val isAndroid: Boolean = false } -actual fun getPlatform(): Platform = IOSPlatform() \ No newline at end of file +actual fun getPlatform(): Platform = IOSPlatform() + diff --git a/shared/src/jvmMain/kotlin/com/grtsinry43/activityanalyzer/Platform.jvm.kt b/shared/src/jvmMain/kotlin/com/grtsinry43/activityanalyzer/Platform.jvm.kt deleted file mode 100644 index df34fc4..0000000 --- a/shared/src/jvmMain/kotlin/com/grtsinry43/activityanalyzer/Platform.jvm.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.grtsinry43.activityanalyzer - -class JVMPlatform: Platform { - override val name: String = "Java ${System.getProperty("java.version")}" -} - -actual fun getPlatform(): Platform = JVMPlatform() \ No newline at end of file diff --git a/shared/src/jvmMain/kotlin/com/grtsinry43/activityanalyzer/ScreenTime.kt b/shared/src/jvmMain/kotlin/com/grtsinry43/activityanalyzer/ScreenTime.kt deleted file mode 100644 index 5f6fa14..0000000 --- a/shared/src/jvmMain/kotlin/com/grtsinry43/activityanalyzer/ScreenTime.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.grtsinry43.activityanalyzer - -import java.io.BufferedReader -import java.io.InputStreamReader - -/** - * @author grtsinry43 - * @date 2025/4/14 16:30 - * @description 热爱可抵岁月漫长 - */ -class LinuxScreenTime : ScreenTime { - override val screenTime: Long - get() { - try { - println("===") - // Execute qdbus command to query active window information - val process = Runtime.getRuntime().exec("qdbus org.kde.KWin /KWin queryWindowInfo") - val reader = BufferedReader(InputStreamReader(process.inputStream)) - val output = reader.readLines() - reader.close() - - // Parse the output to find the caption (window title) - val captionLine = output.find { it.trim().startsWith("caption:") } - val windowTitle = captionLine?.substringAfter("caption:")?.trim() - - if (windowTitle != null) { - println("Active Window Title: $windowTitle") - return 3600 // Example: Return dummy screen time - } - } catch (e: Exception) { - e.printStackTrace() - } - println("Failed to get active window title.") - return 0 - } -} - -actual fun getScreenTime(): ScreenTime = LinuxScreenTime() \ No newline at end of file diff --git a/shared/src/jvmMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt b/shared/src/jvmMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt deleted file mode 100644 index 74eb1d4..0000000 --- a/shared/src/jvmMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResource.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.grtsinry43.activityanalyzer.i18n - -import java.util.Properties -import java.io.InputStream -import java.text.MessageFormat - -actual class StringResource { - private val properties = Properties() - private var currentLocale: String = "en" - - init { - loadProperties("en") - } - - fun setLocale(locale: String) { - if (currentLocale != locale) { - currentLocale = locale - loadProperties(locale) - } - } - - private fun loadProperties(locale: String) { - properties.clear() - val baseName = "strings" - val resourceName = if (locale == "en") "$baseName.properties" else "${baseName}_$locale.properties" - - val inputStream = javaClass.classLoader.getResourceAsStream(resourceName) - if (inputStream != null) { - properties.load(inputStream) - inputStream.close() - } - } - - actual fun getString(key: String): String { - return properties.getProperty(key, key) - } - - actual fun getString(key: String, vararg args: Any): String { - val pattern = properties.getProperty(key, key) - return MessageFormat.format(pattern, *args) - } -} \ No newline at end of file diff --git a/shared/src/jvmMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt b/shared/src/jvmMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt deleted file mode 100644 index 473f7d4..0000000 --- a/shared/src/jvmMain/kotlin/com/grtsinry43/activityanalyzer/i18n/StringResourceFactory.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.grtsinry43.activityanalyzer.i18n - -actual object StringResourceFactory { - private val stringResource = StringResource() - - actual fun create(): StringResource { - return stringResource - } - - fun setLocale(locale: String) { - stringResource.setLocale(locale) - } -} \ No newline at end of file diff --git a/shared/src/jvmMain/kotlin/com/grtsinry43/chronosight/AppUsageAnalyzer.kt b/shared/src/jvmMain/kotlin/com/grtsinry43/chronosight/AppUsageAnalyzer.kt new file mode 100644 index 0000000..f901c6e --- /dev/null +++ b/shared/src/jvmMain/kotlin/com/grtsinry43/chronosight/AppUsageAnalyzer.kt @@ -0,0 +1,76 @@ +package com.grtsinry43.chronosight + +import java.io.File +import java.time.LocalDate +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.util.concurrent.ConcurrentHashMap + +class LinuxAppUsageAnalyzer { + private val usageMap = ConcurrentHashMap() // 进程名 -> 累计时长 + private var lastActiveProcess: String? = null + private var lastActiveTimestamp: Long = System.currentTimeMillis() + private val statsFile = File(System.getProperty("user.home"), ".activity_analyzer_app_usage") + private val today: String = LocalDate.now().format(DateTimeFormatter.ISO_DATE) + + init { + // 启动后台线程,定时检测活跃窗口进程名 + Thread { + while (true) { + val proc = getActiveProcessName() + val now = System.currentTimeMillis() + if (lastActiveProcess != null) { + val delta = now - lastActiveTimestamp + usageMap[lastActiveProcess!!] = (usageMap[lastActiveProcess!!] ?: 0L) + delta + } + lastActiveProcess = proc + lastActiveTimestamp = now + Thread.sleep(5000) + saveTodayStats() + } + }.apply { isDaemon = true }.start() + } + + private fun getActiveProcessName(): String { + // 依赖 wmctrl 工具 + return try { + val winId = Runtime.getRuntime().exec(arrayOf("bash", "-c", "xprop -root _NET_ACTIVE_WINDOW | awk '{print $5}'")).inputStream.bufferedReader().readText().trim().removePrefix("0x").ifEmpty { return "unknown" } + val pidLine = Runtime.getRuntime().exec(arrayOf("bash", "-c", "xprop -id 0x$winId _NET_WM_PID")).inputStream.bufferedReader().readText() + val pid = Regex("_NET_WM_PID\\(CARDINAL\\) = (\\d+)").find(pidLine)?.groupValues?.getOrNull(1) ?: return "unknown" + val procName = Runtime.getRuntime().exec(arrayOf("bash", "-c", "ps -p $pid -o comm= ")).inputStream.bufferedReader().readText().trim() + if (procName.isNotEmpty()) procName else "unknown" + } catch (e: Exception) { + "unknown" + } + } + + private fun saveTodayStats() { + val lines = usageMap.entries.joinToString("\n") { "${today}:${it.key}:${it.value}" } + statsFile.writeText(lines) + } + + private fun loadTodayStats() { + if (!statsFile.exists()) return + statsFile.readLines().forEach { line -> + val parts = line.split(":") + if (parts.size == 3 && parts[0] == today) { + usageMap[parts[1]] = parts[2].toLongOrNull() ?: 0L + } + } + } + + fun getStats(): List { + loadTodayStats() + return usageMap.map { (proc, ms) -> + AppUsageInfo( + packageName = proc, + appName = proc, + usageMillis = ms + ) + }.sortedByDescending { it.usageMillis } + } +} + +private val analyzer by lazy { LinuxAppUsageAnalyzer() } + +actual fun getAppUsageStats(startMillis: Long, endMillis: Long): List = analyzer.getStats() diff --git a/shared/src/jvmMain/kotlin/com/grtsinry43/chronosight/Platform.jvm.kt b/shared/src/jvmMain/kotlin/com/grtsinry43/chronosight/Platform.jvm.kt new file mode 100644 index 0000000..450f656 --- /dev/null +++ b/shared/src/jvmMain/kotlin/com/grtsinry43/chronosight/Platform.jvm.kt @@ -0,0 +1,9 @@ +package com.grtsinry43.chronosight + +class JVMPlatform: Platform { + override val name: String = "Java ${System.getProperty("java.version")}" + override val isAndroid: Boolean = false +} + +actual fun getPlatform(): Platform = JVMPlatform() + diff --git a/shared/src/jvmMain/kotlin/com/grtsinry43/chronosight/ScreenTime.kt b/shared/src/jvmMain/kotlin/com/grtsinry43/chronosight/ScreenTime.kt new file mode 100644 index 0000000..8b995dd --- /dev/null +++ b/shared/src/jvmMain/kotlin/com/grtsinry43/chronosight/ScreenTime.kt @@ -0,0 +1,56 @@ +package com.grtsinry43.chronosight + +import java.io.File +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +/** + * Linux 桌面端屏幕时长统计(本地累计,按天存储) + */ +class LinuxScreenTime : ScreenTime { + private val statsFile = File(System.getProperty("user.home"), ".activity_analyzer_screen_time") + private val today: String = LocalDate.now().format(DateTimeFormatter.ISO_DATE) + + // 读取今日累计时长(毫秒) + private fun readTodayMillis(): Long { + if (!statsFile.exists()) return 0L + val line = statsFile.readLines().find { it.startsWith("") || it.startsWith(today) } + return line?.split(":")?.getOrNull(1)?.toLongOrNull() ?: 0L + } + + // 写入今日累计时长 + private fun writeTodayMillis(millis: Long) { + val lines = if (statsFile.exists()) statsFile.readLines().toMutableList() else mutableListOf() + val idx = lines.indexOfFirst { it.startsWith(today) } + if (idx >= 0) { + lines[idx] = "$today:$millis" + } else { + lines.add("$today:$millis") + } + statsFile.writeText(lines.joinToString("\n")) + } + + private var lastActive: Long = System.currentTimeMillis() + private var cachedMillis: Long = readTodayMillis() + + init { + // 启动定时器,每5秒累计一次活跃时长 + Thread { + while (true) { + Thread.sleep(5000) + val now = System.currentTimeMillis() + cachedMillis += now - lastActive + writeTodayMillis(cachedMillis) + lastActive = now + } + }.apply { isDaemon = true }.start() + } + + override fun getUsageMillis(): Long { + // 返回内存中的今日累计时长 + return cachedMillis + (System.currentTimeMillis() - lastActive) + } +} + +actual fun getScreenTime(): ScreenTime = LinuxScreenTime() + diff --git a/shared/src/jvmMain/resources/strings.properties b/shared/src/jvmMain/resources/strings.properties index 5b5542f..305dfce 100644 --- a/shared/src/jvmMain/resources/strings.properties +++ b/shared/src/jvmMain/resources/strings.properties @@ -1,5 +1,5 @@ -app.title=Activity Analyzer -home.welcome=Welcome to Activity Analyzer +app.title=Chronosight +home.welcome=Welcome to Chronosight home.quickActions=Quick Actions home.newAnalysis=New Analysis home.importData=Import Data @@ -16,7 +16,7 @@ settings.title=Settings settings.dataStorage=Data Storage Location settings.autoBackup=Auto Backup settings.notifications=Notifications -about.title=About Activity Analyzer +about.title=About Chronosight about.version=Version {0} about.description=A powerful activity analysis tool to help you better understand and optimize your activity data. about.checkUpdate=Check for Updates \ No newline at end of file diff --git a/shared/src/nativeMain/kotlin/com/grtsinry43/activityanalyzer/ScreenTime.native.kt b/shared/src/nativeMain/kotlin/com/grtsinry43/chronosight/ScreenTime.native.kt similarity index 65% rename from shared/src/nativeMain/kotlin/com/grtsinry43/activityanalyzer/ScreenTime.native.kt rename to shared/src/nativeMain/kotlin/com/grtsinry43/chronosight/ScreenTime.native.kt index 7b1892e..a80afde 100644 --- a/shared/src/nativeMain/kotlin/com/grtsinry43/activityanalyzer/ScreenTime.native.kt +++ b/shared/src/nativeMain/kotlin/com/grtsinry43/chronosight/ScreenTime.native.kt @@ -1,4 +1,4 @@ -package com.grtsinry43.activityanalyzer +package com.grtsinry43.chronosight actual fun getScreenTime(): ScreenTime { TODO("Not yet implemented")