Dev (#1)
Some checks failed
KMP Build & Package / Build Linux Native on ubuntu-latest (push) Has been cancelled
KMP Build & Package / Build macOS Native on macos-latest (push) Has been cancelled
KMP Build & Package / Build Windows Native on windows-latest (push) Has been cancelled

This commit is contained in:
grtsinry43 2025-05-12 09:14:19 +08:00 committed by GitHub
parent 9550889bdf
commit dad89bcc81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 3627 additions and 688 deletions

View File

@ -1,46 +1,159 @@
name: Build and Release
name: KMP Build & Package
on:
push:
branches: [ main, dev ] # 在推送到 main 或 develop 分支时触发
tags:
- 'v*.*.*' # 仅在推送符合语义化版本的标签时触发
- 'v*' # 在推送版本标签时触发 (例如 v1.0.0)
pull_request:
branches: [ main, dev ] # 在向 main 或 develop 分支发起 Pull Request 时触发
workflow_dispatch: # 允许手动触发
jobs:
build:
name: Build All Targets
runs-on: ubuntu-latest
strategy:
fail-fast: false # 即使一个 job 失败,其他 job 也会继续运行
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ] # 根据你的目标平台选择操作系统
include:
# Android 构建 (通常在 Linux 环境下进行)
- os: ubuntu-latest
target: android
description: "Build Android App"
gradle_task: ":shared:assembleRelease :androidApp:assembleRelease" # 替换成你的 Android 打包任务
artifact_name: "android-app"
artifact_path: |
shared/build/outputs/aar/*.aar
androidApp/build/outputs/apk/release/*.apk
androidApp/build/outputs/bundle/release/*.aab
# iOS 构建 (必须在 macOS 环境下进行)
- os: macos-latest
target: ios
description: "Build iOS Framework"
# 注意: iOS 构建可能需要特定的 Xcode 版本,可以使用 actions/setup-xcode
# gradle_task: ":shared:packForXCFramework" # 常见的 iOS framework 打包任务
gradle_task: ":shared:assembleXCFramework" # 或者使用新的 Kotlin 插件的任务名
artifact_name: "ios-framework"
artifact_path: "shared/build/XCFrameworks/release/*.xcframework" # 检查你的实际输出路径
# JVM/Desktop 构建 (可以在多个操作系统上运行)
- os: ubuntu-latest # 为 Linux 构建
target: jvm_linux
description: "Build JVM/Desktop (Linux)"
gradle_task: ":desktopApp:packageDistributionForCurrentOS" # 替换成你的 Desktop 打包任务
artifact_name: "desktop-app-linux"
artifact_path: "desktopApp/build/compose/binaries/main/app/*" # 检查你的实际输出路径
- os: macos-latest # 为 macOS 构建
target: jvm_macos
description: "Build JVM/Desktop (macOS)"
gradle_task: ":desktopApp:packageDistributionForCurrentOS"
artifact_name: "desktop-app-macos"
artifact_path: "desktopApp/build/compose/binaries/main/app/*" # 检查你的实际输出路径
- os: windows-latest # 为 Windows 构建
target: jvm_windows
description: "Build JVM/Desktop (Windows)"
gradle_task: ":desktopApp:packageDistributionForCurrentOS"
artifact_name: "desktop-app-windows"
artifact_path: "desktopApp/build/compose/binaries/main/app/*" # 检查你的实际输出路径
# JavaScript 构建 (通常在 Linux 环境下进行)
- os: ubuntu-latest
target: js
description: "Build JavaScript"
gradle_task: ":shared:jsBrowserDistribution" # 替换成你的 JS 打包任务
artifact_name: "js-app"
artifact_path: "shared/build/distributions/*" # 检查你的实际输出路径
# Linux Native 构建
- os: ubuntu-latest
target: linux_native
description: "Build Linux Native"
gradle_task: ":shared:linkReleaseExecutableLinuxX64" # 替换成你的 Linux Native 编译任务
artifact_name: "linux-native-executable"
artifact_path: "shared/build/bin/linuxX64/releaseExecutable/*.kexe" # 检查你的实际输出路径
# macOS Native 构建 (也可以针对 arm64)
- os: macos-latest
target: macos_native
description: "Build macOS Native"
gradle_task: ":shared:linkReleaseExecutableMacosX64" # 或者 linkReleaseExecutableMacosArm64
artifact_name: "macos-native-executable"
artifact_path: "shared/build/bin/macosX64/releaseExecutable/*.kexe" # 检查你的实际输出路径
# Windows Native 构建
- os: windows-latest
target: windows_native
description: "Build Windows Native"
gradle_task: ":shared:linkReleaseExecutableMingwX64" # 替换成你的 Windows Native 编译任务
artifact_name: "windows-native-executable"
artifact_path: "shared/build/bin/mingwX64/releaseExecutable/*.exe" # 检查你的实际输出路径
# 如果某些平台不需要在所有操作系统上构建,可以在这里排除
# exclude:
# - os: windows-latest
# target: android
# - os: ubuntu-latest
# target: ios
name: ${{ matrix.description }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
steps:
# 检出代码
- name: Checkout code
uses: actions/checkout@v3
# 设置 JDK 环境
- name: Set up JDK
uses: actions/setup-java@v3
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up JDK 17 # KMP 通常建议使用较新的 JDK 版本
uses: actions/setup-java@v4
with:
distribution: 'zulu'
distribution: 'temurin' # 或者 'zulu', 'adopt' 等
java-version: '17'
# 构建 Android APK
- name: Build Android APK
run: ./gradlew :androidApp:assembleRelease
# 构建 iOS Framework
- name: Build iOS Framework
run: ./gradlew :iosApp:build
# 构建 Desktop 可执行文件
- name: Build Desktop Executable
run: ./gradlew :desktopApp:packageRelease
# 上传构建产物到 GitHub Releases
- name: Upload Release Assets
uses: actions/upload-release-asset@v2
# 仅在 macOS runner 上为 iOS 构建设置 Xcode 版本 (如果需要特定版本)
- name: Select Xcode version (for iOS builds)
if: matrix.os == 'macos-latest' && matrix.target == 'ios'
run: sudo xcode-select -s /Applications/Xcode_15.3.app/Contents/Developer # 替换成你需要的 Xcode 版本路径
# 你也可以使用 action 如 maxim-lobanov/setup-xcode@v1
# 设置 Gradle (会自动处理 Gradle Wrapper)
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3 # 使用 v3 版本
with:
gradle-version: wrapper # 默认使用 Gradle Wrapper
# cache-read-only: ${{ github.ref != 'refs/heads/main' }} # 非 main 分支构建时,缓存只读 (可选)
# cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} # 加密缓存 (可选)
# Gradle 缓存 (KMP 项目的 .konan 目录和 Gradle 缓存很重要)
- name: Cache Gradle packages
uses: actions/cache@v4
with:
name: ${{github.ref_name}}-build
path: |
androidApp/build/outputs/apk/release/*.apk
iosApp/build/bin/ios/*.framework
desktopApp/build/outputs/*.exe
label: "Build for ${{ github.ref_name }}"
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Cache Kotlin Native ($HOME/.konan)
if: matrix.os == 'macos-latest' || matrix.os == 'ubuntu-latest' # .konan 缓存主要用于 Native 编译
uses: actions/cache@v4
with:
path: ~/.konan
key: ${{ runner.os }}-konan-${{ hashFiles('**/build.gradle.kts', '**/gradle.properties') }} # 或者更精确的 hash
restore-keys: |
${{ runner.os }}-konan-
# 授予 Gradle Wrapper 执行权限 (特别是对于 Windows)
- name: Make gradlew executable
if: runner.os != 'windows-latest'
run: chmod +x ./gradlew
- name: Make gradlew.bat executable (Windows)
if: runner.os == 'windows-latest'
run: cmd /c "chmod +x gradlew.bat" # 在 Windows 上,通常 gradlew.bat 默认就有执行权限,此步骤可能非必需
# 执行构建任务
- name: Build with Gradle
run: ./gradlew ${{ matrix.gradle_task }} --no-daemon # --no-daemon 可以在 CI 环境中更稳定
# 上传构建产物
# 注意: actions/upload-artifact@v4 的用法与 v3 不同
- name: Upload Artifact - ${{ matrix.artifact_name }}
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: ${{ matrix.artifact_path }}
if-no-files-found: error # 如果没有找到文件则报错

View File

@ -3,10 +3,12 @@ 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)
@ -16,6 +18,7 @@ class MainActivity : ComponentActivity() {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun AppAndroidPreview() {

View File

@ -1,44 +1,192 @@
package com.grtsinry43.activityanalyzer
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
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 org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import activityanalyzer.composeapp.generated.resources.Res
import activityanalyzer.composeapp.generated.resources.compose_multiplatform
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
@Preview
@ExperimentalMaterial3Api
fun App() {
MaterialTheme {
var showContent by remember { mutableStateOf(false) }
var greetingText by remember { mutableStateOf("加载中...") }
var isDarkTheme by remember { mutableStateOf(false) }
val currentColors = if (isDarkTheme) AppThemes.DarkThemeColors else AppThemes.LightThemeColors
LaunchedEffect(key1 = Unit) {
greetingText = try {
Greeting().greet()
} catch (e: Exception) {
"错误:${e.message}"
}
}
// Navigation state
var selectedNavItemIndex by remember { mutableStateOf(0) }
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Button(onClick = { showContent = !showContent }) {
Text("Click me!")
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)
)
)
}
}
}
AnimatedVisibility(showContent) {
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
Image(painterResource(Res.drawable.compose_multiplatform), null)
Text("Compose: $greetingText")
) {
Scaffold(
containerColor = currentColors.background,
topBar = {
TopAppBar(
title = { Text(currentScreenTitle, color = currentColors.onSurface) },
navigationIcon = {
IconButton(onClick = { scope.launch { drawerState.open() } }) {
Icon(
Icons.Filled.Menu,
contentDescription = "Open Navigation Drawer",
tint = currentColors.onSurface
)
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = currentColors.surface,
titleContentColor = currentColors.onSurface
)
)
}
) { paddingValues ->
Box(
modifier = Modifier.padding(paddingValues).fillMaxSize()
.background(currentColors.background)
) {
when (selectedNavItemIndex) {
0 -> MobileHomeScreen(colors = currentColors)
1 -> MobileAnalyticsScreen(colors = currentColors)
2 -> MobileReportsScreen(colors = currentColors)
3 -> MobileProfileScreen(colors = currentColors)
4 -> MobileSettingsScreen(
colors = currentColors,
isDarkTheme = isDarkTheme,
onThemeChange = { isDarkTheme = it }
)
5 -> MobileAboutScreen(colors = currentColors)
}
}
}
}
}
}
}

View File

@ -0,0 +1,81 @@
package com.grtsinry43.activityanalyzer.components
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun ActivityItem(title: String, time: String, icon: ImageVector, colors: AppThemes.Colors) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(10.dp))
.background(colors.surface.copy(alpha = 0.5f))
.border(1.dp, colors.border.copy(alpha = 0.3f), RoundedCornerShape(10.dp))
.padding(horizontal = 16.dp, vertical = 12.dp), // Adjusted padding for mobile
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = colors.accent,
modifier = Modifier.size(24.dp) // Slightly larger icon for mobile list items
)
Column(modifier = Modifier.weight(1f)) {
Text(
text = title,
fontSize = 15.sp,
color = colors.onSurface,
fontWeight = FontWeight.Normal // Adjusted weight
)
Text(
text = time,
fontSize = 13.sp,
color = colors.secondaryText
)
}
}
}
@Preview
@Composable
fun ActivityItemPreview() {
MaterialTheme {
ActivityItem(
title = "Sample Activity",
time = "10:00 AM",
icon = Icons.Default.Info,
colors = AppThemes.LightThemeColors
)
}
}
@Preview
@Composable
fun ActivityItemDarkPreview() {
MaterialTheme {
ActivityItem(
title = "Sample Activity (Dark)",
time = "10:00 AM",
icon = Icons.Default.Info,
colors = AppThemes.DarkThemeColors
)
}
}

View File

@ -0,0 +1,60 @@
package com.grtsinry43.activityanalyzer.components
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun ChartPlaceholder(text: String, colors: AppThemes.Colors, modifier: Modifier = Modifier) {
Box(
modifier = modifier
.background(colors.background.copy(alpha = 0.5f), RoundedCornerShape(10.dp))
.border(1.dp, colors.border.copy(alpha = 0.7f), RoundedCornerShape(10.dp))
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = text,
color = colors.secondaryText,
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center
)
}
}
@Preview
@Composable
fun ChartPlaceholderPreview() {
MaterialTheme {
ChartPlaceholder(
text = "Sample Chart Placeholder",
colors = AppThemes.LightThemeColors,
modifier = Modifier.fillMaxWidth().height(200.dp)
)
}
}
@Preview
@Composable
fun ChartPlaceholderDarkPreview() {
MaterialTheme {
ChartPlaceholder(
text = "Sample Chart Placeholder (Dark)",
colors = AppThemes.DarkThemeColors,
modifier = Modifier.fillMaxWidth().height(200.dp)
)
}
}

