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)
|
[](https://kotlinlang.org)
|
||||||
|
|
||||||
**English | [简体中文](README_zh.md)**
|
**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
|
## Key Features
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ Gain valuable insights into how you spend your time on your devices. Activity An
|
|||||||
|
|
||||||
## Technology Stack
|
## 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.
|
* **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.
|
* **Android:** Native UI development using Kotlin and Jetpack Compose.
|
||||||
@ -29,7 +29,7 @@ Activity Analyzer is built with the following technologies:
|
|||||||
|
|
||||||
## Platform Support
|
## Platform Support
|
||||||
|
|
||||||
Activity Analyzer is currently targeting the following platforms:
|
Chronosight is currently targeting the following platforms:
|
||||||
|
|
||||||
* 📱 **Mobile:** Android, iOS
|
* 📱 **Mobile:** Android, iOS
|
||||||
* 💻 **Desktop:** macOS, Windows, Linux
|
* 💻 **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:
|
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.
|
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).
|
3. **Open project:** Open the project in IntelliJ IDEA (or Android Studio).
|
||||||
4. **Build and Run:**
|
4. **Build and Run:**
|
||||||
* **Android:** Run the `androidApp` module.
|
* **Android:** Run the `androidApp` module.
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
# Activity Analyzer
|
# Chronosight
|
||||||
|
|
||||||

|

