feat: Rename project from Activity Analyzer to Chronosight and update related resources
This commit is contained in:
parent
dad89bcc81
commit
f67fba3a85
12
README.md
12
README.md
@ -1,13 +1,13 @@
|
||||
# Activity Analyzer
|
||||
# Chronosight
|
||||
|
||||

|
||||
[](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.
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
# Activity Analyzer
|
||||
# Chronosight
|
||||
|
||||

|
||||
[](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` 模块。
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
package com.grtsinry43.chronosight
|
||||
|
||||
import android.app.Application
|
||||
|
||||
class MainApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
// 初始化全局屏幕时长统计
|
||||
initGlobalScreenTimeManager(this)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Activity Analyzer</string>
|
||||
<string name="app_name">Chronosight</string>
|
||||
</resources>
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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平台的实现
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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,
|
||||
@ -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
|
||||
)
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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 = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.grtsinry43.activityanalyzer.theme
|
||||
package com.grtsinry43.chronosight.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -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, // 文本颜色
|
||||
@ -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
|
||||
@ -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()
|
||||
}
|
||||
@ -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
|
||||
@ -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) {
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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 */ } // 打开文件对话框或路径编辑器
|
||||
@ -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" }
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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 */
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
rootProject.name = "ActivityAnalyzer"
|
||||
rootProject.name = "Chronosight"
|
||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||
|
||||
pluginManagement {
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"))
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
package com.grtsinry43.activityanalyzer
|
||||
|
||||
interface Platform {
|
||||
val name: String
|
||||
}
|
||||
|
||||
expect fun getPlatform(): Platform
|
||||
@ -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
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package com.grtsinry43.activityanalyzer.i18n
|
||||
|
||||
expect object StringResourceFactory {
|
||||
fun create(): StringResource
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.grtsinry43.activityanalyzer
|
||||
package com.grtsinry43.chronosight
|
||||
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.get
|
||||
@ -1,4 +1,4 @@
|
||||
package com.grtsinry43.activityanalyzer
|
||||
package com.grtsinry43.chronosight
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
42
shared/src/commonMain/moko-resources/base/strings.xml
Normal file
42
shared/src/commonMain/moko-resources/base/strings.xml
Normal 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>
|
||||
|
||||
42
shared/src/commonMain/moko-resources/zh/strings.xml
Normal file
42
shared/src/commonMain/moko-resources/zh/strings.xml
Normal 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>
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
package com.grtsinry43.activityanalyzer.i18n
|
||||
|
||||
actual object StringResourceFactory {
|
||||
actual fun create(): StringResource {
|
||||
return StringResource()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
package com.grtsinry43.chronosight
|
||||
|
||||
actual fun getAppUsageStats(startMillis: Long, endMillis: Long): List<AppUsageInfo> = emptyList()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
@ -1,4 +1,4 @@
|
||||
package com.grtsinry43.activityanalyzer
|
||||
package com.grtsinry43.chronosight
|
||||
|
||||
actual fun getScreenTime(): ScreenTime {
|
||||
TODO("Not yet implemented")
|
||||
Loading…
x
Reference in New Issue
Block a user