View File

@ -0,0 +1,95 @@
package com.grtsinry43.activityanalyzer.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun InfoItem(
icon: ImageVector,
text: String,
colors: AppThemes.Colors,
isLink: Boolean = false,
linkUrl: String? = null, // Not used in preview, but kept for component signature
onClick: (() -> Unit)? = null
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.clickable(enabled = onClick != null || (isLink && linkUrl != null), onClick = {
onClick?.invoke()
// if (isLink && linkUrl != null) { /* uriHandler.openUri(linkUrl) */ } // Link opening logic removed for preview
})
.padding(vertical = 12.dp, horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = colors.accentVariant,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = text,
fontSize = 15.sp,
color = if (isLink) colors.accent else colors.onSurface,
fontWeight = FontWeight.Normal
)
}
}
@Preview
@Composable
fun InfoItemPreview() {
MaterialTheme {
InfoItem(
icon = Icons.Default.Info,
text = "This is an informational item.",
colors = AppThemes.LightThemeColors
)
}
}
@Preview
@Composable
fun InfoItemLinkPreview() {
MaterialTheme {
InfoItem(
icon = Icons.Default.Info,
text = "This is a clickable link item.",
colors = AppThemes.LightThemeColors,
isLink = true,
linkUrl = "https://example.com",
onClick = {}
)
}
}
@Preview
@Composable
fun InfoItemDarkPreview() {
MaterialTheme {
InfoItem(
icon = Icons.Default.Info,
text = "This is an informational item (Dark).",
colors = AppThemes.DarkThemeColors
)
}
}

View File

@ -0,0 +1,80 @@
package com.grtsinry43.activityanalyzer.components
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun MetricCard(
title: String,
value: String,
icon: ImageVector,
colors: AppThemes.Colors,
modifier: Modifier = Modifier
) {
StyledCard(colors = colors, modifier = modifier) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(6.dp) // Tighter spacing for mobile
) {
Icon(
imageVector = icon,
contentDescription = title,
tint = colors.accent,
modifier = Modifier.size(28.dp)
)
Text(
text = title,
style = MaterialTheme.typography.labelMedium.copy(color = colors.secondaryText),
textAlign = TextAlign.Center
)
Text(
text = value,
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.Bold
),
textAlign = TextAlign.Center
)
}
}
}
@Preview
@Composable
fun MetricCardPreview() {
MaterialTheme {
MetricCard(
title = "Total Users",
value = "1,234",
icon = Icons.Default.Star,
colors = AppThemes.LightThemeColors
)
}
}
@Preview
@Composable
fun MetricCardDarkPreview() {
MaterialTheme {
MetricCard(
title = "Active Sessions (Dark)",
value = "56",
icon = Icons.Default.Star, // Placeholder
colors = AppThemes.DarkThemeColors
)
}
}

View File

@ -0,0 +1,98 @@
package com.grtsinry43.activityanalyzer.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun ReportItem(
title: String,
period: String,
icon: ImageVector,
colors: AppThemes.Colors,
onDownload: () -> Unit,
onView: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(10.dp))
.clickable { onView() } // Make the whole item clickable for viewing
.padding(vertical = 12.dp, horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = colors.accent,
modifier = Modifier.size(32.dp) // Larger icon for report items
)
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text = title,
fontSize = 16.sp, // Slightly larger title
color = colors.onSurface,
fontWeight = FontWeight.Medium
)
Text(
text = period,
fontSize = 13.sp,
color = colors.secondaryText
)
}
Spacer(modifier = Modifier.width(8.dp))
IconButton(onClick = onDownload, modifier = Modifier.size(40.dp)) {
Icon(
imageVector = Icons.Default.Download,
contentDescription = "Download Report",
tint = colors.accentVariant
)
}
}
}
@Preview
@Composable
fun ReportItemPreview() {
MaterialTheme {
ReportItem(
title = "Weekly Summary",
period = "May 5 - May 11",
icon = Icons.Default.Edit, // Changed to avoid conflict with other previews
colors = AppThemes.LightThemeColors,
onDownload = {},
onView = {}
)
}
}
@Preview
@Composable
fun ReportItemDarkPreview() {
MaterialTheme {
ReportItem(
title = "Monthly Summary (Dark)",
period = "April 2024",
icon = Icons.Default.Visibility, // Changed to avoid conflict
colors = AppThemes.DarkThemeColors,
onDownload = {},
onView = {}
)
}
}

View File

@ -0,0 +1,136 @@
package com.grtsinry43.activityanalyzer.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun SettingItem(
title: String,
subtitle: String,
icon: ImageVector,
colors: AppThemes.Colors,
onClick: (() -> Unit)? = null,
showSwitch: Boolean = false,
switchChecked: Boolean = false,
onSwitchChange: ((Boolean) -> Unit)? = null
) {
val itemModifier = if (onClick != null || showSwitch) {
Modifier.clickable {
if (showSwitch && onSwitchChange != null) {
onSwitchChange(!switchChecked)
} else {
onClick?.invoke()
}
}
} else {
Modifier
}
Row(
modifier = Modifier
.fillMaxWidth()
.then(itemModifier) // Apply clickable if needed
.padding(horizontal = 16.dp, vertical = 14.dp), // Standard padding for list items
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = colors.accentVariant,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text = title,
fontSize = 16.sp, // Standard mobile list item title size
color = colors.onSurface,
fontWeight = FontWeight.Normal
)
if (subtitle.isNotEmpty()) {
Text(
text = subtitle,
fontSize = 13.sp,
color = colors.secondaryText
)
}
}
Spacer(modifier = Modifier.width(8.dp))
if (showSwitch && onSwitchChange != null) {
Switch(
checked = switchChecked,
onCheckedChange = null, // Click handled by Row
colors = SwitchDefaults.colors(
checkedThumbColor = colors.accent,
checkedTrackColor = colors.accent.copy(alpha = 0.5f),
uncheckedThumbColor = colors.secondaryText,
uncheckedTrackColor = colors.border.copy(alpha = 0.5f)
),
modifier = Modifier.size(40.dp)
)
} else if (onClick != null && !showSwitch) {
Icon(
imageVector = Icons.Default.ChevronRight,
contentDescription = "Go to setting",
tint = colors.secondaryText.copy(alpha = 0.7f)
)
}
}
}
@Preview
@Composable
fun SettingItemPreview() {
MaterialTheme {
SettingItem(
title = "Display Settings",
subtitle = "Configure screen brightness and theme",
icon = Icons.Default.Settings,
colors = AppThemes.LightThemeColors,
onClick = {}
)
}
}
@Preview
@Composable
fun SettingItemWithSwitchPreview() {
MaterialTheme {
SettingItem(
title = "Enable Notifications",
subtitle = "Receive updates and alerts",
icon = Icons.Default.Settings, // Placeholder
colors = AppThemes.LightThemeColors,
showSwitch = true,
switchChecked = true,
onSwitchChange = {}
)
}
}
@Preview
@Composable
fun SettingItemDarkPreview() {
MaterialTheme {
SettingItem(
title = "Storage Settings (Dark)",
subtitle = "Manage local and cloud storage",
icon = Icons.Default.Settings, // Placeholder
colors = AppThemes.DarkThemeColors,
onClick = {}
)
}
}

View File

@ -0,0 +1,71 @@
package com.grtsinry43.activityanalyzer.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.List
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun SimpleListItem(
icon: ImageVector,
text: String,
colors: AppThemes.Colors,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier.padding(vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = colors.accentVariant,
modifier = Modifier.size(22.dp)
)
Text(
text,
style = MaterialTheme.typography.bodyLarge.copy(
color = colors.onSurface,
fontSize = 15.sp
)
)
}
}
@Preview
@Composable
fun SimpleListItemPreview() {
MaterialTheme {
SimpleListItem(
icon = Icons.Default.List,
text = "Sample List Item",
colors = AppThemes.LightThemeColors
)
}
}
@Preview
@Composable
fun SimpleListItemDarkPreview() {
MaterialTheme {
SimpleListItem(
icon = Icons.Default.List,
text = "Sample List Item (Dark)",
colors = AppThemes.DarkThemeColors
)
}
}

View File

@ -0,0 +1,72 @@
package com.grtsinry43.activityanalyzer.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun StyledButton(
modifier: Modifier = Modifier,
icon: ImageVector,
text: String,
onClick: () -> Unit,
colors: AppThemes.Colors,
enabled: Boolean = true
) {
Button(
onClick = onClick,
modifier = modifier.height(48.dp), // Consistent height for mobile buttons
enabled = enabled,
shape = RoundedCornerShape(10.dp),
colors = ButtonDefaults.buttonColors(
containerColor = colors.accent.copy(alpha = 0.15f), // Slightly more opaque for visibility
contentColor = colors.accent,
disabledContainerColor = colors.border.copy(alpha = 0.1f),
disabledContentColor = colors.secondaryText.copy(alpha = 0.7f)
),
border = BorderStroke(1.dp, colors.accent.copy(alpha = 0.4f)),
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 10.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(imageVector = icon, contentDescription = null, modifier = Modifier.size(20.dp))
Text(text = text, fontSize = 14.sp, fontWeight = FontWeight.Medium)
}
}
}
@Preview
@Composable
fun StyledButtonPreview() {
StyledButton(
icon = Icons.Default.Favorite,
text = "Preview Button",
onClick = {},
colors = AppThemes.LightThemeColors
)
}
@Preview
@Composable
fun StyledButtonDarkPreview() {
StyledButton(
icon = Icons.Default.Favorite,
text = "Preview Button Dark",
onClick = {},
colors = AppThemes.DarkThemeColors
)
}

View File

@ -0,0 +1,45 @@
package com.grtsinry43.activityanalyzer.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun StyledCard(
modifier: Modifier = Modifier,
colors: AppThemes.Colors,
content: @Composable ColumnScope.() -> Unit
) {
Card(
modifier = modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp)), // Slightly more rounded for mobile
colors = CardDefaults.cardColors(
containerColor = colors.surface
),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp), // Slightly more elevation for mobile
border = BorderStroke(1.dp, colors.border.copy(alpha = 0.4f)),
content = content
)
}
@Preview
@Composable
fun StyledCardPreview() {
StyledCard(
colors = AppThemes.LightThemeColors
) {
// Preview content
Text("123")
}
}

View File

@ -0,0 +1,165 @@
package com.grtsinry43.activityanalyzer.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.grtsinry43.activityanalyzer.components.InfoItem
import com.grtsinry43.activityanalyzer.components.SettingItem
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun MobileAboutScreen(colors: AppThemes.Colors) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Filled.Analytics, // App Icon
contentDescription = "App Logo",
tint = colors.accent,
modifier = Modifier.size(72.dp) // Slightly smaller for mobile about screen
)
Text(
"Activity Analyzer",
style = MaterialTheme.typography.headlineMedium.copy(
color = colors.onBackground,
fontWeight = FontWeight.Bold,
fontSize = 24.sp
)
)
Text(
"Version 1.0.0-beta",
style = MaterialTheme.typography.titleMedium.copy(
color = colors.secondaryText,
fontSize = 16.sp
)
)
Text(
"Your personal screen time companion, helping you understand and manage your digital habits across platforms.",
style = MaterialTheme.typography.bodyLarge.copy(
color = colors.onSurface,
fontSize = 15.sp
),
textAlign = TextAlign.Center,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
)
// Developer Info Section
Text(
"Developer",
style = MaterialTheme.typography.titleMedium.copy(
color = colors.accent,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp
),
modifier = Modifier.align(Alignment.Start).padding(top = 8.dp)
)
InfoItem(icon = Icons.Filled.Person, text = "grtsinry43", colors = colors, onClick = {})
Divider(color = colors.border.copy(alpha = 0.2f))
InfoItem(
icon = Icons.Filled.Email,
text = "grtsinry43@outlook.com",
colors = colors,
isLink = true,
linkUrl = "mailto:grtsinry43@outlook.com",
onClick = {})
Divider(color = colors.border.copy(alpha = 0.2f))
InfoItem(
icon = Icons.Filled.Language,
text = "blog.grtsinry43.com",
colors = colors,
isLink = true,
linkUrl = "https://blog.grtsinry43.com",
onClick = {})
Divider(color = colors.border.copy(alpha = 0.2f))
InfoItem(
icon = Icons.Filled.Code,
text = "github.com/grtsinry43",
colors = colors,
isLink = true,
linkUrl = "https://github.com/grtsinry43",
onClick = {})
// Application Info Section
Text(
"Application Info",
style = MaterialTheme.typography.titleMedium.copy(
color = colors.accent,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp
),
modifier = Modifier.align(Alignment.Start).padding(top = 16.dp)
)
SettingItem(
title = "Check for Updates",
subtitle = "Last checked: Today",
icon = Icons.Default.SystemUpdateAlt,
colors = colors,
onClick = {})
Divider(
color = colors.border.copy(alpha = 0.2f),
modifier = Modifier.padding(horizontal = 16.dp)
) // Use full width divider for settings like items
SettingItem(
title = "Acknowledgements",
subtitle = "Libraries and resources",
icon = Icons.Default.FavoriteBorder,
colors = colors,
onClick = {})
Divider(
color = colors.border.copy(alpha = 0.2f),
modifier = Modifier.padding(horizontal = 16.dp)
)
SettingItem(
title = "License Information",
subtitle = "View license",
icon = Icons.Default.Gavel,
colors = colors,
onClick = {})
Spacer(modifier = Modifier.weight(1f))
Text(
"© 2025 grtsinry43. All rights reserved.",
style = MaterialTheme.typography.bodySmall.copy(
color = colors.secondaryText,
fontSize = 12.sp
),
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 8.dp, top = 16.dp)
)
}
}
@Preview
@Composable
fun MobileAboutScreenPreview() {
MaterialTheme {
MobileAboutScreen(colors = AppThemes.LightThemeColors)
}
}
@Preview
@Composable
fun MobileAboutScreenDarkPreview() {
MaterialTheme {
MobileAboutScreen(colors = AppThemes.DarkThemeColors)
}
}

