feat: Rename project from Activity Analyzer to Chronosight and update related resources
Some checks failed
KMP Build & Package / Build Linux Native on ubuntu-latest (push) Has been cancelled
KMP Build & Package / Build macOS Native on macos-latest (push) Has been cancelled
KMP Build & Package / Build Windows Native on windows-latest (push) Has been cancelled

This commit is contained in:
grtsinry43 2025-05-19 15:17:49 +08:00
parent dad89bcc81
commit f67fba3a85
Signed by: grtsinry43
GPG Key ID: F3305FB3A978C934
77 changed files with 1130 additions and 753 deletions

View File

@ -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.

View File

@ -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` 模块。

View File

@ -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"
}
}

View File

@ -1,12 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 添加网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 可选:访问网络状态权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 请求使用情况访问权限 -->
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" />
<application
android:name=".MainApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@ -25,4 +29,5 @@
</activity>
</application>
</manifest>
</manifest>

View File

@ -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()
}

View File

@ -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()
}

View File

@ -0,0 +1,12 @@
package com.grtsinry43.chronosight
import android.app.Application
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
// 初始化全局屏幕时长统计
initGlobalScreenTimeManager(this)
}
}

View File

@ -1,3 +1,3 @@
<resources>
<string name="app_name">Activity Analyzer</string>
<string name="app_name">Chronosight</string>
</resources>

View File

@ -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)
}
}
}
}
}
}

View File

@ -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平台的实现
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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<AppUsageInfo>() }
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<AppUsageInfo>() }
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
)

View File

@ -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<AppUsageInfo>() }
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

View File

@ -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

View File

@ -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

View File

@ -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 = {}
)
}
}

View File

@ -1,4 +1,4 @@
package com.grtsinry43.activityanalyzer.theme
package com.grtsinry43.chronosight.theme
import androidx.compose.ui.graphics.Color

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Activity Analyzer</string>
<string name="home">Home</string>
<string name="analytics">Analytics</string>
<string name="reports">Reports</string>
<string name="settings">Settings</string>
<string name="about">About</string>
<string name="quick_actions">Quick Actions</string>
<string name="new_analysis">New Analysis</string>
<string name="import_data">Import Data</string>
<string name="recent_activities">Recent Activities</string>
<string name="analysis_report">Analysis Report</string>
<string name="data_import">Data Import</string>
<string name="analysis_tools">Analysis Tools</string>
<string name="time_analysis">Time Analysis</string>
<string name="distribution_analysis">Distribution Analysis</string>
<string name="my_reports">My Reports</string>
<string name="monthly_report">Monthly Activity Report</string>
<string name="weekly_report">Weekly Activity Report</string>
<string name="download">Download</string>
<string name="data_storage">Data Storage Location</string>
<string name="default_location">Default Location</string>
<string name="change">Change</string>
<string name="auto_backup">Auto Backup</string>
<string name="daily">Daily</string>
<string name="notifications">Notifications</string>
<string name="enabled">Enabled</string>
<string name="version">Version</string>
<string name="app_description">A powerful activity analysis tool to help you better understand and optimize your activity data.</string>
<string name="check_updates">Check Updates</string>
</resources>

View File

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">活动分析器</string>
<string name="home">首页</string>
<string name="analytics">数据分析</string>
<string name="reports">报告</string>
<string name="settings">设置</string>
<string name="about">关于</string>
<string name="quick_actions">快速操作</string>
<string name="new_analysis">新建分析</string>
<string name="import_data">导入数据</string>
<string name="recent_activities">最近活动</string>
<string name="analysis_report">数据分析报告</string>
<string name="data_import">活动数据导入</string>
<string name="analysis_tools">分析工具</string>
<string name="time_analysis">时间分析</string>
<string name="distribution_analysis">分布分析</string>
<string name="my_reports">我的报告</string>
<string name="monthly_report">月度活动报告</string>
<string name="weekly_report">周活动分析</string>
<string name="download">下载</string>
<string name="data_storage">数据存储位置</string>
<string name="default_location">默认位置</string>
<string name="change">更改</string>
<string name="auto_backup">自动备份</string>
<string name="daily">每天</string>
<string name="notifications">通知设置</string>
<string name="enabled">开启</string>
<string name="version">版本</string>
<string name="app_description">一个强大的活动分析工具,帮助您更好地理解和优化您的活动数据。</string>
<string name="check_updates">检查更新</string>
</resources>

View File

@ -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, // 文本颜色

View File

@ -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

View File

@ -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()
}

View File

@ -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

View File

@ -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<AppUsageInfo>() }
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<AppUsageInfo>() }
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) {

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 */ } // 打开文件对话框或路径编辑器

View File

@ -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" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
mokoMultiplatformResources = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "mokoResources" }

View File

@ -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

View File

@ -1,3 +1,3 @@
TEAM_ID=
BUNDLE_ID=com.grtsinry43.activityanalyzer.ActivityAnalyzer
APP_NAME=Activity Analyzer
BUNDLE_ID=com.grtsinry43.chronosight.Chronosight
APP_NAME=Chronosight

View File

@ -18,7 +18,7 @@
058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
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 = "<group>"; };
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
@ -64,7 +64,7 @@
7555FF7C242A565900829871 /* Products */ = {
isa = PBXGroup;
children = (
7555FF7B242A565900829871 /* Activity Analyzer.app */,
7555FF7B242A565900829871 /* Chronosight.app */,
);
name = Products;
sourceTree = "<group>";
@ -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 */

View File

@ -1,4 +1,4 @@
rootProject.name = "ActivityAnalyzer"
rootProject.name = "Chronosight"
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
pluginManagement {

View File

@ -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"
}

View File

@ -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()

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -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"))
}
}

View File

@ -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<AppUsageInfo> {
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<AppUsageInfo>) {
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<AppUsageInfo> {
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

View File

@ -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()

View File

@ -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()
}

View File

@ -1,7 +0,0 @@
package com.grtsinry43.activityanalyzer
interface Platform {
val name: String
}
expect fun getPlatform(): Platform

View File

@ -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
}

View File

@ -1,5 +0,0 @@
package com.grtsinry43.activityanalyzer.i18n
expect object StringResourceFactory {
fun create(): StringResource
}

View File

@ -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<AppUsageInfo>

View File

@ -1,4 +1,4 @@
package com.grtsinry43.activityanalyzer
package com.grtsinry43.chronosight
import io.ktor.client.call.body
import io.ktor.client.request.get

View File

@ -1,4 +1,4 @@
package com.grtsinry43.activityanalyzer
package com.grtsinry43.chronosight
import io.ktor.client.HttpClient
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation

View File

@ -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

View File

@ -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
expect fun getScreenTime(): ScreenTime

View File

@ -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"
}

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Chronosight</string>
<string name="welcome">Welcome Back, %s!</string>
<string name="today_screen_time">Today's Screen Time</string>
<string name="on_track">You're on track with your daily goal!</string>
<string name="quick_glance">Quick Glance: Top Apps</string>
<string name="grant_usage_access">Go to grant Usage Access</string>
<string name="quick_actions">Quick Actions</string>
<string name="start_focus">Start Focus Session</string>
<string name="view_today">View Today's Details</string>
<string name="recent_insights">Recent Insights</string>
<string name="exceeded_goal">Exceeded daily goal for App X.</string>
<string name="higher_yesterday">Screen time 20%% higher yesterday.</string>
<string name="home">Home</string>
<string name="analytics">Analytics</string>
<string name="reports">Reports</string>
<string name="profile">Profile</string>
<string name="settings">Settings</string>
<string name="about">About</string>
<string name="email_placeholder">grtsinry43@outlook.com</string>
<string name="last_7_days">Last 7 Days</string>
<string name="today">Today</string>
<string name="yesterday">Yesterday</string>
<string name="last_30_days">Last 30 Days</string>
<string name="screen_time_analytics">Screen Time Analytics</string>
<string name="total_screen_on_time">Total Screen Time</string>
<string name="avg_daily">Avg Daily Time</string>
<string name="most_used">Most Used App</string>
<string name="pickups">Pickups</string>
<string name="usage_patterns">Usage Patterns</string>
<string name="daily_screen_time_chart">Daily Screen Time (Bar Chart)</string>
<string name="app_usage_pie_chart">App Usage (Pie Chart)</string>
<string name="app_usage_breakdown">App Usage Breakdown</string>
<string name="no_usage_data">No usage data.</string>
<string name="analysis_tools">Analysis Tools</string>
<string name="app_usage_breakdown_btn">App Breakdown</string>
<string name="time_of_day_analysis">Time of Day</string>
<string name="set_usage_goals">Usage Goals</string>
<string name="view_today_details">View Today's Details</string>
</resources>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">活动分析器</string>
<string name="welcome">欢迎回来,%s</string>
<string name="today_screen_time">今日屏幕时间</string>
<string name="on_track">你已接近今日目标!</string>
<string name="quick_glance">速览:高频应用</string>
<string name="grant_usage_access">前往授权使用情况访问</string>
<string name="quick_actions">快捷操作</string>
<string name="start_focus">开始专注模式</string>
<string name="view_today">查看今日详情</string>
<string name="recent_insights">近期洞察</string>
<string name="exceeded_goal">App X 超出每日目标。</string>
<string name="higher_yesterday">昨日屏幕时间高出 20%。</string>
<string name="screen_time_analytics">屏幕时间分析</string>
<string name="total_screen_on_time">总亮屏时长</string>
<string name="avg_daily">日均时长</string>
<string name="most_used">最常用应用</string>
<string name="pickups">点亮次数</string>
<string name="usage_patterns">使用模式</string>
<string name="daily_screen_time_chart">每日屏幕时间(柱状图)</string>
<string name="app_usage_pie_chart">应用使用(饼图)</string>
<string name="app_usage_breakdown">应用使用明细</string>
<string name="no_usage_data">无使用数据或未授权。</string>
<string name="analysis_tools">分析工具</string>
<string name="app_usage_breakdown_btn">应用明细</string>
<string name="time_of_day_analysis">时段分析</string>
<string name="set_usage_goals">设定目标</string>
<string name="view_today_details">查看今日详情</string>
<string name="home">首页</string>
<string name="analytics">分析</string>
<string name="reports">报表</string>
<string name="profile">我的</string>
<string name="settings">设置</string>
<string name="about">关于</string>
<string name="email_placeholder">grtsinry43@outlook.com</string>
<string name="last_7_days">最近7天</string>
<string name="today">今天</string>
<string name="yesterday">昨天</string>
<string name="last_30_days">最近30天</string>
</resources>

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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])
}
}
}
}

View File

@ -1,7 +0,0 @@
package com.grtsinry43.activityanalyzer.i18n
actual object StringResourceFactory {
actual fun create(): StringResource {
return StringResource()
}
}

View File

@ -0,0 +1,4 @@
package com.grtsinry43.chronosight
actual fun getAppUsageStats(startMillis: Long, endMillis: Long): List<AppUsageInfo> = emptyList()

View File

@ -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()
actual fun getPlatform(): Platform = IOSPlatform()

View File

@ -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()

View File

@ -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()

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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<String, Long>() // 进程名 -> 累计时长
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<AppUsageInfo> {
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<AppUsageInfo> = analyzer.getStats()

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -1,4 +1,4 @@
package com.grtsinry43.activityanalyzer
package com.grtsinry43.chronosight
actual fun getScreenTime(): ScreenTime {
TODO("Not yet implemented")