|
||||||
[](https://kotlinlang.org)
|
[](https://kotlinlang.org)
|
||||||
|
|
||||||
**[English](README.md) | 简体中文**
|
**[English](README.md) | 简体中文**
|
||||||
|
|
||||||
**时间都去哪儿了?通过 Activity Analyzer 了解您的数字习惯,跨平台跟踪和分析您的屏幕时间。**
|
**时间都去哪儿了?通过 Chronosight 了解您的数字习惯,跨平台跟踪和分析您的屏幕时间。**
|
||||||
|
|
||||||
深入了解您在设备上花费时间的方式。Activity Analyzer 帮助您跟踪屏幕使用时间、分析您的应用使用情况,并最终促进您在手机、平板电脑和电脑上实现更好的数字健康。
|
深入了解您在设备上花费时间的方式。Chronosight 帮助您跟踪屏幕使用时间、分析您的应用使用情况,并最终促进您在手机、平板电脑和电脑上实现更好的数字健康。
|
||||||
|
|
||||||
## 主要功能
|
## 主要功能
|
||||||
|
|
||||||
@ -35,7 +35,7 @@
|
|||||||
如果您有兴趣贡献代码或自行构建项目,请参考以下简要指南:
|
如果您有兴趣贡献代码或自行构建项目,请参考以下简要指南:
|
||||||
|
|
||||||
1. **前提条件:** 确保您已设置好 Android、iOS 和桌面 Kotlin 开发所需的 SDK 和开发环境。
|
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 中打开项目。
|
3. **打开:** 在 IntelliJ IDEA 或 Android Studio 中打开项目。
|
||||||
4. **构建并运行:**
|
4. **构建并运行:**
|
||||||
* **Android:** 运行 `androidApp` 模块。
|
* **Android:** 运行 `androidApp` 模块。
|
||||||
|
|||||||
@ -37,6 +37,7 @@ kotlin {
|
|||||||
implementation(libs.androidx.lifecycle.runtime.compose)
|
implementation(libs.androidx.lifecycle.runtime.compose)
|
||||||
implementation(projects.shared)
|
implementation(projects.shared)
|
||||||
implementation(compose.materialIconsExtended)
|
implementation(compose.materialIconsExtended)
|
||||||
|
implementation(libs.moko.resources.compose)
|
||||||
}
|
}
|
||||||
desktopMain.dependencies {
|
desktopMain.dependencies {
|
||||||
implementation(compose.desktop.currentOs)
|
implementation(compose.desktop.currentOs)
|
||||||
@ -46,11 +47,11 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.grtsinry43.activityanalyzer"
|
namespace = "com.grtsinry43.chronosight"
|
||||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.grtsinry43.activityanalyzer"
|
applicationId = "com.grtsinry43.chronosight"
|
||||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||||
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
@ -78,11 +79,11 @@ dependencies {
|
|||||||
|
|
||||||
compose.desktop {
|
compose.desktop {
|
||||||
application {
|
application {
|
||||||
mainClass = "com.grtsinry43.activityanalyzer.MainKt"
|
mainClass = "com.grtsinry43.chronosight.MainKt"
|
||||||
|
|
||||||
nativeDistributions {
|
nativeDistributions {
|
||||||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
||||||
packageName = "com.grtsinry43.activityanalyzer"
|
packageName = "com.grtsinry43.chronosight"
|
||||||
packageVersion = "1.0.0"
|
packageVersion = "1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,16 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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.INTERNET" />
|
||||||
<!-- 可选:访问网络状态权限 -->
|
<!-- 可选:访问网络状态权限 -->
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<!-- 请求使用情况访问权限 -->
|
||||||
|
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".MainApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
@ -25,4 +29,5 @@
|
|||||||
</activity>
|
</activity>
|
||||||
</application>
|
</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>
|
<resources>
|
||||||
<string name="app_name">Activity Analyzer</string>
|
<string name="app_name">Chronosight</string>
|
||||||
</resources>
|
</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.background
|
||||||
import androidx.compose.foundation.border
|
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.grtsinry43.activityanalyzer.components
|
package com.grtsinry43.chronosight.components
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
@ -14,7 +14,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.grtsinry43.activityanalyzer.components
|
package com.grtsinry43.chronosight.components
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.grtsinry43.activityanalyzer.components
|
package com.grtsinry43.chronosight.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.icons.Icons
|
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.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.grtsinry43.activityanalyzer.components
|
package com.grtsinry43.chronosight.components
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.grtsinry43.activityanalyzer.components
|
package com.grtsinry43.chronosight.components
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@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.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
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.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.grtsinry43.activityanalyzer.components
|
package com.grtsinry43.chronosight.components
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.layout.*
|
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.grtsinry43.activityanalyzer.components
|
package com.grtsinry43.chronosight.components
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
@ -11,7 +11,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.unit.dp
|
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
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.grtsinry43.activityanalyzer.screens
|
package com.grtsinry43.chronosight.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
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.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.grtsinry43.activityanalyzer.components.InfoItem
|
import com.grtsinry43.chronosight.components.InfoItem
|
||||||
import com.grtsinry43.activityanalyzer.components.SettingItem
|
import com.grtsinry43.chronosight.components.SettingItem
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -36,7 +36,7 @@ fun MobileAboutScreen(colors: AppThemes.Colors) {
|
|||||||
modifier = Modifier.size(72.dp) // Slightly smaller for mobile about screen
|
modifier = Modifier.size(72.dp) // Slightly smaller for mobile about screen
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
"Activity Analyzer",
|
"Chronosight",
|
||||||
style = MaterialTheme.typography.headlineMedium.copy(
|
style = MaterialTheme.typography.headlineMedium.copy(
|
||||||
color = colors.onBackground,
|
color = colors.onBackground,
|
||||||
fontWeight = FontWeight.Bold,
|
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.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.grtsinry43.activityanalyzer.components.ChartPlaceholder
|
import com.grtsinry43.chronosight.components.ChartPlaceholder
|
||||||
import com.grtsinry43.activityanalyzer.components.MetricCard
|
import com.grtsinry43.chronosight.components.MetricCard
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledButton
|
import com.grtsinry43.chronosight.components.StyledButton
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledCard
|
import com.grtsinry43.chronosight.components.StyledCard
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
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 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
|
@Composable
|
||||||
fun MobileAnalyticsScreen(colors: AppThemes.Colors) {
|
fun MobileAnalyticsScreen(colors: AppThemes.Colors) {
|
||||||
var selectedTimeRange by remember { mutableStateOf("Last 7 Days") }
|
// Get string values inside the composable scope
|
||||||
val timeRanges =
|
val todayStr = stringResource(MR.strings.today)
|
||||||
listOf("Today", "Yesterday", "Last 7 Days", "Last 30 Days") // Simplified for mobile
|
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(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -32,7 +54,7 @@ fun MobileAnalyticsScreen(colors: AppThemes.Colors) {
|
|||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"Screen Time Analytics",
|
stringResource(MR.strings.screen_time_analytics),
|
||||||
style = MaterialTheme.typography.headlineSmall.copy(
|
style = MaterialTheme.typography.headlineSmall.copy(
|
||||||
color = colors.onBackground,
|
color = colors.onBackground,
|
||||||
fontWeight = FontWeight.Bold,
|
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
|
// Key Metrics - Use a Grid for better mobile layout if more than 2, or stacked Rows
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||||
MetricCard(
|
MetricCard(
|
||||||
title = "Total Time",
|
title = stringResource(MR.strings.total_screen_on_time),
|
||||||
value = "25h 10m",
|
value = formatMillisToHourMin(screenTimeMillis.value),
|
||||||
icon = Icons.Default.Smartphone,
|
icon = Icons.Default.Smartphone,
|
||||||
colors = colors,
|
colors = colors,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
MetricCard(
|
MetricCard(
|
||||||
title = "Avg Daily",
|
title = stringResource(MR.strings.avg_daily),
|
||||||
value = "3h 35m",
|
value = formatMillisToHourMin(screenTimeMillis.value),
|
||||||
icon = Icons.Default.AvTimer,
|
icon = Icons.Default.AvTimer,
|
||||||
colors = colors,
|
colors = colors,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
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(
|
MetricCard(
|
||||||
title = "Most Used",
|
title = stringResource(MR.strings.most_used),
|
||||||
value = "App A",
|
value = mostUsed?.appName ?: "-",
|
||||||
icon = Icons.Default.StarOutline,
|
icon = Icons.Default.StarOutline,
|
||||||
colors = colors,
|
colors = colors,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
MetricCard(
|
MetricCard(
|
||||||
title = "Pickups",
|
title = stringResource(MR.strings.pickups),
|
||||||
value = "75",
|
value = "-",
|
||||||
icon = Icons.Default.TouchApp,
|
icon = Icons.Default.TouchApp,
|
||||||
colors = colors,
|
colors = colors,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StyledCard(colors = colors) {
|
StyledCard(colors = colors) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"Usage Patterns",
|
stringResource(MR.strings.usage_patterns),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
color = colors.onSurface,
|
color = colors.onSurface,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
@ -123,25 +159,71 @@ fun MobileAnalyticsScreen(colors: AppThemes.Colors) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
ChartPlaceholder(
|
ChartPlaceholder(
|
||||||
text = "Daily Screen Time (Bar Chart)",
|
text = stringResource(MR.strings.daily_screen_time_chart),
|
||||||
colors = colors,
|
colors = colors,
|
||||||
modifier = Modifier.fillMaxWidth().height(180.dp)
|
modifier = Modifier.fillMaxWidth().height(180.dp)
|
||||||
) // Slightly smaller charts for mobile
|
) // Slightly smaller charts for mobile
|
||||||
ChartPlaceholder(
|
ChartPlaceholder(
|
||||||
text = "App Usage (Pie Chart)",
|
text = stringResource(MR.strings.app_usage_pie_chart),
|
||||||
colors = colors,
|
colors = colors,
|
||||||
modifier = Modifier.fillMaxWidth().height(180.dp)
|
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) {
|
StyledCard(colors = colors) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"Analysis Tools",
|
stringResource(MR.strings.analysis_tools),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
color = colors.onSurface,
|
color = colors.onSurface,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
@ -151,21 +233,21 @@ fun MobileAnalyticsScreen(colors: AppThemes.Colors) {
|
|||||||
StyledButton(
|
StyledButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
icon = Icons.Default.PieChartOutline,
|
icon = Icons.Default.PieChartOutline,
|
||||||
text = "App Usage Breakdown",
|
text = stringResource(MR.strings.app_usage_breakdown_btn),
|
||||||
onClick = { /* TODO */ },
|
onClick = { /* TODO */ },
|
||||||
colors = colors
|
colors = colors
|
||||||
)
|
)
|
||||||
StyledButton(
|
StyledButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
icon = Icons.Default.AccessTime,
|
icon = Icons.Default.AccessTime,
|
||||||
text = "Time of Day Analysis",
|
text = stringResource(MR.strings.time_of_day_analysis),
|
||||||
onClick = { /* TODO */ },
|
onClick = { /* TODO */ },
|
||||||
colors = colors
|
colors = colors
|
||||||
)
|
)
|
||||||
StyledButton(
|
StyledButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
icon = Icons.Default.TrackChanges,
|
icon = Icons.Default.TrackChanges,
|
||||||
text = "Set Usage Goals",
|
text = stringResource(MR.strings.set_usage_goals),
|
||||||
onClick = { /* TODO */ },
|
onClick = { /* TODO */ },
|
||||||
colors = colors
|
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.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
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.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.grtsinry43.activityanalyzer.components.ActivityItem
|
import com.grtsinry43.chronosight.components.ActivityItem
|
||||||
import com.grtsinry43.activityanalyzer.components.SimpleListItem
|
import com.grtsinry43.chronosight.components.SimpleListItem
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledButton
|
import com.grtsinry43.chronosight.components.StyledButton
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledCard
|
import com.grtsinry43.chronosight.components.StyledCard
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
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 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
|
@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(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@ -29,7 +47,7 @@ fun MobileHomeScreen(colors: AppThemes.Colors) {
|
|||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Welcome Back, grtsinry43!",
|
text = stringResource(MR.strings.welcome, "grtsinry43"),
|
||||||
style = MaterialTheme.typography.headlineSmall.copy(
|
style = MaterialTheme.typography.headlineSmall.copy(
|
||||||
color = colors.onBackground,
|
color = colors.onBackground,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
@ -43,7 +61,7 @@ fun MobileHomeScreen(colors: AppThemes.Colors) {
|
|||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Today's Screen Time",
|
text = stringResource(MR.strings.today_screen_time),
|
||||||
style = MaterialTheme.typography.titleLarge.copy( // Larger title for emphasis
|
style = MaterialTheme.typography.titleLarge.copy( // Larger title for emphasis
|
||||||
color = colors.onSurface,
|
color = colors.onSurface,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
@ -51,7 +69,7 @@ fun MobileHomeScreen(colors: AppThemes.Colors) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "3h 45m", // Placeholder
|
text = formatMillisToHourMin(screenTimeMillis.value),
|
||||||
style = MaterialTheme.typography.displayMedium.copy( // Prominent display
|
style = MaterialTheme.typography.displayMedium.copy( // Prominent display
|
||||||
color = colors.accent,
|
color = colors.accent,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
@ -60,7 +78,7 @@ fun MobileHomeScreen(colors: AppThemes.Colors) {
|
|||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "You're on track with your daily goal!", // Placeholder
|
text = stringResource(MR.strings.on_track), // Placeholder
|
||||||
style = MaterialTheme.typography.bodyMedium.copy(
|
style = MaterialTheme.typography.bodyMedium.copy(
|
||||||
color = colors.secondaryText,
|
color = colors.secondaryText,
|
||||||
fontSize = 14.sp
|
fontSize = 14.sp
|
||||||
@ -76,24 +94,49 @@ fun MobileHomeScreen(colors: AppThemes.Colors) {
|
|||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Quick Glance: Top Apps",
|
text = stringResource(MR.strings.quick_glance),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
color = colors.onSurface,
|
color = colors.onSurface,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
SimpleListItem(
|
// 动态获取今日前3应用
|
||||||
icon = Icons.Filled.SmartDisplay,
|
val now = remember { System.currentTimeMillis() }
|
||||||
text = "App A: 1h 15m",
|
val todayStart = remember {
|
||||||
colors = colors
|
Calendar.getInstance().apply {
|
||||||
)
|
set(Calendar.HOUR_OF_DAY, 0)
|
||||||
SimpleListItem(
|
set(Calendar.MINUTE, 0)
|
||||||
icon = Icons.Filled.PhotoCamera,
|
set(Calendar.SECOND, 0)
|
||||||
text = "App B: 45m",
|
set(Calendar.MILLISECOND, 0)
|
||||||
colors = colors
|
}.timeInMillis
|
||||||
)
|
}
|
||||||
SimpleListItem(icon = Icons.Filled.Chat, text = "App C: 30m", colors = colors)
|
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
|
verticalArrangement = Arrangement.spacedBy(12.dp) // Spacing for buttons
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Quick Actions",
|
text = stringResource(MR.strings.quick_actions),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
color = colors.onSurface,
|
color = colors.onSurface,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
@ -113,14 +156,14 @@ fun MobileHomeScreen(colors: AppThemes.Colors) {
|
|||||||
StyledButton( // Full width buttons for mobile quick actions
|
StyledButton( // Full width buttons for mobile quick actions
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
icon = Icons.Default.HourglassTop,
|
icon = Icons.Default.HourglassTop,
|
||||||
text = "Start Focus Session",
|
text = stringResource(MR.strings.start_focus),
|
||||||
onClick = { /* TODO */ },
|
onClick = { /* TODO */ },
|
||||||
colors = colors
|
colors = colors
|
||||||
)
|
)
|
||||||
StyledButton(
|
StyledButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
icon = Icons.Default.CalendarViewDay, // Changed icon
|
icon = Icons.Default.CalendarViewDay, // Changed icon
|
||||||
text = "View Today's Details",
|
text = stringResource(MR.strings.view_today),
|
||||||
onClick = { /* TODO */ },
|
onClick = { /* TODO */ },
|
||||||
colors = colors
|
colors = colors
|
||||||
)
|
)
|
||||||
@ -133,7 +176,7 @@ fun MobileHomeScreen(colors: AppThemes.Colors) {
|
|||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Recent Insights",
|
text = stringResource(MR.strings.recent_insights),
|
||||||
style = MaterialTheme.typography.titleMedium.copy(
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
color = colors.onSurface,
|
color = colors.onSurface,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
@ -141,13 +184,13 @@ fun MobileHomeScreen(colors: AppThemes.Colors) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
ActivityItem(
|
ActivityItem(
|
||||||
title = "Exceeded daily goal for App X.",
|
title = stringResource(MR.strings.exceeded_goal),
|
||||||
time = "Today, 2:30 PM",
|
time = "Today, 2:30 PM",
|
||||||
icon = Icons.Default.WarningAmber,
|
icon = Icons.Default.WarningAmber,
|
||||||
colors = colors
|
colors = colors
|
||||||
)
|
)
|
||||||
ActivityItem(
|
ActivityItem(
|
||||||
title = "Screen time 20% higher yesterday.",
|
title = stringResource(MR.strings.higher_yesterday),
|
||||||
time = "Insight from yesterday",
|
time = "Insight from yesterday",
|
||||||
icon = Icons.Default.TrendingUp,
|
icon = Icons.Default.TrendingUp,
|
||||||
colors = colors
|
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.BorderStroke
|
||||||
import androidx.compose.foundation.background
|
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.grtsinry43.activityanalyzer.components.SimpleListItem
|
import com.grtsinry43.chronosight.components.SimpleListItem
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledButton
|
import com.grtsinry43.chronosight.components.StyledButton
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledCard
|
import com.grtsinry43.chronosight.components.StyledCard
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.grtsinry43.activityanalyzer.screens
|
package com.grtsinry43.chronosight.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.grtsinry43.activityanalyzer.components.ReportItem
|
import com.grtsinry43.chronosight.components.ReportItem
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.grtsinry43.activityanalyzer.screens
|
package com.grtsinry43.chronosight.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.grtsinry43.activityanalyzer.components.SettingItem
|
import com.grtsinry43.chronosight.components.SettingItem
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MobileSettingsScreen(
|
fun MobileSettingsScreen(
|
||||||
colors: AppThemes.Colors,
|
colors: AppThemes.Colors,
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
onThemeChange: (Boolean) -> Unit
|
onThemeChange: (Boolean) -> Unit,
|
||||||
|
currentLocale: String,
|
||||||
|
onLocaleChange: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
var autoBackupEnabled by remember { mutableStateOf(true) }
|
var autoBackupEnabled by remember { mutableStateOf(true) }
|
||||||
var notificationsEnabled 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(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -34,6 +39,31 @@ fun MobileSettingsScreen(
|
|||||||
) {
|
) {
|
||||||
// General Group
|
// General Group
|
||||||
SettingsGroupHeader(title = "General", colors = colors)
|
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(
|
SettingItem(
|
||||||
title = "Dark Theme",
|
title = "Dark Theme",
|
||||||
subtitle = if (isDarkTheme) "Enabled" else "Disabled",
|
subtitle = if (isDarkTheme) "Enabled" else "Disabled",
|
||||||
@ -169,7 +199,13 @@ fun SettingsGroupHeader(title: String, colors: AppThemes.Colors) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun MobileSettingsScreenPreview() {
|
fun MobileSettingsScreenPreview() {
|
||||||
MaterialTheme {
|
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
|
@Composable
|
||||||
fun MobileSettingsScreenDarkPreview() {
|
fun MobileSettingsScreenDarkPreview() {
|
||||||
MaterialTheme {
|
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
|
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.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
@ -17,14 +17,14 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import com.grtsinry43.activityanalyzer.components.*
|
import com.grtsinry43.chronosight.components.*
|
||||||
import com.grtsinry43.activityanalyzer.screens.AboutScreen
|
import com.grtsinry43.chronosight.screens.AboutScreen
|
||||||
import com.grtsinry43.activityanalyzer.screens.AnalyticsScreen
|
import com.grtsinry43.chronosight.screens.AnalyticsScreen
|
||||||
import com.grtsinry43.activityanalyzer.screens.HomeScreen
|
import com.grtsinry43.chronosight.screens.HomeScreen
|
||||||
import com.grtsinry43.activityanalyzer.screens.ProfileScreen
|
import com.grtsinry43.chronosight.screens.ProfileScreen
|
||||||
import com.grtsinry43.activityanalyzer.screens.ReportsScreen
|
import com.grtsinry43.chronosight.screens.ReportsScreen
|
||||||
import com.grtsinry43.activityanalyzer.screens.SettingsScreen
|
import com.grtsinry43.chronosight.screens.SettingsScreen
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
@ -64,7 +64,7 @@ fun DesktopApp() {
|
|||||||
) {
|
) {
|
||||||
if (!isSidebarCollapsed) {
|
if (!isSidebarCollapsed) {
|
||||||
Text(
|
Text(
|
||||||
"Activity Analyzer", // 应用标题
|
"Chronosight", // 应用标题
|
||||||
fontSize = 18.sp, // 字体大小
|
fontSize = 18.sp, // 字体大小
|
||||||
fontWeight = FontWeight.SemiBold, // 字体粗细
|
fontWeight = FontWeight.SemiBold, // 字体粗细
|
||||||
color = currentColors.onSurface, // 文本颜色
|
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.background
|
||||||
import androidx.compose.foundation.clickable
|
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.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.grtsinry43.activityanalyzer
|
package com.grtsinry43.chronosight
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@ -50,7 +50,7 @@ fun main() = application {
|
|||||||
Tray(
|
Tray(
|
||||||
state = trayState,
|
state = trayState,
|
||||||
icon = trayIconPainter ?: BitmapPainter(createDefaultTrayIcon()), // 如果自定义图标加载失败,使用一个备用图标
|
icon = trayIconPainter ?: BitmapPainter(createDefaultTrayIcon()), // 如果自定义图标加载失败,使用一个备用图标
|
||||||
tooltip = "Activity Analyzer", // 鼠标悬停在托盘图标上时显示的提示文字
|
tooltip = "Chronosight", // 鼠标悬停在托盘图标上时显示的提示文字
|
||||||
menu = {
|
menu = {
|
||||||
// 托盘菜单项
|
// 托盘菜单项
|
||||||
Item(
|
Item(
|
||||||
@ -82,7 +82,7 @@ fun main() = application {
|
|||||||
// exitApplication()
|
// exitApplication()
|
||||||
},
|
},
|
||||||
state = windowState, // 应用窗口状态
|
state = windowState, // 应用窗口状态
|
||||||
title = "Activity Analyzer",
|
title = "Chronosight",
|
||||||
resizable = true, // 允许用户调整窗口大小 (默认为 true)
|
resizable = true, // 允许用户调整窗口大小 (默认为 true)
|
||||||
// icon = painterResource("app_icon.png") // 可选:设置窗口左上角的图标和任务栏图标
|
// 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.color = java.awt.Color.BLUE
|
||||||
g.fillRect(0, 0, width, height)
|
g.fillRect(0, 0, width, height)
|
||||||
g.color = java.awt.Color.WHITE
|
g.color = java.awt.Color.WHITE
|
||||||
g.drawString("AA", 20, 40) // "Activity Analyzer" 缩写
|
g.drawString("AA", 20, 40) // "Chronosight" 缩写
|
||||||
} finally {
|
} finally {
|
||||||
g.dispose()
|
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.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
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.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.grtsinry43.activityanalyzer.components.InfoItem
|
import com.grtsinry43.chronosight.components.InfoItem
|
||||||
import com.grtsinry43.activityanalyzer.components.SettingItem
|
import com.grtsinry43.chronosight.components.SettingItem
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledCard
|
import com.grtsinry43.chronosight.components.StyledCard
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -35,7 +35,7 @@ fun AboutScreen(colors: AppThemes.Colors) {
|
|||||||
modifier = Modifier.size(80.dp) // 图标大小
|
modifier = Modifier.size(80.dp) // 图标大小
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
"Activity Analyzer", // 应用名称
|
"Chronosight", // 应用名称
|
||||||
style = MaterialTheme.typography.headlineMedium.copy(
|
style = MaterialTheme.typography.headlineMedium.copy(
|
||||||
color = colors.onBackground,
|
color = colors.onBackground,
|
||||||
fontWeight = FontWeight.Bold
|
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.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
@ -12,11 +12,17 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.grtsinry43.activityanalyzer.components.ChartPlaceholder
|
import androidx.compose.ui.unit.sp
|
||||||
import com.grtsinry43.activityanalyzer.components.MetricCard
|
import com.grtsinry43.chronosight.components.ChartPlaceholder
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledButton
|
import com.grtsinry43.chronosight.components.MetricCard
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledCard
|
import com.grtsinry43.chronosight.components.StyledButton
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
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
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -84,36 +90,60 @@ fun AnalyticsScreen(colors: AppThemes.Colors) {
|
|||||||
// Key Metrics Cards
|
// Key Metrics Cards
|
||||||
// 关键指标卡片
|
// 关键指标卡片
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
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(
|
MetricCard(
|
||||||
title = "Total Screen Time",
|
title = "Total Screen Time",
|
||||||
value = "25h 10m", // 示例数据
|
value = formatMillisToHourMin(screenTimeMillis.value),
|
||||||
icon = Icons.Default.Smartphone,
|
icon = Icons.Default.Smartphone,
|
||||||
colors = colors,
|
colors = colors,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
) // 总屏幕时间
|
)
|
||||||
MetricCard(
|
MetricCard(
|
||||||
title = "Avg Daily Time",
|
title = "Avg Daily Time",
|
||||||
value = "3h 35m", // 示例数据
|
value = formatMillisToHourMin(screenTimeMillis.value),
|
||||||
icon = Icons.Default.AvTimer,
|
icon = Icons.Default.AvTimer,
|
||||||
colors = colors,
|
colors = colors,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
) // 平均每日时间
|
)
|
||||||
}
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
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(
|
MetricCard(
|
||||||
title = "Most Used App",
|
title = "Most Used App",
|
||||||
value = "App A (8h)", // 示例数据
|
value = mostUsed?.appName ?: "-",
|
||||||
icon = Icons.Default.StarOutline,
|
icon = Icons.Default.StarOutline,
|
||||||
colors = colors,
|
colors = colors,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
) // 最常用应用
|
)
|
||||||
MetricCard(
|
MetricCard(
|
||||||
title = "Pickups",
|
title = "Pickups",
|
||||||
value = "75 today", // 示例数据
|
value = "-",
|
||||||
icon = Icons.Default.TouchApp,
|
icon = Icons.Default.TouchApp,
|
||||||
colors = colors,
|
colors = colors,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
) // 拿起次数
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charts Section
|
// 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
|
// Analysis Tools
|
||||||
// 分析工具
|
// 分析工具
|
||||||
StyledCard(colors = colors) {
|
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.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
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.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.grtsinry43.activityanalyzer.components.ActivityItem
|
import com.grtsinry43.chronosight.components.ActivityItem
|
||||||
import com.grtsinry43.activityanalyzer.components.SimpleListItem
|
import com.grtsinry43.chronosight.components.SimpleListItem
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledButton
|
import com.grtsinry43.chronosight.components.StyledButton
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledCard
|
import com.grtsinry43.chronosight.components.StyledCard
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.getScreenTime
|
||||||
|
import com.grtsinry43.chronosight.formatMillisToHourMin
|
||||||
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -41,6 +43,14 @@ fun HomeScreen(colors: AppThemes.Colors) {
|
|||||||
// Overview of today's screen time
|
// Overview of today's screen time
|
||||||
// 今日屏幕时间概览
|
// 今日屏幕时间概览
|
||||||
StyledCard(colors = colors) {
|
StyledCard(colors = colors) {
|
||||||
|
// 动态获取屏幕时长,每秒刷新
|
||||||
|
val screenTimeMillis = remember { mutableStateOf(getScreenTime().getUsageMillis()) }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
while (true) {
|
||||||
|
screenTimeMillis.value = getScreenTime().getUsageMillis()
|
||||||
|
kotlinx.coroutines.delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
@ -53,7 +63,7 @@ fun HomeScreen(colors: AppThemes.Colors) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "3h 45m", // 示例数据
|
text = formatMillisToHourMin(screenTimeMillis.value),
|
||||||
style = MaterialTheme.typography.displaySmall.copy(
|
style = MaterialTheme.typography.displaySmall.copy(
|
||||||
color = colors.accent,
|
color = colors.accent,
|
||||||
fontWeight = FontWeight.Bold
|
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.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
@ -12,10 +12,10 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.grtsinry43.activityanalyzer.components.SimpleListItem
|
import com.grtsinry43.chronosight.components.SimpleListItem
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledButton
|
import com.grtsinry43.chronosight.components.StyledButton
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledCard
|
import com.grtsinry43.chronosight.components.StyledCard
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.grtsinry43.activityanalyzer.screens
|
package com.grtsinry43.chronosight.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
@ -11,10 +11,10 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.grtsinry43.activityanalyzer.components.ReportItem
|
import com.grtsinry43.chronosight.components.ReportItem
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledButton
|
import com.grtsinry43.chronosight.components.StyledButton
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledCard
|
import com.grtsinry43.chronosight.components.StyledCard
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.grtsinry43.activityanalyzer.screens
|
package com.grtsinry43.chronosight.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
@ -10,9 +10,9 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.grtsinry43.activityanalyzer.components.SettingItem
|
import com.grtsinry43.chronosight.components.SettingItem
|
||||||
import com.grtsinry43.activityanalyzer.components.StyledCard
|
import com.grtsinry43.chronosight.components.StyledCard
|
||||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
import com.grtsinry43.chronosight.theme.AppThemes
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -68,7 +68,7 @@ fun SettingsScreen(
|
|||||||
Divider(color = colors.border.copy(alpha = 0.3f)) // 分隔线
|
Divider(color = colors.border.copy(alpha = 0.3f)) // 分隔线
|
||||||
SettingItem( // 数据存储位置
|
SettingItem( // 数据存储位置
|
||||||
title = "Data Storage Location", // 数据存储位置
|
title = "Data Storage Location", // 数据存储位置
|
||||||
subtitle = "/Users/grtsinry43/Documents/ActivityAnalyzer", // 示例路径
|
subtitle = "/Users/grtsinry43/Documents/Chronosight", // 示例路径
|
||||||
icon = Icons.Default.FolderOpen, // 打开文件夹图标
|
icon = Icons.Default.FolderOpen, // 打开文件夹图标
|
||||||
colors = colors,
|
colors = colors,
|
||||||
onClick = { /* TODO: Open file dialog or path editor */ } // 打开文件对话框或路径编辑器
|
onClick = { /* TODO: Open file dialog or path editor */ } // 打开文件对话框或路径编辑器
|
||||||
@ -1,5 +1,5 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.5.2"
|
agp = "8.10.0"
|
||||||
android-compileSdk = "35"
|
android-compileSdk = "35"
|
||||||
android-minSdk = "24"
|
android-minSdk = "24"
|
||||||
android-targetSdk = "35"
|
android-targetSdk = "35"
|
||||||
@ -15,8 +15,9 @@ compose-multiplatform = "1.7.3"
|
|||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
kotlin = "2.1.20"
|
kotlin = "2.1.20"
|
||||||
kotlinx-coroutines = "1.10.1"
|
kotlinx-coroutines = "1.10.1"
|
||||||
kotlinxSerializationJson = "1.8.0"
|
kotlinxSerializationJson = "1.8.1"
|
||||||
ktorClientCore = "3.1.1"
|
ktorClientCore = "3.1.3"
|
||||||
|
mokoResources = "0.24.5"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
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-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorClientCore" }
|
||||||
ktor-client-ios = { module = "io.ktor:ktor-client-ios", 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" }
|
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]
|
[plugins]
|
||||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
androidLibrary = { id = "com.android.library", version.ref = "agp" }
|
androidLibrary = { id = "com.android.library", version.ref = "agp" }
|
||||||
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
|
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
|
||||||
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
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
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
TEAM_ID=
|
TEAM_ID=
|
||||||
BUNDLE_ID=com.grtsinry43.activityanalyzer.ActivityAnalyzer
|
BUNDLE_ID=com.grtsinry43.chronosight.Chronosight
|
||||||
APP_NAME=Activity Analyzer
|
APP_NAME=Chronosight
|
||||||
@ -18,7 +18,7 @@
|
|||||||
058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
||||||
@ -64,7 +64,7 @@
|
|||||||
7555FF7C242A565900829871 /* Products */ = {
|
7555FF7C242A565900829871 /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7555FF7B242A565900829871 /* Activity Analyzer.app */,
|
7555FF7B242A565900829871 /* Chronosight.app */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -110,7 +110,7 @@
|
|||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
);
|
);
|
||||||
productName = iosApp;
|
productName = iosApp;
|
||||||
productReference = 7555FF7B242A565900829871 /* Activity Analyzer.app */;
|
productReference = 7555FF7B242A565900829871 /* Chronosight.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
rootProject.name = "ActivityAnalyzer"
|
rootProject.name = "Chronosight"
|
||||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||||
|
|
||||||
pluginManagement {
|
pluginManagement {
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
|
import dev.icerock.gradle.MRVisibility
|
||||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
import dev.icerock.gradle.MultiplatformResourcesPluginExtension // Required for resourcesPackage configuration
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.kotlinMultiplatform)
|
alias(libs.plugins.kotlinMultiplatform)
|
||||||
alias(libs.plugins.androidLibrary)
|
alias(libs.plugins.androidLibrary)
|
||||||
|
|
||||||
kotlin("plugin.serialization") version libs.versions.kotlin.get()
|
kotlin("plugin.serialization") version libs.versions.kotlin.get()
|
||||||
|
alias(libs.plugins.mokoMultiplatformResources) // Use alias from version catalog
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
@ -15,7 +17,7 @@ kotlin {
|
|||||||
jvmTarget.set(JvmTarget.JVM_11)
|
jvmTarget.set(JvmTarget.JVM_11)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
listOf(
|
listOf(
|
||||||
iosX64(),
|
iosX64(),
|
||||||
iosArm64(),
|
iosArm64(),
|
||||||
@ -26,16 +28,19 @@ kotlin {
|
|||||||
isStatic = true
|
isStatic = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jvm()
|
jvm()
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain.dependencies {
|
val commonMain by getting {
|
||||||
// put your Multiplatform dependencies here
|
resources.srcDirs("src/commonMain/resources")
|
||||||
implementation(libs.ktor.client.core)
|
dependencies {
|
||||||
implementation(libs.ktor.client.content.negotiation)
|
implementation(libs.ktor.client.core)
|
||||||
implementation(libs.ktor.serialization.kotlinx.json)
|
implementation(libs.ktor.client.content.negotiation)
|
||||||
implementation(libs.kotlinx.serialization.json)
|
implementation(libs.ktor.serialization.kotlinx.json)
|
||||||
|
implementation(libs.kotlinx.serialization.json)
|
||||||
|
implementation(libs.moko.resources)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
androidMain.dependencies {
|
androidMain.dependencies {
|
||||||
implementation(libs.ktor.client.android)
|
implementation(libs.ktor.client.android)
|
||||||
@ -47,7 +52,7 @@ kotlin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.grtsinry43.activityanalyzer.shared"
|
namespace = "com.grtsinry43.chronosight.shared"
|
||||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
@ -57,3 +62,20 @@ android {
|
|||||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
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.call.body
|
||||||
import io.ktor.client.request.get
|
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.HttpClient
|
||||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
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
|
* @author grtsinry43
|
||||||
@ -6,7 +6,8 @@ package com.grtsinry43.activityanalyzer
|
|||||||
* @description 热爱可抵岁月漫长
|
* @description 热爱可抵岁月漫长
|
||||||
*/
|
*/
|
||||||
interface ScreenTime {
|
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
|
app.title=Chronosight
|
||||||
home.welcome=Welcome to Activity Analyzer
|
home.welcome=Welcome to Chronosight
|
||||||
home.quickActions=Quick Actions
|
home.quickActions=Quick Actions
|
||||||
home.newAnalysis=New Analysis
|
home.newAnalysis=New Analysis
|
||||||
home.importData=Import Data
|
home.importData=Import Data
|
||||||
@ -16,7 +16,7 @@ settings.title=Settings
|
|||||||
settings.dataStorage=Data Storage Location
|
settings.dataStorage=Data Storage Location
|
||||||
settings.autoBackup=Auto Backup
|
settings.autoBackup=Auto Backup
|
||||||
settings.notifications=Notifications
|
settings.notifications=Notifications
|
||||||
about.title=About Activity Analyzer
|
about.title=About Chronosight
|
||||||
about.version=Version {0}
|
about.version=Version {0}
|
||||||
about.description=A powerful activity analysis tool to help you better understand and optimize your activity data.
|
about.description=A powerful activity analysis tool to help you better understand and optimize your activity data.
|
||||||
about.checkUpdate=Check for Updates
|
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
|
import platform.UIKit.UIDevice
|
||||||
|
|
||||||
class IOSPlatform: Platform {
|
class IOSPlatform: Platform {
|
||||||
override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
|
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
|
app.title=Chronosight
|
||||||
home.welcome=Welcome to Activity Analyzer
|
home.welcome=Welcome to Chronosight
|
||||||
home.quickActions=Quick Actions
|
home.quickActions=Quick Actions
|
||||||
home.newAnalysis=New Analysis
|
home.newAnalysis=New Analysis
|
||||||
home.importData=Import Data
|
home.importData=Import Data
|
||||||
@ -16,7 +16,7 @@ settings.title=Settings
|
|||||||
settings.dataStorage=Data Storage Location
|
settings.dataStorage=Data Storage Location
|
||||||
settings.autoBackup=Auto Backup
|
settings.autoBackup=Auto Backup
|
||||||
settings.notifications=Notifications
|
settings.notifications=Notifications
|
||||||
about.title=About Activity Analyzer
|
about.title=About Chronosight
|
||||||
about.version=Version {0}
|
about.version=Version {0}
|
||||||
about.description=A powerful activity analysis tool to help you better understand and optimize your activity data.
|
about.description=A powerful activity analysis tool to help you better understand and optimize your activity data.
|
||||||
about.checkUpdate=Check for Updates
|
about.checkUpdate=Check for Updates
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.grtsinry43.activityanalyzer
|
package com.grtsinry43.chronosight
|
||||||
|
|
||||||
actual fun getScreenTime(): ScreenTime {
|
actual fun getScreenTime(): ScreenTime {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
Loading…
x
Reference in New Issue
Block a user