View File

@ -0,0 +1,191 @@
package com.grtsinry43.activityanalyzer.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.grtsinry43.activityanalyzer.components.ChartPlaceholder
import com.grtsinry43.activityanalyzer.components.MetricCard
import com.grtsinry43.activityanalyzer.components.StyledButton
import com.grtsinry43.activityanalyzer.components.StyledCard
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun MobileAnalyticsScreen(colors: AppThemes.Colors) {
var selectedTimeRange by remember { mutableStateOf("Last 7 Days") }
val timeRanges =
listOf("Today", "Yesterday", "Last 7 Days", "Last 30 Days") // Simplified for mobile
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
"Screen Time Analytics",
style = MaterialTheme.typography.headlineSmall.copy(
color = colors.onBackground,
fontWeight = FontWeight.Bold,
fontSize = 22.sp
)
)
var expanded by remember { mutableStateOf(false) }
Box(modifier = Modifier.fillMaxWidth()) { // Make dropdown full width for better mobile UX
OutlinedButton(
onClick = { expanded = true },
shape = MaterialTheme.shapes.medium,
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(16.dp)
) {
Text(selectedTimeRange, color = colors.accent, fontSize = 16.sp)
Spacer(Modifier.weight(1f))
Icon(
Icons.Default.ArrowDropDown,
contentDescription = "Select time range",
tint = colors.accent
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier.fillMaxWidth(0.9f) // Adjust width as needed
) {
timeRanges.forEach { range ->
DropdownMenuItem(
text = { Text(range, fontSize = 16.sp) },
onClick = {
selectedTimeRange = range
expanded = false
// TODO: Update analytics data
}
)
}
}
}
// Key Metrics - Use a Grid for better mobile layout if more than 2, or stacked Rows
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
MetricCard(
title = "Total Time",
value = "25h 10m",
icon = Icons.Default.Smartphone,
colors = colors,
modifier = Modifier.weight(1f)
)
MetricCard(
title = "Avg Daily",
value = "3h 35m",
icon = Icons.Default.AvTimer,
colors = colors,
modifier = Modifier.weight(1f)
)
}
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
MetricCard(
title = "Most Used",
value = "App A",
icon = Icons.Default.StarOutline,
colors = colors,
modifier = Modifier.weight(1f)
)
MetricCard(
title = "Pickups",
value = "75",
icon = Icons.Default.TouchApp,
colors = colors,
modifier = Modifier.weight(1f)
)
}
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
"Usage Patterns",
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp
)
)
ChartPlaceholder(
text = "Daily Screen Time (Bar Chart)",
colors = colors,
modifier = Modifier.fillMaxWidth().height(180.dp)
) // Slightly smaller charts for mobile
ChartPlaceholder(
text = "App Usage (Pie Chart)",
colors = colors,
modifier = Modifier.fillMaxWidth().height(180.dp)
)
}
}
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
"Analysis Tools",
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp
)
)
StyledButton(
modifier = Modifier.fillMaxWidth(),
icon = Icons.Default.PieChartOutline,
text = "App Usage Breakdown",
onClick = { /* TODO */ },
colors = colors
)
StyledButton(
modifier = Modifier.fillMaxWidth(),
icon = Icons.Default.AccessTime,
text = "Time of Day Analysis",
onClick = { /* TODO */ },
colors = colors
)
StyledButton(
modifier = Modifier.fillMaxWidth(),
icon = Icons.Default.TrackChanges,
text = "Set Usage Goals",
onClick = { /* TODO */ },
colors = colors
)
}
}
}
}
@Preview
@Composable
fun MobileAnalyticsScreenPreview() {
MaterialTheme {
MobileAnalyticsScreen(colors = AppThemes.LightThemeColors)
}
}
@Preview
@Composable
fun MobileAnalyticsScreenDarkPreview() {
MaterialTheme {
MobileAnalyticsScreen(colors = AppThemes.DarkThemeColors)
}
}

View File

@ -0,0 +1,174 @@
package com.grtsinry43.activityanalyzer.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.grtsinry43.activityanalyzer.components.ActivityItem
import com.grtsinry43.activityanalyzer.components.SimpleListItem
import com.grtsinry43.activityanalyzer.components.StyledButton
import com.grtsinry43.activityanalyzer.components.StyledCard
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun MobileHomeScreen(colors: AppThemes.Colors) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp), // Standard padding for mobile screens
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Welcome Back, grtsinry43!",
style = MaterialTheme.typography.headlineSmall.copy(
color = colors.onBackground,
fontWeight = FontWeight.Bold,
fontSize = 22.sp // Adjusted for mobile
)
)
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "Today's Screen Time",
style = MaterialTheme.typography.titleLarge.copy( // Larger title for emphasis
color = colors.onSurface,
fontWeight = FontWeight.SemiBold,
fontSize = 18.sp
)
)
Text(
text = "3h 45m", // Placeholder
style = MaterialTheme.typography.displayMedium.copy( // Prominent display
color = colors.accent,
fontWeight = FontWeight.Bold,
fontSize = 36.sp
),
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Text(
text = "You're on track with your daily goal!", // Placeholder
style = MaterialTheme.typography.bodyMedium.copy(
color = colors.secondaryText,
fontSize = 14.sp
),
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
}
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = "Quick Glance: Top Apps",
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp
)
)
SimpleListItem(
icon = Icons.Filled.SmartDisplay,
text = "App A: 1h 15m",
colors = colors
)
SimpleListItem(
icon = Icons.Filled.PhotoCamera,
text = "App B: 45m",
colors = colors
)
SimpleListItem(icon = Icons.Filled.Chat, text = "App C: 30m", colors = colors)
}
}
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp) // Spacing for buttons
) {
Text(
text = "Quick Actions",
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp
)
)
StyledButton( // Full width buttons for mobile quick actions
modifier = Modifier.fillMaxWidth(),
icon = Icons.Default.HourglassTop,
text = "Start Focus Session",
onClick = { /* TODO */ },
colors = colors
)
StyledButton(
modifier = Modifier.fillMaxWidth(),
icon = Icons.Default.CalendarViewDay, // Changed icon
text = "View Today's Details",
onClick = { /* TODO */ },
colors = colors
)
}
}
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = "Recent Insights",
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp
)
)
ActivityItem(
title = "Exceeded daily goal for App X.",
time = "Today, 2:30 PM",
icon = Icons.Default.WarningAmber,
colors = colors
)
ActivityItem(
title = "Screen time 20% higher yesterday.",
time = "Insight from yesterday",
icon = Icons.Default.TrendingUp,
colors = colors
)
}
}
}
}
@Preview
@Composable
fun MobileHomeScreenPreview() {
MaterialTheme {
MobileHomeScreen(colors = AppThemes.LightThemeColors)
}
}
@Preview
@Composable
fun MobileHomeScreenDarkPreview() {
MaterialTheme {
MobileHomeScreen(colors = AppThemes.DarkThemeColors)
}
}

View File

@ -0,0 +1,188 @@
package com.grtsinry43.activityanalyzer.screens
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.grtsinry43.activityanalyzer.components.SimpleListItem
import com.grtsinry43.activityanalyzer.components.StyledButton
import com.grtsinry43.activityanalyzer.components.StyledCard
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun MobileProfileScreen(colors: AppThemes.Colors) {
var nickname by remember { mutableStateOf("grtsinry43") }
var email by remember { mutableStateOf("grtsinry43@outlook.com") }
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(20.dp),
horizontalAlignment = Alignment.CenterHorizontally // Center content like avatar
) {
Text(
"User Profile",
style = MaterialTheme.typography.headlineSmall.copy(
color = colors.onBackground,
fontWeight = FontWeight.Bold,
fontSize = 22.sp
),
modifier = Modifier.align(Alignment.Start) // Align title to start
)
Icon(
imageVector = Icons.Filled.AccountCircle,
contentDescription = "User Avatar",
tint = colors.accent,
modifier = Modifier.size(120.dp) // Large avatar for profile screen
)
// TODO: Add option to change avatar (e.g., an Edit icon button)
OutlinedTextField(
value = nickname,
onValueChange = { nickname = it },
label = { Text("Nickname") },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = colors.accent,
unfocusedBorderColor = colors.border,
focusedLabelColor = colors.accent,
cursorColor = colors.accent,
focusedTextColor = colors.onSurface,
unfocusedTextColor = colors.onSurface
)
)
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") },
singleLine = true,
modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = colors.accent,
unfocusedBorderColor = colors.border,
focusedLabelColor = colors.accent,
cursorColor = colors.accent,
focusedTextColor = colors.onSurface,
unfocusedTextColor = colors.onSurface
)
)
StyledButton(
icon = Icons.Default.Save,
text = "Save Changes",
onClick = { /* TODO: Save profile changes */ },
colors = colors,
modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
)
// Overall Statistics Card
StyledCard(colors = colors, modifier = Modifier.padding(top = 16.dp)) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
"Overall Statistics",
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp
)
)
SimpleListItem(
icon = Icons.Filled.Timer,
text = "Total Time Tracked: 1250 hrs",
colors = colors
)
SimpleListItem(
icon = Icons.Filled.CheckCircleOutline,
text = "Goals Met Streak: 15 days",
colors = colors
)
SimpleListItem(
icon = Icons.Filled.EventAvailable,
text = "Joined: Jan 1, 2024",
colors = colors
)
}
}
// Account Actions
Column(
modifier = Modifier.padding(top = 16.dp).fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
"Account Actions",
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onBackground,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp
),
modifier = Modifier.padding(bottom = 4.dp)
)
StyledButton(
icon = Icons.Default.LockReset,
text = "Change Password",
onClick = { /* TODO */ },
colors = colors,
modifier = Modifier.fillMaxWidth()
)
Button( // Destructive action button styling
onClick = { /* TODO: Show confirmation dialog */ },
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(10.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Red.copy(alpha = 0.1f),
contentColor = Color.Red.copy(alpha = 0.9f)
),
border = BorderStroke(1.dp, Color.Red.copy(alpha = 0.3f)),
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
Icons.Default.DeleteForever,
contentDescription = null,
modifier = Modifier.size(20.dp)
)
Text("Delete Account", fontSize = 14.sp, fontWeight = FontWeight.Medium)
}
}
}
}
}
@Preview
@Composable
fun MobileProfileScreenPreview() {
MaterialTheme {
MobileProfileScreen(colors = AppThemes.LightThemeColors)
}
}
@Preview
@Composable
fun MobileProfileScreenDarkPreview() {
MaterialTheme {
MobileProfileScreen(colors = AppThemes.DarkThemeColors)
}
}

View File

@ -0,0 +1,104 @@
package com.grtsinry43.activityanalyzer.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.grtsinry43.activityanalyzer.components.ReportItem
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun MobileReportsScreen(colors: AppThemes.Colors) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
"Screen Time Reports",
style = MaterialTheme.typography.headlineSmall.copy(
color = colors.onBackground,
fontWeight = FontWeight.Bold,
fontSize = 22.sp
)
)
IconButton(onClick = { /* TODO: Generate new report */ }) { // Icon button for mobile
Icon(
Icons.Default.Addchart,
contentDescription = "Generate New Report",
tint = colors.accent
)
}
}
// Example Reports - for mobile, a simple list is often best
ReportItem(
title = "Weekly Summary - May 5-11",
period = "Generated: May 12, 2025",
icon = Icons.Default.CalendarToday,
colors = colors,
onDownload = {},
onView = {})
Divider(color = colors.border.copy(alpha = 0.2f), thickness = 0.5.dp)
ReportItem(
title = "Monthly App Usage - April",
period = "Generated: May 1, 2025",
icon = Icons.Default.PieChart,
colors = colors,
onDownload = {},
onView = {})
Divider(color = colors.border.copy(alpha = 0.2f), thickness = 0.5.dp)
ReportItem(
title = "Q1 Device Pickups",
period = "Generated: April 5, 2025",
icon = Icons.Default.TouchApp,
colors = colors,
onDownload = {},
onView = {})
Divider(color = colors.border.copy(alpha = 0.2f), thickness = 0.5.dp)
ReportItem(
title = "Focus Session - Project X",
period = "Generated: May 10, 2025",
icon = Icons.Default.HourglassEmpty,
colors = colors,
onDownload = {},
onView = {})
// Placeholder if no reports
// Box(modifier = Modifier.fillMaxSize().padding(top = 32.dp), contentAlignment = Alignment.Center) {
// Text("No reports generated yet.", color = colors.secondaryText, style = MaterialTheme.typography.bodyLarge)
// }
}
}
@Preview
@Composable
fun MobileReportsScreenPreview() {
MaterialTheme {
MobileReportsScreen(colors = AppThemes.LightThemeColors)
}
}
@Preview
@Composable
fun MobileReportsScreenDarkPreview() {
MaterialTheme {
MobileReportsScreen(colors = AppThemes.DarkThemeColors)
}
}

View File

@ -0,0 +1,182 @@
package com.grtsinry43.activityanalyzer.screens
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.grtsinry43.activityanalyzer.components.SettingItem
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun MobileSettingsScreen(
colors: AppThemes.Colors,
isDarkTheme: Boolean,
onThemeChange: (Boolean) -> Unit
) {
var autoBackupEnabled by remember { mutableStateOf(true) }
var notificationsEnabled by remember { mutableStateOf(true) }
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(bottom = 16.dp), // Padding at the bottom for scrollable content
verticalArrangement = Arrangement.spacedBy(0.dp) // No space between items, handled by SettingItem padding and dividers
) {
// General Group
SettingsGroupHeader(title = "General", colors = colors)
SettingItem(
title = "Dark Theme",
subtitle = if (isDarkTheme) "Enabled" else "Disabled",
icon = Icons.Default.Brightness6,
colors = colors,
showSwitch = true,
switchChecked = isDarkTheme,
onSwitchChange = onThemeChange
)
Divider(
color = colors.border.copy(alpha = 0.2f),
modifier = Modifier.padding(horizontal = 16.dp)
)
SettingItem(
title = "Data Storage",
subtitle = "Manage storage location", // Simplified for mobile
icon = Icons.Default.FolderOpen,
colors = colors,
onClick = { /* TODO */ }
)
Divider(
color = colors.border.copy(alpha = 0.2f),
modifier = Modifier.padding(horizontal = 16.dp)
)
SettingItem(
title = "Auto Backup",
subtitle = if (autoBackupEnabled) "Daily at 2:00 AM" else "Disabled",
icon = Icons.Default.SaveAlt,
colors = colors,
showSwitch = true,
switchChecked = autoBackupEnabled,
onSwitchChange = { autoBackupEnabled = it }
)
Divider(
color = colors.border.copy(alpha = 0.2f),
modifier = Modifier.padding(horizontal = 16.dp)
)
SettingItem(
title = "Cloud Sync",
subtitle = "Not Connected",
icon = Icons.Default.CloudQueue,
colors = colors,
onClick = { /* TODO */ }
)
Divider(
color = colors.border.copy(alpha = 0.2f),
modifier = Modifier.padding(horizontal = 16.dp)
)
SettingItem(
title = "Export Data",
subtitle = "Export activity data",
icon = Icons.Default.Output,
colors = colors,
onClick = { /* TODO */ }
)
// Tracking Group
SettingsGroupHeader(title = "Tracking", colors = colors)
SettingItem(
title = "Apps to Track",
subtitle = "All Apps", // Placeholder
icon = Icons.Default.AppBlocking,
colors = colors,
onClick = { /* TODO */ }
)
Divider(
color = colors.border.copy(alpha = 0.2f),
modifier = Modifier.padding(horizontal = 16.dp)
)
SettingItem(
title = "Tracking Sensitivity",
subtitle = "Medium", // Placeholder
icon = Icons.Default.Tune,
colors = colors,
onClick = { /* TODO */ }
)
// Notifications Group
SettingsGroupHeader(title = "Notifications", colors = colors)
SettingItem(
title = "Screen Time Limits",
subtitle = "Notify when limits exceeded",
icon = Icons.Default.NotificationsActive,
colors = colors,
showSwitch = true,
switchChecked = notificationsEnabled,
onSwitchChange = { notificationsEnabled = it }
)
Divider(
color = colors.border.copy(alpha = 0.2f),
modifier = Modifier.padding(horizontal = 16.dp)
)
SettingItem(
title = "Break Reminders",
subtitle = "Get reminded to take breaks",
icon = Icons.Default.SelfImprovement,
colors = colors,
onClick = { /* TODO */ }
)
Divider(
color = colors.border.copy(alpha = 0.2f),
modifier = Modifier.padding(horizontal = 16.dp)
)
SettingItem(
title = "Weekly Summary",
subtitle = "Notify every Monday",
icon = Icons.Default.MarkEmailRead,
colors = colors,
showSwitch = true,
switchChecked = true, // Placeholder
onSwitchChange = { /* TODO */ }
)
}
}
@Composable
fun SettingsGroupHeader(title: String, colors: AppThemes.Colors) {
Text(
text = title,
style = MaterialTheme.typography.titleSmall.copy( // Using titleSmall for group headers
color = colors.accent, // Use accent color for headers
fontWeight = FontWeight.SemiBold,
fontSize = 14.sp
),
modifier = Modifier
.fillMaxWidth()
.background(colors.background) // Ensure header background matches screen
.padding(horizontal = 16.dp, vertical = 12.dp) // More padding for group headers
)
}
@Preview
@Composable
fun MobileSettingsScreenPreview() {
MaterialTheme {
MobileSettingsScreen(colors = AppThemes.LightThemeColors, isDarkTheme = false, onThemeChange = {})
}
}
@Preview
@Composable
fun MobileSettingsScreenDarkPreview() {
MaterialTheme {
MobileSettingsScreen(colors = AppThemes.DarkThemeColors, isDarkTheme = true, onThemeChange = {})
}
}

View File

@ -0,0 +1,43 @@
package com.grtsinry43.activityanalyzer.theme
import androidx.compose.ui.graphics.Color
// --- THEMES (Reused from Desktop App) ---
// 定义颜色主题,提供更传统的桌面应用外观
object AppThemes {
data class Colors(
val background: Color,
val surface: Color,
val onSurface: Color,
val onBackground: Color,
val accent: Color,
val accentVariant: Color, // 用于细微强调或悬停状态
val border: Color,
val secondaryText: Color,
val onAccent: Color // 强调背景上的文本/图标颜色
)
val LightThemeColors = Colors(
background = Color(0xFFE0E0E0), // Light Gray // 浅灰色
surface = Color(0xFFFFFFFF), // White // 白色
onSurface = Color(0xFF212121), // Dark Gray // 深灰色
onBackground = Color(0xFF212121), // Dark Gray // 深灰色
accent = Color(0xFF0D47A1), // Dark Blue // 深蓝色
accentVariant = Color(0xFF1565C0), // Medium Blue // 中蓝色
border = Color(0xFFB0B0B0), // Medium Gray // 中灰色
secondaryText = Color(0xFF757575), // Gray // 灰色
onAccent = Color.White
)
val DarkThemeColors = Colors(
background = Color(0xFF212121), // Very Dark Gray // 非常深的灰色
surface = Color(0xFF303030), // Dark Gray // 深灰色
onSurface = Color(0xFFE0E0E0), // Light Gray // 浅灰色
onBackground = Color(0xFFE0E0E0), // Light Gray // 浅灰色
accent = Color(0xFF42A5F5), // Light Blue // 浅蓝色
accentVariant = Color(0xFF64B5F6), // Lighter Blue // 更浅的蓝色
border = Color(0xFF525252), // Medium Dark Gray // 中度深灰色
secondaryText = Color(0xFFBDBDBD), // Light Gray // 浅灰色
onAccent = Color.Black
)
}

View File

@ -1,11 +1,9 @@
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.foundation.text.BasicText
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
@ -14,633 +12,171 @@ 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.unit.dp
import androidx.compose.ui.unit.sp
import org.jetbrains.compose.ui.tooling.preview.Preview
import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.TextButton
import androidx.compose.foundation.text.selection.SelectionContainer
import com.grtsinry43.activityanalyzer.components.*
import com.grtsinry43.activityanalyzer.screens.AboutScreen
import com.grtsinry43.activityanalyzer.screens.AnalyticsScreen
import com.grtsinry43.activityanalyzer.screens.HomeScreen
import com.grtsinry43.activityanalyzer.screens.ProfileScreen
import com.grtsinry43.activityanalyzer.screens.ReportsScreen
import com.grtsinry43.activityanalyzer.screens.SettingsScreen
import com.grtsinry43.activityanalyzer.theme.AppThemes
@Composable
@Preview
fun DesktopApp() {
var selectedItem by remember { mutableStateOf(0) }
var isDarkTheme by remember { mutableStateOf(false) }
var isSidebarCollapsed by remember { mutableStateOf(false) } // 新增状态变量
var selectedItem by remember { mutableStateOf(0) } // 记住当前选中的导航项索引
var isDarkTheme by remember { mutableStateOf(false) } // 记住当前是否为暗色主题
var isSidebarCollapsed by remember { mutableStateOf(false) } // 记住侧边栏是否折叠
val backgroundColor = if (isDarkTheme) Color(0xFF1E1E1E) else Color(0xFFF5F5F5)
val surfaceColor = if (isDarkTheme) Color(0xFF2D2D2D) else Color.White
val textColor = if (isDarkTheme) Color.White else Color.Black
val accentColor = Color(0xFF007AFF)
val currentColors =
if (isDarkTheme) AppThemes.DarkThemeColors else AppThemes.LightThemeColors // 根据主题选择颜色
Box(
modifier = Modifier
.fillMaxSize()
.background(backgroundColor)
.fillMaxSize() // 填充整个可用空间
.background(currentColors.background) // 设置背景色
) {
Row(
modifier = Modifier.fillMaxSize()
modifier = Modifier.fillMaxSize() // 主行布局,填充整个可用空间
) {
// Sidebar
// 侧边栏
Box(
modifier = Modifier
.width(if (isSidebarCollapsed) 60.dp else 200.dp) // 动态宽度
.fillMaxHeight()
.background(surfaceColor)
.padding(16.dp)
.width(if (isSidebarCollapsed) 70.dp else 240.dp) // 根据折叠状态调整宽度
.fillMaxHeight() // 填充整个高度
.background(currentColors.surface) // 设置侧边栏背景色
.padding(
start = 8.dp,
end = 8.dp,
top = 16.dp,
bottom = 16.dp
) // 设置内边距
) {
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
modifier = Modifier.fillMaxHeight(), // 列布局,填充整个高度
verticalArrangement = Arrangement.spacedBy(8.dp) // 子项垂直间距
) {
if (!isSidebarCollapsed) {
Text(
"Activity Analyzer",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = textColor
"Activity Analyzer", // 应用标题
fontSize = 18.sp, // 字体大小
fontWeight = FontWeight.SemiBold, // 字体粗细
color = currentColors.onSurface, // 文本颜色
modifier = Modifier.padding(
horizontal = 8.dp,
vertical = 16.dp
) // 标题内边距
)
} else {
// 折叠时显示应用Logo图标
Icon(
Icons.Default.Analytics, // 应用Logo图标 (示例)
contentDescription = "App Logo", // 内容描述
tint = currentColors.accent, // 图标颜色
modifier = Modifier
.size(40.dp) // 图标大小
.align(Alignment.CenterHorizontally) // 水平居中
.padding(vertical = 16.dp) // 垂直内边距
)
Spacer(modifier = Modifier.height(32.dp))
}
// Navigation items
NavItem(
icon = Icons.Default.Home,
text = if (isSidebarCollapsed) "" else "Home", // 根据收缩状态显示文本
isSelected = selectedItem == 0,
onClick = { selectedItem = 0 },
textColor = textColor,
accentColor = accentColor,
isSidebarCollapsed = isSidebarCollapsed
)
NavItem(
icon = Icons.Default.BarChart,
text = if (isSidebarCollapsed) "" else "Analytics",
isSelected = selectedItem == 1,
onClick = { selectedItem = 1 },
textColor = textColor,
accentColor = accentColor,
isSidebarCollapsed = isSidebarCollapsed
)
NavItem(
icon = Icons.Default.Dataset,
text = if (isSidebarCollapsed) "" else "Reports",
isSelected = selectedItem == 2,
onClick = { selectedItem = 2 },
textColor = textColor,
accentColor = accentColor,
isSidebarCollapsed = isSidebarCollapsed
)
NavItem(
icon = Icons.Default.Settings,
text = if (isSidebarCollapsed) "" else "Settings",
isSelected = selectedItem == 3,
onClick = { selectedItem = 3 },
textColor = textColor,
accentColor = accentColor,
isSidebarCollapsed = isSidebarCollapsed
// 导航项列表
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 // 关于页
)
Spacer(modifier = Modifier.weight(1f))
// Theme switch
if (!isSidebarCollapsed) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.background(if (isDarkTheme) Color(0xFF3D3D3D) else Color(0xFFE8E8E8))
.padding(8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
"Dark Theme",
color = textColor
)
Switch(
checked = isDarkTheme,
onCheckedChange = { isDarkTheme = it },
colors = SwitchDefaults.colors(
checkedThumbColor = accentColor,
checkedTrackColor = accentColor.copy(alpha = 0.5f)
)
)
}
navItems.forEachIndexed { index, (text, icon) ->
NavItem(
icon = icon,
text = if (isSidebarCollapsed) "" else text, // 折叠时不显示文本
isSelected = selectedItem == index, // 判断是否选中
onClick = { selectedItem = index }, // 点击事件,更新选中项
colors = currentColors,
isSidebarCollapsed = isSidebarCollapsed
)
}
Spacer(modifier = Modifier.weight(1f)) // 弹性空间,将后续内容推到底部
// User Profile Section
// 用户资料区域 (侧边栏底部)
if (!isSidebarCollapsed) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally // 水平居中对齐
) {
Icon(
imageVector = Icons.Default.AccountCircle, // 用户头像图标
contentDescription = "User Avatar", // 内容描述
tint = currentColors.accent, // 图标颜色
modifier = Modifier
.size(48.dp) // 图标大小
.clip(CircleShape) // 圆形裁剪
)
Spacer(modifier = Modifier.height(8.dp)) // 垂直间距
Text(
text = "grtsinry43", // 用户昵称 (占位符)
color = currentColors.onSurface, // 文本颜色
fontSize = 14.sp, // 字体大小
fontWeight = FontWeight.Medium // 字体粗细
)
}
Spacer(modifier = Modifier.height(16.dp)) // 垂直间距
}
// Collapse/Expand button
// 折叠/展开侧边栏按钮
IconButton(
onClick = { isSidebarCollapsed = !isSidebarCollapsed },
modifier = Modifier.align(Alignment.CenterHorizontally)
onClick = { isSidebarCollapsed = !isSidebarCollapsed }, // 点击切换折叠状态
modifier = Modifier.align(Alignment.CenterHorizontally) // 水平居中
.size(40.dp) // 按钮大小
) {
Icon(
imageVector = if (isSidebarCollapsed) Icons.Default.ChevronRight else Icons.Default.ChevronLeft,
contentDescription = "Toggle Sidebar",
tint = textColor
imageVector = if (isSidebarCollapsed) Icons.Default.MenuOpen else Icons.Default.Menu, // 根据状态选择图标
contentDescription = "Toggle Sidebar", // 内容描述
tint = currentColors.onSurface // 图标颜色
)
}
}
}
// Main content
// 主内容区域
Box(
modifier = Modifier
.fillMaxSize()
.padding(24.dp)
.fillMaxSize() // 填充整个可用空间
.padding(24.dp) // 内边距
) {
// 根据选中的导航项显示不同的屏幕内容
when (selectedItem) {
0 -> HomeScreen(textColor = textColor, surfaceColor = surfaceColor)
1 -> AnalyticsScreen(textColor = textColor, surfaceColor = surfaceColor)
2 -> ReportsScreen(textColor = textColor, surfaceColor = surfaceColor)
3 -> SettingsScreen(textColor = textColor, surfaceColor = surfaceColor)
0 -> HomeScreen(colors = currentColors)
1 -> AnalyticsScreen(colors = currentColors)
2 -> ReportsScreen(colors = currentColors)
3 -> ProfileScreen(colors = currentColors)
4 -> SettingsScreen(
colors = currentColors,
isDarkTheme = isDarkTheme,
onThemeChange = { isDarkTheme = it } // 主题切换回调
)
5 -> AboutScreen(colors = currentColors)
}
}
}
}
}
@Composable
fun NavItem(
icon: ImageVector,
text: String,
isSelected: Boolean,
onClick: () -> Unit,
textColor: Color,
accentColor: Color,
isSidebarCollapsed: Boolean
) {
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp)) // 更大的圆角
.background(if (isSelected) accentColor.copy(alpha = 0.2f) else Color.Transparent)
.padding(vertical = 12.dp, horizontal = if (isSidebarCollapsed) 8.dp else 16.dp)
.clickable(
onClick = onClick,
indication = null,
interactionSource = remember { MutableInteractionSource() }
),
contentAlignment = Alignment.CenterStart
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = if (isSelected) accentColor else textColor,
modifier = Modifier.size(if (isSidebarCollapsed) 32.dp else 24.dp) // 动态调整图标大小
)
if (!isSidebarCollapsed) {
Text(
text = text,
style = MaterialTheme.typography.titleMedium.copy(
color = if (isSelected) accentColor else textColor,
fontSize = 16.sp,
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal
)
)
}
}
}
}
@Composable
fun HomeScreen(textColor: Color, surfaceColor: Color) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
SelectionContainer {
Text(
text = "Welcome Back!",
style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.onBackground
)
}
// Quick actions
Card(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.height(150.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
SelectionContainer {
Text(
text = "Quick Actions",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface
)
}
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
ActionButton(
icon = Icons.Default.Add,
text = "New Activity",
onClick = {/* TODO */ }
)
ActionButton(
icon = Icons.Default.Upload,
text = "Import Data",
onClick = {/* TODO */ }
)
}
}
}
}
// // Recent activities
// Card(
// modifier = Modifier
// .fillMaxWidth()
// .clip(RoundedCornerShape(16.dp)),
// colors = CardDefaults.cardColors(
// containerColor = MaterialTheme.colorScheme.surface
// )
// ) {
// Column(
// modifier = Modifier.padding(16.dp),
// verticalArrangement = Arrangement.spacedBy(16.dp)
// ) {
// Text(
// text = "Recent Activities",
// style = MaterialTheme.typography.titleMedium,
// color = MaterialTheme.colorScheme.onSurface
// )
// ActivityItem(
// title = "Activity Report Generated",
// time = "2024-04-12 14:30",
// icon = Icons.Default.Description
// )
// ActivityItem(
// title = "Data Imported",
// time = "2024-04-12 10:15",
// icon = Icons.Default.Upload
// )
// }
// }
}
@Composable
fun ActionButton(
icon: ImageVector,
text: String,
onClick: () -> Unit
) {
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFF007AFF).copy(alpha = 0.1f))
.clickable(onClick = onClick)
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = Color(0xFF007AFF)
)
Text(
text = text,
color = Color(0xFF007AFF),
fontSize = 14.sp
)
}
}
}
@Composable
fun ActivityItem(
title: String,
time: String,
icon: ImageVector
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.background(Color(0xFFF5F5F5))
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = Color(0xFF007AFF)
)
Column {
Text(
text = title,
fontSize = 16.sp,
color = Color.Black
)
Text(
text = time,
fontSize = 14.sp,
color = Color.Gray
)
}
}
}
@Composable
fun AnalyticsScreen(textColor: Color, surfaceColor: Color) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
Text(
"Analytics",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = textColor
)
// Analysis tools card
Card(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(surfaceColor)
.padding(24.dp)
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
"Analysis Tools",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = textColor
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
AnalysisToolButton(
icon = Icons.Default.Info,
text = "Time Analysis",
onClick = {/* TODO */ }
)
AnalysisToolButton(
icon = Icons.Default.Info,
text = "Distribution Analysis",
onClick = {/* TODO */ }
)
}
}
}
}
}
@Composable
fun AnalysisToolButton(
icon: ImageVector,
text: String,
onClick: () -> Unit
) {
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFF007AFF).copy(alpha = 0.1f))
.clickable(onClick = onClick)
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = Color(0xFF007AFF)
)
Text(
text = text,
color = Color(0xFF007AFF),
fontSize = 14.sp
)
}
}
}
@Composable
fun ReportsScreen(textColor: Color, surfaceColor: Color) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
Text(
"Reports",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = textColor
)
// Report list
Card(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(surfaceColor)
.padding(24.dp)
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
"My Reports",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = textColor
)
ReportItem(
title = "Monthly Activity Report",
period = "March 2024",
icon = Icons.Default.Info
)
ReportItem(
title = "Weekly Activity Analysis",
period = "Week 12, 2024",
icon = Icons.Default.Info
)
}
}
}
}
@Composable
fun ReportItem(
title: String,
period: String,
icon: ImageVector
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.background(Color(0xFFF5F5F5))
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = Color(0xFF007AFF)
)
Column {
Text(
text = title,
fontSize = 16.sp,
color = Color.Black
)
Text(
text = period,
fontSize = 14.sp,
color = Color.Gray
)
}
}
IconButton(onClick = { /* TODO */ }) {
Icon(
imageVector = Icons.Default.Info,
contentDescription = "Download",
tint = Color(0xFF007AFF)
)
}
}
}
@Composable
fun SettingsScreen(textColor: Color, surfaceColor: Color) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
Text(
"Settings",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = textColor
)
// Settings options
Card(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(surfaceColor)
.padding(24.dp)
) {
Column(
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
SettingItem(
title = "Data Storage Location",
subtitle = "Default Location",
icon = Icons.Default.Info,
onClick = {/* TODO */ }
)
SettingItem(
title = "Auto Backup",
subtitle = "Daily",
icon = Icons.Default.Info,
showSwitch = true
)
SettingItem(
title = "Notification Settings",
subtitle = "Enabled",
icon = Icons.Default.Info,
showSwitch = true
)
}
}
}
}
@Composable
fun SettingItem(
title: String,
subtitle: String,
icon: ImageVector,
onClick: (() -> Unit)? = null,
showSwitch: Boolean = false
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.background(Color(0xFFF5F5F5))
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = Color(0xFF007AFF)
)
Column {
Text(
text = title,
fontSize = 16.sp,
color = Color.Black
)
Text(
text = subtitle,
fontSize = 14.sp,
color = Color.Gray
)
}
}
if (showSwitch) {
Switch(
checked = true,
onCheckedChange = {/* TODO */ },
colors = SwitchDefaults.colors(
checkedThumbColor = Color(0xFF007AFF),
checkedTrackColor = Color(0xFF007AFF).copy(alpha = 0.5f)
)
)
} else if (onClick != null) {
TextButton(onClick = onClick) {
Text(
"Change",
color = Color(0xFF007AFF)
)
}
}
}
}
}

View File

@ -0,0 +1,132 @@
package com.grtsinry43.activityanalyzer.components
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Home
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.unit.dp
import androidx.compose.ui.unit.sp
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun NavItem(
icon: ImageVector, // 导航项图标
text: String, // 导航项文本
isSelected: Boolean, // 是否被选中
onClick: () -> Unit, // 点击事件回调
colors: AppThemes.Colors, // 当前颜色主题
isSidebarCollapsed: Boolean // 侧边栏是否折叠
) {
val interactionSource = remember { MutableInteractionSource() } // 用于自定义点击效果
val backgroundColor =
if (isSelected) colors.accent.copy(alpha = 0.15f) else Color.Transparent // 选中时背景色高亮
val contentColor =
if (isSelected) colors.accent else colors.onSurface.copy(alpha = 0.8f) // 选中时内容颜色使用强调色
Row( // 使用 Row 布局导航项
modifier = Modifier
.fillMaxWidth() // 填充宽度
.height(if (isSidebarCollapsed) 50.dp else 44.dp) // 根据折叠状态调整高度
.clip(RoundedCornerShape(8.dp)) // 圆角
.background(backgroundColor) // 背景色
.clickable( // 设置点击事件
onClick = onClick,
interactionSource = interactionSource,
indication = null // 不使用默认的点击涟漪效果,依赖背景色变化
)
.padding(horizontal = if (isSidebarCollapsed) 0.dp else 12.dp), // 折叠时水平内边距为0使图标居中
verticalAlignment = Alignment.CenterVertically, // 垂直居中对齐
horizontalArrangement = if (isSidebarCollapsed) Arrangement.Center else Arrangement.Start // 折叠时水平居中,否则从左开始
) {
Icon(
imageVector = icon,
contentDescription = text.ifEmpty { null }, // 如果文本为空则内容描述为null
tint = contentColor, // 图标颜色
modifier = Modifier.size(if (isSidebarCollapsed) 28.dp else 22.dp) // 根据折叠状态调整图标大小
)
if (!isSidebarCollapsed) { // 如果侧边栏未折叠,则显示文本
Spacer(modifier = Modifier.width(12.dp)) // 图标和文本之间的间距
Text(
text = text,
color = contentColor, // 文本颜色
fontSize = 15.sp, // 字体大小
fontWeight = if (isSelected) FontWeight.SemiBold else FontWeight.Normal // 选中时字体加粗
)
}
}
}
@Preview
@Composable
fun NavItemSelectedPreview() {
MaterialTheme {
NavItem(
icon = Icons.Default.Home,
text = "Home",
isSelected = true,
onClick = {},
colors = AppThemes.LightThemeColors,
isSidebarCollapsed = false
)
}
}
@Preview
@Composable
fun NavItemUnselectedPreview() {
MaterialTheme {
NavItem(
icon = Icons.Default.Home,
text = "Home",
isSelected = false,
onClick = {},
colors = AppThemes.LightThemeColors,
isSidebarCollapsed = false
)
}
}
@Preview
@Composable
fun NavItemSelectedCollapsedPreview() {
MaterialTheme {
NavItem(
icon = Icons.Default.Home,
text = "Home", // Text won't be visible
isSelected = true,
onClick = {},
colors = AppThemes.LightThemeColors,
isSidebarCollapsed = true
)
}
}
@Preview
@Composable
fun NavItemDarkSelectedPreview() {
MaterialTheme {
NavItem(
icon = Icons.Default.Home,
text = "Home",
isSelected = true,
onClick = {},
colors = AppThemes.DarkThemeColors,
isSidebarCollapsed = false
)
}
}

View File

@ -1,13 +1,120 @@
package com.grtsinry43.activityanalyzer
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Analytics // 用一个合适的图标作为托盘图标
import androidx.compose.material3.Icon
import androidx.compose.runtime.*
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.toComposeImageBitmap
import androidx.compose.ui.res.loadImageBitmap
import androidx.compose.ui.res.useResource
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.*
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "Activity Analyzer",
) {
DesktopApp()
// 管理窗口状态,例如可见性、位置、大小
val windowState = rememberWindowState(
size = DpSize(1024.dp, 768.dp), // 设置窗口的初始大小
// position = WindowPosition(Win) // 初始位置居中
)
// 管理托盘状态
val trayState = rememberTrayState()
var isWindowVisible by remember { mutableStateOf(true) } // 控制窗口是否可见
// 尝试加载自定义托盘图标 (推荐使用 .ico 或 .png)
// 请将 "tray_icon.png" 替换为你的图标文件名,并确保它在 resources 目录下
// 如果加载失败,则使用默认图标
val trayIconPainter = try {
useResource("tray_icon.png") { stream ->
BitmapPainter(loadImageBitmap(stream))
}
} catch (e: Exception) {
println("警告:无法加载自定义托盘图标 'tray_icon.png'。将使用默认图标。错误: ${e.message}")
null // 稍后会处理 null 情况
}
// 系统托盘设置
// 只有当窗口第一次变为不可见时,或者托盘图标加载成功时,才显示托盘图标
// 这样可以避免在没有图标的情况下尝试创建托盘
if (!isWindowVisible || trayIconPainter != null) {
Tray(
state = trayState,
icon = trayIconPainter ?: BitmapPainter(createDefaultTrayIcon()), // 如果自定义图标加载失败,使用一个备用图标
tooltip = "Activity Analyzer", // 鼠标悬停在托盘图标上时显示的提示文字
menu = {
// 托盘菜单项
Item(
if (isWindowVisible) "隐藏窗口" else "显示窗口",
onClick = {
isWindowVisible = !isWindowVisible
if (isWindowVisible) {
windowState.isMinimized = false // 如果窗口被最小化了,恢复它
}
}
)
Separator() // 分隔线
Item(
"退出应用",
onClick = ::exitApplication // 点击后退出整个应用
)
}
)
}
// 主窗口设置
if (isWindowVisible) {
Window(
onCloseRequest = {
// 点击关闭按钮时,可以选择隐藏到托盘而不是直接退出
isWindowVisible = false
// 或者,如果你希望点击关闭直接退出应用,则使用:
// exitApplication()
},
state = windowState, // 应用窗口状态
title = "Activity Analyzer",
resizable = true, // 允许用户调整窗口大小 (默认为 true)
// icon = painterResource("app_icon.png") // 可选:设置窗口左上角的图标和任务栏图标
) {
// 在这里设置窗口的最小尺寸
// 这会影响用户能将窗口缩小到的最小程度
window.minimumSize = java.awt.Dimension(600, 400)
// 你之前创建的 DesktopApp UI
// Assuming DesktopApp is a Composable function defined elsewhere
DesktopApp()
}
}
}
// 创建一个简单的默认托盘图标 (如果自定义图标加载失败)
// 这是一个备选方案,实际项目中强烈建议使用合适的图片资源
private fun createDefaultTrayIcon(): ImageBitmap { // Change return type to ImageBitmap
// 使用 Compose 图标作为示例,实际中你应该加载一个图像文件
// 这里我们创建一个简单的 BufferedImage 代替
val width = 64
val height = 64
val image = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
val g = image.createGraphics()
try {
// 简单绘制一个蓝色方块作为示例
g.color = java.awt.Color.BLUE
g.fillRect(0, 0, width, height)
g.color = java.awt.Color.WHITE
g.drawString("AA", 20, 40) // "Activity Analyzer" 缩写
} finally {
g.dispose()
}
// Convert BufferedImage to ImageBitmap before returning
return image.toComposeImageBitmap()
}

View File

@ -0,0 +1,173 @@
package com.grtsinry43.activityanalyzer.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.grtsinry43.activityanalyzer.components.InfoItem
import com.grtsinry43.activityanalyzer.components.SettingItem
import com.grtsinry43.activityanalyzer.components.StyledCard
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun AboutScreen(colors: AppThemes.Colors) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState()), // 允许垂直滚动
verticalArrangement = Arrangement.spacedBy(20.dp),
horizontalAlignment = Alignment.CenterHorizontally // 内容水平居中
) {
Icon(
imageVector = Icons.Filled.Analytics, // 应用Logo图标 (示例)
contentDescription = "App Logo", // 内容描述
tint = colors.accent, // 图标颜色
modifier = Modifier.size(80.dp) // 图标大小
)
Text(
"Activity Analyzer", // 应用名称
style = MaterialTheme.typography.headlineMedium.copy(
color = colors.onBackground,
fontWeight = FontWeight.Bold
)
)
Text(
"Version 1.0.0-beta", // 应用版本号
style = MaterialTheme.typography.titleMedium.copy(color = colors.secondaryText)
)
Spacer(modifier = Modifier.height(8.dp)) // 间距
// App Description Card
// 应用描述卡片
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
"Your personal screen time companion, helping you understand and manage your digital habits across platforms.", // 应用描述
style = MaterialTheme.typography.bodyLarge.copy(color = colors.onSurface),
textAlign = TextAlign.Center // 文本居中
)
}
}
// Developer Info Card
// 开发者信息卡片
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
"Developer", // 开发者
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold
),
modifier = Modifier.padding(bottom = 8.dp)
)
InfoItem(icon = Icons.Filled.Person, text = "grtsinry43", colors = colors) // 开发者名称
Divider(color = colors.border.copy(alpha = 0.3f))
InfoItem(
icon = Icons.Filled.Email,
text = "grtsinry43@outlook.com", // 开发者邮箱
colors = colors,
isLink = true, // 可点击链接
linkUrl = "mailto:grtsinry43@outlook.com" // 邮箱链接
)
Divider(color = colors.border.copy(alpha = 0.3f))
InfoItem(
icon = Icons.Filled.Language, // 网站/博客图标
text = "blog.grtsinry43.com", // 网站/博客地址
colors = colors,
isLink = true,
linkUrl = "https://blog.grtsinry43.com" // 网站链接
) // 个人网站/博客 (可点击)
Divider(color = colors.border.copy(alpha = 0.3f))
InfoItem(
icon = Icons.Filled.Code, // Github图标
text = "github.com/grtsinry43", // Github用户名
colors = colors,
isLink = true,
linkUrl = "https://github.com/grtsinry43" // Github链接
) // GitHub (可点击)
}
}
// Application Info Card
// 应用信息卡片
StyledCard(colors = colors) { // 应用信息卡片
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
"Application Info", // 应用信息
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold
),
modifier = Modifier.padding(bottom = 8.dp)
)
SettingItem( // 检查更新 (复用 SettingItem)
title = "Check for Updates", // 检查更新
subtitle = "Last checked: Today", // 上次检查时间 (占位符)
icon = Icons.Default.SystemUpdateAlt, // 更新图标
colors = colors,
onClick = { /* TODO: Implement update check */ } // 实现更新检查逻辑
)
Divider(color = colors.border.copy(alpha = 0.3f))
SettingItem( // 致谢
title = "Acknowledgements", // 致谢
subtitle = "Libraries and resources used", // 使用的库和资源
icon = Icons.Default.FavoriteBorder, // 爱心图标 (代表感谢)
colors = colors,
onClick = { /* TODO: Show acknowledgements dialog/screen */ } // 显示致谢对话框/屏幕
)
Divider(color = colors.border.copy(alpha = 0.3f))
SettingItem( // 许可证信息
title = "License Information", // 许可证信息
subtitle = "View application license", // 查看应用许可证
icon = Icons.Default.Gavel, // 法槌图标 (代表法律/许可)
colors = colors,
onClick = { /* TODO: Show license */ } // 显示许可证
)
}
}
Spacer(modifier = Modifier.weight(1f)) // 弹性空间,将版权信息推到底部
Text(
"© 2025 grtsinry43. All rights reserved.", // 版权信息
style = MaterialTheme.typography.bodySmall.copy(color = colors.secondaryText),
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 8.dp)
)
}
}
@Preview
@Composable
fun AboutScreenPreview() {
MaterialTheme {
AboutScreen(colors = AppThemes.LightThemeColors)
}
}
@Preview
@Composable
fun AboutScreenDarkPreview() {
MaterialTheme {
AboutScreen(colors = AppThemes.DarkThemeColors)
}
}

View File

@ -0,0 +1,222 @@
package com.grtsinry43.activityanalyzer.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.grtsinry43.activityanalyzer.components.ChartPlaceholder
import com.grtsinry43.activityanalyzer.components.MetricCard
import com.grtsinry43.activityanalyzer.components.StyledButton
import com.grtsinry43.activityanalyzer.components.StyledCard
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun AnalyticsScreen(colors: AppThemes.Colors) {
var selectedTimeRange by remember { mutableStateOf("Last 7 Days") } // 默认选中的时间范围
val timeRanges = // 可选的时间范围
listOf("Today", "Yesterday", "Last 7 Days", "Last 30 Days", "Custom Range")
Column(
modifier = Modifier
.fillMaxSize()
.padding(8.dp)
.verticalScroll(rememberScrollState()), // 允许垂直滚动
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
Text(
"Screen Time Analytics", // 屏幕时间分析
style = MaterialTheme.typography.headlineSmall.copy(
color = colors.onBackground,
fontWeight = FontWeight.Bold // 加粗
)
)
// Time Range Filter
// 时间范围筛选器
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
"Time Range:",
style = MaterialTheme.typography.titleSmall.copy(color = colors.onBackground)
) // 时间范围标签
var expanded by remember { mutableStateOf(false) } // 下拉菜单是否展开
Box {
OutlinedButton(
onClick = { expanded = true },
shape = RoundedCornerShape(8.dp) // 圆角按钮
) { // 点击展开下拉菜单
Text(selectedTimeRange, color = colors.accent) // 显示选中的时间范围
Icon(
Icons.Default.ArrowDropDown,
contentDescription = "Select time range", // 内容描述
tint = colors.accent
) // 下拉箭头图标
}
DropdownMenu( // 下拉菜单内容
expanded = expanded,
onDismissRequest = { expanded = false } // 点击外部关闭下拉菜单
) {
timeRanges.forEach { range ->
DropdownMenuItem(
text = { Text(range) },
onClick = {
selectedTimeRange = range // 更新选中的时间范围
expanded = false // 关闭下拉菜单
// TODO: Update analytics data based on range // 根据选中的时间范围更新分析数据
}
)
}
}
}
}
// Key Metrics Cards
// 关键指标卡片
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
MetricCard(
title = "Total Screen Time",
value = "25h 10m", // 示例数据
icon = Icons.Default.Smartphone,
colors = colors,
modifier = Modifier.weight(1f)
) // 总屏幕时间
MetricCard(
title = "Avg Daily Time",
value = "3h 35m", // 示例数据
icon = Icons.Default.AvTimer,
colors = colors,
modifier = Modifier.weight(1f)
) // 平均每日时间
}
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
MetricCard(
title = "Most Used App",
value = "App A (8h)", // 示例数据
icon = Icons.Default.StarOutline,
colors = colors,
modifier = Modifier.weight(1f)
) // 最常用应用
MetricCard(
title = "Pickups",
value = "75 today", // 示例数据
icon = Icons.Default.TouchApp,
colors = colors,
modifier = Modifier.weight(1f)
) // 拿起次数
}
// Charts Section
// 图表部分
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
"Usage Patterns", // 使用模式
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold
)
)
// Placeholder for Daily/Weekly Screen Time Bar Chart
// 每日/每周屏幕时间柱状图占位符
ChartPlaceholder(
text = "Daily/Weekly Screen Time (Bar Chart)",
colors = colors,
modifier = Modifier.fillMaxWidth().height(200.dp)
)
Spacer(modifier = Modifier.height(16.dp))
// Placeholder for App Usage Distribution Pie Chart
// 应用使用分布饼图占位符
ChartPlaceholder(
text = "App Usage Distribution (Pie Chart)",
colors = colors,
modifier = Modifier.fillMaxWidth().height(200.dp)
)
}
}
// Analysis Tools
// 分析工具
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
"Analysis Tools", // 分析工具
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold
)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
StyledButton(
modifier = Modifier.weight(1f),
icon = Icons.Default.PieChartOutline, // 应用细分图标
text = "App Breakdown", // 应用细分
onClick = { /* TODO */ },
colors = colors
)
StyledButton(
modifier = Modifier.weight(1f),
icon = Icons.Default.AccessTime, // 时段分析图标
text = "Time of Day", // 时段分析
onClick = { /* TODO */ },
colors = colors
)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
StyledButton(
modifier = Modifier.weight(1f),
icon = Icons.Default.TrackChanges, // 设定目标图标
text = "Usage Goals", // 设定目标
onClick = { /* TODO */ },
colors = colors
)
StyledButton(
modifier = Modifier.weight(1f),
icon = Icons.Default.CompareArrows, // 比较时段图标
text = "Compare Periods", // 比较时段
onClick = { /* TODO */ },
colors = colors
)
}
}
}
}
}
@Preview
@Composable
fun AnalyticsScreenPreview() {
MaterialTheme {
AnalyticsScreen(colors = AppThemes.LightThemeColors)
}
}
@Preview
@Composable
fun AnalyticsScreenDarkPreview() {
MaterialTheme {
AnalyticsScreen(colors = AppThemes.DarkThemeColors)
}
}

View File

@ -0,0 +1,184 @@
package com.grtsinry43.activityanalyzer.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.grtsinry43.activityanalyzer.components.ActivityItem
import com.grtsinry43.activityanalyzer.components.SimpleListItem
import com.grtsinry43.activityanalyzer.components.StyledButton
import com.grtsinry43.activityanalyzer.components.StyledCard
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun HomeScreen(colors: AppThemes.Colors) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(8.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
SelectionContainer {
Text(
text = "Welcome Back, grtsinry43!",
style = MaterialTheme.typography.headlineSmall.copy(
color = colors.onBackground,
fontWeight = FontWeight.Bold
)
)
}
// Overview of today's screen time
// 今日屏幕时间概览
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = "Today's Screen Time", // 今日屏幕时间
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold
)
)
Text(
text = "3h 45m", // 示例数据
style = MaterialTheme.typography.displaySmall.copy(
color = colors.accent,
fontWeight = FontWeight.Bold
),
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Text(
text = "You're on track with your daily goal!", // 示例消息
style = MaterialTheme.typography.bodyMedium.copy(color = colors.secondaryText),
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
}
// Quick Glance: Top Apps Today
// 今日热门应用速览
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = "Quick Glance: Top Apps Today", // 今日热门应用
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold
)
)
SimpleListItem(
icon = Icons.Filled.SmartDisplay, // 示例图标
text = "App A: 1h 15m", // 示例数据
colors = colors
)
SimpleListItem(
icon = Icons.Filled.PhotoCamera, // 示例图标
text = "App B: 45m", // 示例数据
colors = colors
)
SimpleListItem(
icon = Icons.Filled.Chat, // 示例图标
text = "App C: 30m", // 示例数据
colors = colors
)
}
}
// Quick actions
// 快捷操作
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Quick Actions", // 快捷操作
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold
)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
StyledButton(
modifier = Modifier.weight(1f),
icon = Icons.Default.HourglassTop, // 专注时段图标
text = "Focus Session", // 专注时段
onClick = { /* TODO: Start focus session */ },
colors = colors
)
StyledButton(
modifier = Modifier.weight(1f),
icon = Icons.Default.CalendarViewWeek, // 每周报告图标
text = "Weekly Report", // 每周报告
onClick = { /* TODO: Navigate to weekly report */ },
colors = colors
)
}
}
}
// Recent Alerts/Insights
// 最近提醒/洞察 (例如:与上周比较)
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = "Recent Insights", // 最近洞察
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold
)
)
ActivityItem(
title = "You've exceeded your daily goal for App X.", // 您已超出App X的每日目标
time = "Today, 2:30 PM", // 时间示例
icon = Icons.Default.WarningAmber, // 警告图标
colors = colors
)
ActivityItem(
title = "Screen time was 20% higher yesterday.", // 昨天的屏幕时间增加了20%
time = "Insight from yesterday", // 来自昨天的洞察
icon = Icons.Default.TrendingUp, // 上升趋势图标
colors = colors
)
}
}
}
}
@Preview
@Composable
fun HomeScreenPreview() {
MaterialTheme {
HomeScreen(colors = AppThemes.LightThemeColors)
}
}
@Preview
@Composable
fun HomeScreenDarkPreview() {
MaterialTheme {
HomeScreen(colors = AppThemes.DarkThemeColors)
}
}

View File

@ -0,0 +1,183 @@
package com.grtsinry43.activityanalyzer.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.grtsinry43.activityanalyzer.components.SimpleListItem
import com.grtsinry43.activityanalyzer.components.StyledButton
import com.grtsinry43.activityanalyzer.components.StyledCard
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun ProfileScreen(colors: AppThemes.Colors) {
var nickname by remember { mutableStateOf("grtsinry43") } // 用户昵称
var email by remember { mutableStateOf("grtsinry43@outlook.com") } // 用户邮箱 (占位符)
Column(
modifier = Modifier
.fillMaxSize()
.padding(8.dp)
.verticalScroll(rememberScrollState()), // 允许垂直滚动
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
Text(
"User Profile", // 个人资料
style = MaterialTheme.typography.headlineSmall.copy(
color = colors.onBackground,
fontWeight = FontWeight.Bold // 加粗
)
)
// Profile Details Card
// 个人资料详情卡片
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally, // 水平居中
verticalArrangement = Arrangement.spacedBy(16.dp) // 子项间距
) {
Icon(
imageVector = Icons.Filled.AccountCircle, // 用户头像图标
contentDescription = "User Avatar", // 内容描述
tint = colors.accent, // 图标颜色
modifier = Modifier.size(100.dp) // 图标大小
)
// TODO: Add option to change avatar // 添加更换头像的选项
OutlinedTextField( // 昵称输入框
value = nickname,
onValueChange = { nickname = it },
label = { Text("Nickname") }, // 标签:昵称
singleLine = true, // 单行输入
modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors( // 自定义输入框颜色
focusedBorderColor = colors.accent,
unfocusedBorderColor = colors.border,
focusedLabelColor = colors.accent,
cursorColor = colors.accent
)
)
OutlinedTextField( // 邮箱输入框
value = email,
onValueChange = { email = it },
label = { Text("Email") }, // 标签:邮箱
singleLine = true,
modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = colors.accent,
unfocusedBorderColor = colors.border,
focusedLabelColor = colors.accent,
cursorColor = colors.accent
)
)
StyledButton( // 保存更改按钮
icon = Icons.Default.Save, // 保存图标
text = "Save Changes", // 保存更改
onClick = { /* TODO: Save profile changes */ }, // 保存个人资料更改
colors = colors,
modifier = Modifier.fillMaxWidth(0.6f)
.align(Alignment.CenterHorizontally) // 按钮宽度为父容器的60%,并居中
)
}
}
// Overall Statistics Card
// 总体统计数据卡片
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
"Overall Statistics", // 总体统计
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold
)
)
SimpleListItem(
icon = Icons.Filled.Timer, // 计时器图标
text = "Total Time Tracked: 1250 hours", // 总追踪时间
colors = colors
)
SimpleListItem(
icon = Icons.Filled.CheckCircleOutline, // 勾选图标
text = "Goals Met Streak: 15 days", // 目标达成连胜天数
colors = colors
)
SimpleListItem(
icon = Icons.Filled.EventAvailable, // 日历图标
text = "Joined: January 1, 2024", // 加入日期
colors = colors
)
}
}
// Account Actions Card
// 账户操作卡片
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
"Account Actions", // 账户操作
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold
)
)
StyledButton( // 更改密码按钮
icon = Icons.Default.LockReset, // 密码重置图标
text = "Change Password", // 更改密码
onClick = { /* TODO */ },
colors = colors,
modifier = Modifier.fillMaxWidth()
)
StyledButton( // 删除账户按钮
icon = Icons.Default.DeleteForever, // 永久删除图标
text = "Delete Account", // 删除账户
onClick = { /* TODO: Show confirmation dialog */ }, // 显示确认对话框
colors = AppThemes.Colors( // 删除按钮使用警示性颜色主题
background = colors.background,
surface = colors.surface,
onSurface = colors.onSurface,
onBackground = colors.onBackground,
accent = Color.Red.copy(alpha = 0.7f), // 红色强调色
accentVariant = Color.Red,
border = colors.border,
secondaryText = colors.secondaryText,
onAccent = Color.White
),
modifier = Modifier.fillMaxWidth()
)
}
}
}
}
@Preview
@Composable
fun ProfileScreenPreview() {
MaterialTheme {
ProfileScreen(colors = AppThemes.LightThemeColors)
}
}
@Preview
@Composable
fun ProfileScreenDarkPreview() {
MaterialTheme {
ProfileScreen(colors = AppThemes.DarkThemeColors)
}
}

View File

@ -0,0 +1,123 @@
package com.grtsinry43.activityanalyzer.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.grtsinry43.activityanalyzer.components.ReportItem
import com.grtsinry43.activityanalyzer.components.StyledButton
import com.grtsinry43.activityanalyzer.components.StyledCard
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun ReportsScreen(colors: AppThemes.Colors) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(8.dp)
.verticalScroll(rememberScrollState()), // 允许垂直滚动
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween, // 两端对齐
verticalAlignment = Alignment.CenterVertically // 垂直居中
) {
Text(
"Screen Time Reports", // 屏幕时间报告
style = MaterialTheme.typography.headlineSmall.copy(
color = colors.onBackground,
fontWeight = FontWeight.Bold // 加粗
)
)
StyledButton(
icon = Icons.Default.Addchart, // 生成报告图标
text = "Generate New Report", // 生成新报告
onClick = { /* TODO: Open report generation dialog */ }, // 打开报告生成对话框
colors = colors
)
}
// Generated Reports List
// 已生成报告列表
StyledCard(colors = colors) { // 报告列表卡片
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp) // 列表项之间的紧凑间距
) {
Text(
"Generated Reports", // 已生成报告
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold
),
modifier = Modifier.padding(bottom = 8.dp) // 标题下方间距
)
ReportItem( // 报告项示例1
title = "Weekly Summary - May 5-11, 2025", // 每周总结
period = "Generated: May 12, 2025", // 生成日期
icon = Icons.Default.CalendarToday, // 日历图标
colors = colors,
onDownload = { /* TODO */ }, // 下载回调
onView = { /* TODO */ } // 查看回调
)
Divider(color = colors.border.copy(alpha = 0.3f)) // 分隔线
ReportItem( // 报告项示例2
title = "Monthly App Usage - April 2025", // 每月应用使用情况
period = "Generated: May 1, 2025", // 生成日期
icon = Icons.Default.PieChart, // 饼图图标
colors = colors,
onDownload = { /* TODO */ },
onView = { /* TODO */ }
)
Divider(color = colors.border.copy(alpha = 0.3f))
ReportItem( // 报告项示例3
title = "Q1 Device Pickup Analysis", // Q1设备拿起分析
period = "Generated: April 5, 2025", // 生成日期
icon = Icons.Default.TouchApp, // 触摸应用图标
colors = colors,
onDownload = { /* TODO */ },
onView = { /* TODO */ }
)
Divider(color = colors.border.copy(alpha = 0.3f))
ReportItem( // 报告项示例4
title = "Focus Session Report - Project X", // 专注时段报告 - 项目X
period = "Generated: May 10, 2025", // 生成日期
icon = Icons.Default.HourglassEmpty, // 沙漏图标
colors = colors,
onDownload = { /* TODO */ },
onView = { /* TODO */ }
)
}
}
// Placeholder if no reports
// 如果没有报告,显示占位符
// Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
// Text("No reports generated yet.", color = colors.secondaryText, style = MaterialTheme.typography.bodyLarge)
// }
}
}
@Preview
@Composable
fun ReportsScreenPreview() {
MaterialTheme {
ReportsScreen(colors = AppThemes.LightThemeColors)
}
}
@Preview
@Composable
fun ReportsScreenDarkPreview() {
MaterialTheme {
ReportsScreen(colors = AppThemes.DarkThemeColors)
}
}

View File

@ -0,0 +1,207 @@
package com.grtsinry43.activityanalyzer.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.grtsinry43.activityanalyzer.components.SettingItem
import com.grtsinry43.activityanalyzer.components.StyledCard
import com.grtsinry43.activityanalyzer.theme.AppThemes
import org.jetbrains.compose.ui.tooling.preview.Preview
@Composable
fun SettingsScreen(
colors: AppThemes.Colors, // 当前颜色主题
isDarkTheme: Boolean, // 当前是否为暗色主题
onThemeChange: (Boolean) -> Unit // 主题更改回调
) {
var autoBackupEnabled by remember { mutableStateOf(true) } // 自动备份是否启用
var notificationsEnabled by remember { mutableStateOf(true) } // 通知是否启用
var selectedTrackingApps by remember { mutableStateOf("All Apps") } // 要追踪的应用 (示例状态)
var trackingSensitivity by remember { mutableStateOf("Medium") } // 追踪灵敏度 (示例状态)
Column(
modifier = Modifier
.fillMaxSize()
.padding(8.dp)
.verticalScroll(rememberScrollState()), // 允许垂直滚动,因为设置项可能很多
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
Text(
"Application Settings", // 应用设置
style = MaterialTheme.typography.headlineSmall.copy(
color = colors.onBackground,
fontWeight = FontWeight.Bold // 加粗
)
)
// General Settings Card
// 通用设置卡片
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp) // 列表项之间的紧凑间距
) {
Text(
"General", // 通用
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold
),
modifier = Modifier.padding(bottom = 8.dp) // 标题下方间距
)
SettingItem( // 暗色主题切换
title = "Dark Theme", // 暗色主题
subtitle = if (isDarkTheme) "Enabled" else "Disabled", // 已启用/已禁用
icon = Icons.Default.Brightness6, // 主题图标
colors = colors,
showSwitch = true, // 显示切换开关
switchChecked = isDarkTheme, // 开关状态
onSwitchChange = onThemeChange // 开关切换回调
)
Divider(color = colors.border.copy(alpha = 0.3f)) // 分隔线
SettingItem( // 数据存储位置
title = "Data Storage Location", // 数据存储位置
subtitle = "/Users/grtsinry43/Documents/ActivityAnalyzer", // 示例路径
icon = Icons.Default.FolderOpen, // 打开文件夹图标
colors = colors,
onClick = { /* TODO: Open file dialog or path editor */ } // 打开文件对话框或路径编辑器
)
Divider(color = colors.border.copy(alpha = 0.3f))
SettingItem( // 自动备份
title = "Auto Backup", // 自动备份
subtitle = if (autoBackupEnabled) "Daily at 2:00 AM" else "Disabled", // 每日凌晨2点/已禁用
icon = Icons.Default.SaveAlt, // 保存图标
colors = colors,
showSwitch = true,
switchChecked = autoBackupEnabled,
onSwitchChange = { autoBackupEnabled = it }
)
Divider(color = colors.border.copy(alpha = 0.3f))
SettingItem( // 云同步
title = "Cloud Sync", // 云同步
subtitle = "Not Connected", // 未连接 (占位符)
icon = Icons.Default.CloudQueue, // 云队列图标
colors = colors,
onClick = { /* TODO: Cloud sync setup */ } // 云同步设置
)
Divider(color = colors.border.copy(alpha = 0.3f))
SettingItem( // 导出数据
title = "Export Data", // 导出数据
subtitle = "Export your activity data (CSV, JSON)", // 导出活动数据 (CSV, JSON)
icon = Icons.Default.Output, // 导出图标
colors = colors,
onClick = { /* TODO: Data export options */ } // 数据导出选项
)
}
}
// Tracking Settings Card
// 追踪设置卡片
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
"Tracking", // 追踪
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold
),
modifier = Modifier.padding(bottom = 8.dp)
)
SettingItem( // 要追踪的应用
title = "Apps to Track", // 要追踪的应用
subtitle = selectedTrackingApps, // 当前选中的应用
icon = Icons.Default.AppBlocking, // 应用阻止图标 (或类似)
colors = colors,
onClick = { /* TODO: Open app selection dialog */ } // 打开应用选择对话框
)
Divider(color = colors.border.copy(alpha = 0.3f))
SettingItem( // 追踪灵敏度
title = "Tracking Sensitivity", // 追踪灵敏度
subtitle = "Ignore app opens shorter than: $trackingSensitivity", // 忽略短于...的应用打开 (示例)
icon = Icons.Default.Tune, // 调整图标
colors = colors,
onClick = { /* TODO: Open sensitivity options */ } // 打开灵敏度选项
)
}
}
// Notification Settings Card
// 通知设置卡片
StyledCard(colors = colors) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
"Notifications", // 通知
style = MaterialTheme.typography.titleMedium.copy(
color = colors.onSurface,
fontWeight = FontWeight.SemiBold
),
modifier = Modifier.padding(bottom = 8.dp)
)
SettingItem( // 屏幕时间限制通知
title = "Screen Time Limits", // 屏幕时间限制
subtitle = "Notify when daily/app limits exceeded", // 当超出每日/应用限制时通知
icon = Icons.Default.NotificationsActive, // 活动通知图标
colors = colors,
showSwitch = true,
switchChecked = notificationsEnabled, // 示例状态
onSwitchChange = { notificationsEnabled = it }
)
Divider(color = colors.border.copy(alpha = 0.3f))
SettingItem( // 休息提醒
title = "Break Reminders", // 休息提醒
subtitle = "Get reminded to take breaks", // 获取休息提醒
icon = Icons.Default.SelfImprovement, // 自我提升图标 (代表休息)
colors = colors,
onClick = { /* TODO: Configure break reminders */ } // 配置休息提醒
)
Divider(color = colors.border.copy(alpha = 0.3f))
SettingItem( // 每周总结通知
title = "Weekly Summary Notification", // 每周总结通知
subtitle = "Receive a summary every Monday", // 每周一接收总结
icon = Icons.Default.MarkEmailRead, // 已读邮件图标
colors = colors,
showSwitch = true,
switchChecked = true, // 示例状态,默认开启
onSwitchChange = { /* TODO */ }
)
}
}
}
}
@Preview
@Composable
fun SettingsScreenPreview() {
MaterialTheme {
SettingsScreen(
colors = AppThemes.LightThemeColors,
isDarkTheme = false,
onThemeChange = {}
)
}
}
@Preview
@Composable
fun SettingsScreenDarkPreview() {
MaterialTheme {
SettingsScreen(
colors = AppThemes.DarkThemeColors,
isDarkTheme = true,
onThemeChange = {}
)
}
}

View File

@ -1,11 +1,11 @@
import SwiftUI
import Shared
// MARK: - BottomBar
struct BottomBar: View {
// Use meaningful tab names
@State private var selectedTab: Tab = .today
// Define Tabs with associated icons and names
enum Tab: CaseIterable {
case today, weekly, settings
@ -28,69 +28,61 @@ struct BottomBar: View {
var body: some View {
TabView(selection: $selectedTab) {
// Use ContentView for the "Today" tab
ContentView()
.tag(Tab.today)
.tabItem { Label(Tab.today.title, systemImage: Tab.today.iconName) }
// Placeholder for Weekly view
WeeklyView()
.tag(Tab.weekly)
.tabItem { Label(Tab.weekly.title, systemImage: Tab.weekly.iconName) }
// Placeholder for Settings view
SettingsView()
.tag(Tab.settings)
.tabItem { Label(Tab.settings.title, systemImage: Tab.settings.iconName) }
}
.accentColor(.purple) // Example: Set a custom accent color for the tab bar
.accentColor(.purple)
}
}
// --- Weekly View ---
// MARK: - WeeklyView
struct WeeklyView: View {
// Placeholder data for weekly usage (e.g., hours per day)
let weeklyData: [Double] = [3.5, 4.2, 5.1, 2.8, 6.0, 7.5, 4.8]
let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
let goalHours: Double = 8.0 // Example daily goal
let goalHours: Double = 8.0
// Calculate max value for chart scaling
var maxValue: Double {
(weeklyData.max() ?? goalHours) * 1.1 // Add 10% buffer
(weeklyData.max() ?? goalHours) * 1.1
}
var body: some View {
NavigationView {
// Use List for standard iOS layout
List {
Section("Usage Trend") {
// Bar Chart Visualization
HStack(alignment: .bottom, spacing: 15) { // Increased spacing
HStack(alignment: .bottom, spacing: 15) {
ForEach(0..<weeklyData.count, id: \.self) { index in
VStack(spacing: 4) { // Spacing within the bar stack
VStack(spacing: 4) {
Text(String(format: "%.1fh", weeklyData[index]))
.font(.caption)
.foregroundColor(.accentColor)
.lineLimit(1)
.rotationEffect(.degrees(-90)) // Rotate text for better fit if needed
.offset(y: -25) // Adjust offset if rotated
.frame(height: 50) // Give space for text
.rotationEffect(.degrees(-90))
.offset(y: -25)
.frame(height: 50)
Rectangle()
.fill(weeklyData[index] > goalHours ? Color.orange : Color.accentColor) // Highlight days over goal
// Scale height relative to the max value
.frame(height: max(1, CGFloat(weeklyData[index] / maxValue) * 150)) // Ensure min height, scale based on max
.cornerRadius(5) // Rounded corners for bars
.fill(weeklyData[index] > goalHours ? Color.orange : Color.accentColor)
.frame(height: max(1, CGFloat(weeklyData[index] / maxValue) * 150))
.cornerRadius(5)
Text(days[index])
.font(.caption)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity) // Allow bars to take equal width
.frame(maxWidth: .infinity)
}
}
.frame(height: 220) // Adjust overall chart height
.padding(.vertical) // Add padding around the chart
.frame(height: 220)
.padding(.vertical)
}
Section("Summary") {
@ -114,33 +106,54 @@ struct WeeklyView: View {
}
}
}
.listStyle(.insetGrouped) // Use inset grouped style
.listStyle(.insetGrouped)
.navigationTitle("Weekly Stats")
}
.navigationViewStyle(StackNavigationViewStyle())
}
// Helper function to calculate weekly average
func calculateWeeklyAverage() -> String {
let totalHours = weeklyData.reduce(0, +)
let averageHours = totalHours / Double(weeklyData.count)
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute]
formatter.unitsStyle = .abbreviated
// Convert hours to seconds for formatter
return formatter.string(from: TimeInterval(averageHours * 3600)) ?? "0m"
}
// Helper property for days over goal
var daysOverGoalCount: Int {
weeklyData.filter { $0 > goalHours }.count
}
}
// --- Settings View ---
// MARK: - GreetingViewModel
class GreetingViewModel: ObservableObject {
@Published var greetingText = "加载中..."
private let greeting = Greeting()
func loadGreeting() {
greetingText = "加载中..."
Task {
do {
let result = try await greeting.greet()
await MainActor.run {
self.greetingText = result
}
} catch {
await MainActor.run {
self.greetingText = "错误: \(error.localizedDescription)"
}
}
}
}
}
// MARK: - SettingsView
struct SettingsView: View {
@StateObject private var viewModel = GreetingViewModel()
@State private var usageReminders = true
@State private var darkModeEnabled = false // Example state
@State private var darkModeEnabled = false
@State private var selectedLimit = "No Limit"
let appLimits = ["No Limit", "1 hour", "2 hours", "Custom"]
@ -149,7 +162,7 @@ struct SettingsView: View {
Form {
Section("Notifications") {
Toggle("Usage Reminders", isOn: $usageReminders)
Toggle("Goal Achievement Alerts", isOn: .constant(false)) // Added toggle
Toggle("Goal Achievement Alerts", isOn: .constant(false))
}
Section("Usage Limits") {
@ -158,11 +171,11 @@ struct SettingsView: View {
Text($0)
}
}
NavigationLink("App Specific Limits", destination: Text("App Limits Detail (Placeholder)")) // Added link
NavigationLink("App Specific Limits", destination: Text("App Limits Detail (Placeholder)"))
}
Section("Appearance") {
Toggle("Dark Mode", isOn: $darkModeEnabled) // Added toggle
Toggle("Dark Mode", isOn: $darkModeEnabled)
NavigationLink("Accent Color", destination: Text("Color Picker (Placeholder)"))
}
@ -175,11 +188,21 @@ struct SettingsView: View {
}
NavigationLink("Privacy Policy", destination: Text("Privacy Policy Details (Placeholder)"))
}
Section("test") {
Text("Hello From Kotlin Multiplatform: \n\(Greeting().greet()) ")
Section("Kotlin Multiplatform 测试") {
Text("来自KMP的问候:\n\(viewModel.greetingText)")
.padding(.vertical, 8)
Button("重新加载问候语") {
viewModel.loadGreeting()
}
.foregroundColor(.accentColor)
}
}
.navigationTitle("Settings")
.onAppear {
viewModel.loadGreeting()
}
}
.navigationViewStyle(StackNavigationViewStyle())
}