Dev (#1)
This commit is contained in:
parent
9550889bdf
commit
dad89bcc81
173
.github/workflows/build.yml
vendored
173
.github/workflows/build.yml
vendored
@ -1,46 +1,159 @@
|
|||||||
name: Build and Release
|
name: KMP Build & Package
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches: [ main, dev ] # 在推送到 main 或 develop 分支时触发
|
||||||
tags:
|
tags:
|
||||||
- 'v*.*.*' # 仅在推送符合语义化版本的标签时触发
|
- 'v*' # 在推送版本标签时触发 (例如 v1.0.0)
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, dev ] # 在向 main 或 develop 分支发起 Pull Request 时触发
|
||||||
|
workflow_dispatch: # 允许手动触发
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build All Targets
|
strategy:
|
||||||
runs-on: ubuntu-latest
|
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:
|
steps:
|
||||||
# 检出代码
|
- name: Checkout repository
|
||||||
- name: Checkout code
|
uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
# 设置 JDK 环境
|
- name: Set up JDK 17 # KMP 通常建议使用较新的 JDK 版本
|
||||||
- name: Set up JDK
|
uses: actions/setup-java@v4
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: 'temurin' # 或者 'zulu', 'adopt' 等
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
|
|
||||||
# 构建 Android APK
|
# 仅在 macOS runner 上为 iOS 构建设置 Xcode 版本 (如果需要特定版本)
|
||||||
- name: Build Android APK
|
- name: Select Xcode version (for iOS builds)
|
||||||
run: ./gradlew :androidApp:assembleRelease
|
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
|
||||||
|
|
||||||
# 构建 iOS Framework
|
# 设置 Gradle (会自动处理 Gradle Wrapper)
|
||||||
- name: Build iOS Framework
|
- name: Setup Gradle
|
||||||
run: ./gradlew :iosApp:build
|
uses: gradle/actions/setup-gradle@v3 # 使用 v3 版本
|
||||||
|
with:
|
||||||
# 构建 Desktop 可执行文件
|
gradle-version: wrapper # 默认使用 Gradle Wrapper
|
||||||
- name: Build Desktop Executable
|
# cache-read-only: ${{ github.ref != 'refs/heads/main' }} # 非 main 分支构建时,缓存只读 (可选)
|
||||||
run: ./gradlew :desktopApp:packageRelease
|
# cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} # 加密缓存 (可选)
|
||||||
|
|
||||||
# 上传构建产物到 GitHub Releases
|
# Gradle 缓存 (KMP 项目的 .konan 目录和 Gradle 缓存很重要)
|
||||||
- name: Upload Release Assets
|
- name: Cache Gradle packages
|
||||||
uses: actions/upload-release-asset@v2
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
name: ${{github.ref_name}}-build
|
|
||||||
path: |
|
path: |
|
||||||
androidApp/build/outputs/apk/release/*.apk
|
~/.gradle/caches
|
||||||
iosApp/build/bin/ios/*.framework
|
~/.gradle/wrapper
|
||||||
desktopApp/build/outputs/*.exe
|
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
label: "Build for ${{ github.ref_name }}"
|
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 # 如果没有找到文件则报错
|
||||||
@ -3,10 +3,12 @@ package com.grtsinry43.activityanalyzer
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@ -16,6 +18,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun AppAndroidPreview() {
|
fun AppAndroidPreview() {
|
||||||
|
|||||||
@ -1,42 +1,190 @@
|
|||||||
package com.grtsinry43.activityanalyzer
|
package com.grtsinry43.activityanalyzer
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import org.jetbrains.compose.resources.painterResource
|
import androidx.compose.ui.draw.clip
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import activityanalyzer.composeapp.generated.resources.Res
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import activityanalyzer.composeapp.generated.resources.compose_multiplatform
|
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
|
@Composable
|
||||||
@Preview
|
@ExperimentalMaterial3Api
|
||||||
fun App() {
|
fun App() {
|
||||||
MaterialTheme {
|
var isDarkTheme by remember { mutableStateOf(false) }
|
||||||
var showContent by remember { mutableStateOf(false) }
|
val currentColors = if (isDarkTheme) AppThemes.DarkThemeColors else AppThemes.LightThemeColors
|
||||||
var greetingText by remember { mutableStateOf("加载中...") }
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = Unit) {
|
// Navigation state
|
||||||
greetingText = try {
|
var selectedNavItemIndex by remember { mutableStateOf(0) }
|
||||||
Greeting().greet()
|
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||||
} catch (e: Exception) {
|
val scope = rememberCoroutineScope()
|
||||||
"错误:${e.message}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
val navItems = listOf(
|
||||||
Button(onClick = { showContent = !showContent }) {
|
"Home" to Icons.Default.Home,
|
||||||
Text("Click me!")
|
"Analytics" to Icons.Default.BarChart,
|
||||||
|
"Reports" to Icons.Default.Assessment,
|
||||||
|
"Profile" to Icons.Default.AccountCircle,
|
||||||
|
"Settings" to Icons.Default.Settings,
|
||||||
|
"About" to Icons.Default.Info
|
||||||
|
)
|
||||||
|
|
||||||
|
val currentScreenTitle = navItems[selectedNavItemIndex].first
|
||||||
|
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = if (isDarkTheme) darkColorScheme(
|
||||||
|
primary = currentColors.accent,
|
||||||
|
secondary = currentColors.accentVariant,
|
||||||
|
background = currentColors.background,
|
||||||
|
surface = currentColors.surface,
|
||||||
|
onPrimary = currentColors.onAccent,
|
||||||
|
onSecondary = currentColors.onAccent,
|
||||||
|
onBackground = currentColors.onBackground,
|
||||||
|
onSurface = currentColors.onSurface,
|
||||||
|
) else lightColorScheme(
|
||||||
|
primary = currentColors.accent,
|
||||||
|
secondary = currentColors.accentVariant,
|
||||||
|
background = currentColors.background,
|
||||||
|
surface = currentColors.surface,
|
||||||
|
onPrimary = currentColors.onAccent,
|
||||||
|
onSecondary = currentColors.onAccent,
|
||||||
|
onBackground = currentColors.onBackground,
|
||||||
|
onSurface = currentColors.onSurface,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ModalNavigationDrawer(
|
||||||
|
drawerState = drawerState,
|
||||||
|
drawerContent = {
|
||||||
|
ModalDrawerSheet(
|
||||||
|
drawerContainerColor = currentColors.surface,
|
||||||
|
drawerContentColor = currentColors.onSurface
|
||||||
|
) {
|
||||||
|
// Drawer Header with User Info
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(currentColors.accent.copy(alpha = 0.1f))
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.AccountCircle,
|
||||||
|
contentDescription = "User Avatar",
|
||||||
|
tint = currentColors.accent,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(64.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "grtsinry43", // Placeholder Nickname
|
||||||
|
color = currentColors.onSurface,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "grtsinry43@outlook.com", // Placeholder Email
|
||||||
|
color = currentColors.secondaryText,
|
||||||
|
fontSize = 12.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
|
// Navigation Items
|
||||||
|
navItems.forEachIndexed { index, item ->
|
||||||
|
NavigationDrawerItem(
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
item.second,
|
||||||
|
contentDescription = item.first,
|
||||||
|
tint = if (selectedNavItemIndex == index) currentColors.accent else currentColors.onSurface.copy(
|
||||||
|
alpha = 0.7f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
item.first,
|
||||||
|
color = if (selectedNavItemIndex == index) currentColors.accent else currentColors.onSurface.copy(
|
||||||
|
alpha = 0.7f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
selected = selectedNavItemIndex == index,
|
||||||
|
onClick = {
|
||||||
|
selectedNavItemIndex = index
|
||||||
|
scope.launch { drawerState.close() }
|
||||||
|
},
|
||||||
|
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding),
|
||||||
|
colors = NavigationDrawerItemDefaults.colors(
|
||||||
|
selectedContainerColor = currentColors.accent.copy(alpha = 0.1f),
|
||||||
|
unselectedContainerColor = Color.Transparent,
|
||||||
|
selectedTextColor = currentColors.accent,
|
||||||
|
unselectedTextColor = currentColors.onSurface.copy(alpha = 0.7f),
|
||||||
|
selectedIconColor = currentColors.accent,
|
||||||
|
unselectedIconColor = currentColors.onSurface.copy(alpha = 0.7f)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
containerColor = currentColors.background,
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = { Text(currentScreenTitle, color = currentColors.onSurface) },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = { scope.launch { drawerState.open() } }) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Menu,
|
||||||
|
contentDescription = "Open Navigation Drawer",
|
||||||
|
tint = currentColors.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
containerColor = currentColors.surface,
|
||||||
|
titleContentColor = currentColors.onSurface
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(paddingValues).fillMaxSize()
|
||||||
|
.background(currentColors.background)
|
||||||
|
) {
|
||||||
|
when (selectedNavItemIndex) {
|
||||||
|
0 -> MobileHomeScreen(colors = currentColors)
|
||||||
|
1 -> MobileAnalyticsScreen(colors = currentColors)
|
||||||
|
2 -> MobileReportsScreen(colors = currentColors)
|
||||||
|
3 -> MobileProfileScreen(colors = currentColors)
|
||||||
|
4 -> MobileSettingsScreen(
|
||||||
|
colors = currentColors,
|
||||||
|
isDarkTheme = isDarkTheme,
|
||||||
|
onThemeChange = { isDarkTheme = it }
|
||||||
|
)
|
||||||
|
|
||||||
|
5 -> MobileAboutScreen(colors = currentColors)
|
||||||
}
|
}
|
||||||
AnimatedVisibility(showContent) {
|
|
||||||
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
Image(painterResource(Res.drawable.compose_multiplatform), null)
|
|
||||||
Text("Compose: $greetingText")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 = {})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,11 +1,9 @@
|
|||||||
package com.grtsinry43.activityanalyzer
|
package com.grtsinry43.activityanalyzer
|
||||||
|
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
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.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
@ -14,633 +12,171 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
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.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material3.SwitchDefaults
|
import com.grtsinry43.activityanalyzer.components.*
|
||||||
import androidx.compose.material3.TextButton
|
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
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun DesktopApp() {
|
fun DesktopApp() {
|
||||||
var selectedItem by remember { mutableStateOf(0) }
|
var selectedItem by remember { mutableStateOf(0) } // 记住当前选中的导航项索引
|
||||||
var isDarkTheme by remember { mutableStateOf(false) }
|
var isDarkTheme by remember { mutableStateOf(false) } // 记住当前是否为暗色主题
|
||||||
var isSidebarCollapsed by remember { mutableStateOf(false) } // 新增状态变量
|
var isSidebarCollapsed by remember { mutableStateOf(false) } // 记住侧边栏是否折叠
|
||||||
|
|
||||||
val backgroundColor = if (isDarkTheme) Color(0xFF1E1E1E) else Color(0xFFF5F5F5)
|
val currentColors =
|
||||||
val surfaceColor = if (isDarkTheme) Color(0xFF2D2D2D) else Color.White
|
if (isDarkTheme) AppThemes.DarkThemeColors else AppThemes.LightThemeColors // 根据主题选择颜色
|
||||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
|
||||||
val accentColor = Color(0xFF007AFF)
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize() // 填充整个可用空间
|
||||||
.background(backgroundColor)
|
.background(currentColors.background) // 设置背景色
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize() // 主行布局,填充整个可用空间
|
||||||
) {
|
) {
|
||||||
// Sidebar
|
// Sidebar
|
||||||
|
// 侧边栏
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(if (isSidebarCollapsed) 60.dp else 200.dp) // 动态宽度
|
.width(if (isSidebarCollapsed) 70.dp else 240.dp) // 根据折叠状态调整宽度
|
||||||
.fillMaxHeight()
|
.fillMaxHeight() // 填充整个高度
|
||||||
.background(surfaceColor)
|
.background(currentColors.surface) // 设置侧边栏背景色
|
||||||
.padding(16.dp)
|
.padding(
|
||||||
|
start = 8.dp,
|
||||||
|
end = 8.dp,
|
||||||
|
top = 16.dp,
|
||||||
|
bottom = 16.dp
|
||||||
|
) // 设置内边距
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxHeight(), // 列布局,填充整个高度
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp) // 子项垂直间距
|
||||||
) {
|
) {
|
||||||
if (!isSidebarCollapsed) {
|
if (!isSidebarCollapsed) {
|
||||||
Text(
|
Text(
|
||||||
"Activity Analyzer",
|
"Activity Analyzer", // 应用标题
|
||||||
fontSize = 16.sp,
|
fontSize = 18.sp, // 字体大小
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.SemiBold, // 字体粗细
|
||||||
color = textColor
|
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
|
// Navigation items
|
||||||
NavItem(
|
// 导航项列表
|
||||||
icon = Icons.Default.Home,
|
val navItems = listOf(
|
||||||
text = if (isSidebarCollapsed) "" else "Home", // 根据收缩状态显示文本
|
"Home" to Icons.Default.Home, // 首页
|
||||||
isSelected = selectedItem == 0,
|
"Analytics" to Icons.Default.BarChart, // 分析页
|
||||||
onClick = { selectedItem = 0 },
|
"Reports" to Icons.Default.Assessment, // 报告页 (图标已更改)
|
||||||
textColor = textColor,
|
"Profile" to Icons.Default.AccountCircle, // 个人资料页
|
||||||
accentColor = accentColor,
|
"Settings" to Icons.Default.Settings, // 设置页
|
||||||
isSidebarCollapsed = isSidebarCollapsed
|
"About" to Icons.Default.Info // 关于页
|
||||||
)
|
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
navItems.forEachIndexed { index, (text, icon) ->
|
||||||
|
NavItem(
|
||||||
|
icon = icon,
|
||||||
|
text = if (isSidebarCollapsed) "" else text, // 折叠时不显示文本
|
||||||
|
isSelected = selectedItem == index, // 判断是否选中
|
||||||
|
onClick = { selectedItem = index }, // 点击事件,更新选中项
|
||||||
|
colors = currentColors,
|
||||||
|
isSidebarCollapsed = isSidebarCollapsed
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Theme switch
|
Spacer(modifier = Modifier.weight(1f)) // 弹性空间,将后续内容推到底部
|
||||||
|
|
||||||
|
// User Profile Section
|
||||||
|
// 用户资料区域 (侧边栏底部)
|
||||||
if (!isSidebarCollapsed) {
|
if (!isSidebarCollapsed) {
|
||||||
Row(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||||
.background(if (isDarkTheme) Color(0xFF3D3D3D) else Color(0xFFE8E8E8))
|
horizontalAlignment = Alignment.CenterHorizontally // 水平居中对齐
|
||||||
.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)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collapse/Expand button
|
|
||||||
IconButton(
|
|
||||||
onClick = { isSidebarCollapsed = !isSidebarCollapsed },
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = if (isSidebarCollapsed) Icons.Default.ChevronRight else Icons.Default.ChevronLeft,
|
imageVector = Icons.Default.AccountCircle, // 用户头像图标
|
||||||
contentDescription = "Toggle Sidebar",
|
contentDescription = "User Avatar", // 内容描述
|
||||||
tint = textColor
|
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) // 水平居中
|
||||||
|
.size(40.dp) // 按钮大小
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isSidebarCollapsed) Icons.Default.MenuOpen else Icons.Default.Menu, // 根据状态选择图标
|
||||||
|
contentDescription = "Toggle Sidebar", // 内容描述
|
||||||
|
tint = currentColors.onSurface // 图标颜色
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main content
|
// Main content
|
||||||
|
// 主内容区域
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize() // 填充整个可用空间
|
||||||
.padding(24.dp)
|
.padding(24.dp) // 内边距
|
||||||
) {
|
) {
|
||||||
|
// 根据选中的导航项显示不同的屏幕内容
|
||||||
when (selectedItem) {
|
when (selectedItem) {
|
||||||
0 -> HomeScreen(textColor = textColor, surfaceColor = surfaceColor)
|
0 -> HomeScreen(colors = currentColors)
|
||||||
1 -> AnalyticsScreen(textColor = textColor, surfaceColor = surfaceColor)
|
1 -> AnalyticsScreen(colors = currentColors)
|
||||||
2 -> ReportsScreen(textColor = textColor, surfaceColor = surfaceColor)
|
2 -> ReportsScreen(colors = currentColors)
|
||||||
3 -> SettingsScreen(textColor = textColor, surfaceColor = surfaceColor)
|
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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,120 @@
|
|||||||
package com.grtsinry43.activityanalyzer
|
package com.grtsinry43.activityanalyzer
|
||||||
|
|
||||||
import androidx.compose.ui.window.Window
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.ui.window.application
|
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 {
|
fun main() = application {
|
||||||
|
// 管理窗口状态,例如可见性、位置、大小
|
||||||
|
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(
|
Window(
|
||||||
onCloseRequest = ::exitApplication,
|
onCloseRequest = {
|
||||||
|
// 点击关闭按钮时,可以选择隐藏到托盘而不是直接退出
|
||||||
|
isWindowVisible = false
|
||||||
|
// 或者,如果你希望点击关闭直接退出应用,则使用:
|
||||||
|
// exitApplication()
|
||||||
|
},
|
||||||
|
state = windowState, // 应用窗口状态
|
||||||
title = "Activity Analyzer",
|
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()
|
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()
|
||||||
}
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,11 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Shared
|
import Shared
|
||||||
|
|
||||||
|
// MARK: - BottomBar
|
||||||
struct BottomBar: View {
|
struct BottomBar: View {
|
||||||
// Use meaningful tab names
|
|
||||||
@State private var selectedTab: Tab = .today
|
@State private var selectedTab: Tab = .today
|
||||||
|
|
||||||
// Define Tabs with associated icons and names
|
|
||||||
enum Tab: CaseIterable {
|
enum Tab: CaseIterable {
|
||||||
case today, weekly, settings
|
case today, weekly, settings
|
||||||
|
|
||||||
@ -28,69 +28,61 @@ struct BottomBar: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TabView(selection: $selectedTab) {
|
TabView(selection: $selectedTab) {
|
||||||
// Use ContentView for the "Today" tab
|
|
||||||
ContentView()
|
ContentView()
|
||||||
.tag(Tab.today)
|
.tag(Tab.today)
|
||||||
.tabItem { Label(Tab.today.title, systemImage: Tab.today.iconName) }
|
.tabItem { Label(Tab.today.title, systemImage: Tab.today.iconName) }
|
||||||
|
|
||||||
// Placeholder for Weekly view
|
|
||||||
WeeklyView()
|
WeeklyView()
|
||||||
.tag(Tab.weekly)
|
.tag(Tab.weekly)
|
||||||
.tabItem { Label(Tab.weekly.title, systemImage: Tab.weekly.iconName) }
|
.tabItem { Label(Tab.weekly.title, systemImage: Tab.weekly.iconName) }
|
||||||
|
|
||||||
// Placeholder for Settings view
|
|
||||||
SettingsView()
|
SettingsView()
|
||||||
.tag(Tab.settings)
|
.tag(Tab.settings)
|
||||||
.tabItem { Label(Tab.settings.title, systemImage: Tab.settings.iconName) }
|
.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 {
|
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 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 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 {
|
var maxValue: Double {
|
||||||
(weeklyData.max() ?? goalHours) * 1.1 // Add 10% buffer
|
(weeklyData.max() ?? goalHours) * 1.1
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
// Use List for standard iOS layout
|
|
||||||
List {
|
List {
|
||||||
Section("Usage Trend") {
|
Section("Usage Trend") {
|
||||||
// Bar Chart Visualization
|
HStack(alignment: .bottom, spacing: 15) {
|
||||||
HStack(alignment: .bottom, spacing: 15) { // Increased spacing
|
|
||||||
ForEach(0..<weeklyData.count, id: \.self) { index in
|
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]))
|
Text(String(format: "%.1fh", weeklyData[index]))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.rotationEffect(.degrees(-90)) // Rotate text for better fit if needed
|
.rotationEffect(.degrees(-90))
|
||||||
.offset(y: -25) // Adjust offset if rotated
|
.offset(y: -25)
|
||||||
.frame(height: 50) // Give space for text
|
.frame(height: 50)
|
||||||
|
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.fill(weeklyData[index] > goalHours ? Color.orange : Color.accentColor) // Highlight days over goal
|
.fill(weeklyData[index] > goalHours ? Color.orange : Color.accentColor)
|
||||||
// Scale height relative to the max value
|
.frame(height: max(1, CGFloat(weeklyData[index] / maxValue) * 150))
|
||||||
.frame(height: max(1, CGFloat(weeklyData[index] / maxValue) * 150)) // Ensure min height, scale based on max
|
.cornerRadius(5)
|
||||||
.cornerRadius(5) // Rounded corners for bars
|
|
||||||
|
|
||||||
Text(days[index])
|
Text(days[index])
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity) // Allow bars to take equal width
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: 220) // Adjust overall chart height
|
.frame(height: 220)
|
||||||
.padding(.vertical) // Add padding around the chart
|
.padding(.vertical)
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("Summary") {
|
Section("Summary") {
|
||||||
@ -114,33 +106,54 @@ struct WeeklyView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.listStyle(.insetGrouped) // Use inset grouped style
|
.listStyle(.insetGrouped)
|
||||||
.navigationTitle("Weekly Stats")
|
.navigationTitle("Weekly Stats")
|
||||||
}
|
}
|
||||||
.navigationViewStyle(StackNavigationViewStyle())
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to calculate weekly average
|
|
||||||
func calculateWeeklyAverage() -> String {
|
func calculateWeeklyAverage() -> String {
|
||||||
let totalHours = weeklyData.reduce(0, +)
|
let totalHours = weeklyData.reduce(0, +)
|
||||||
let averageHours = totalHours / Double(weeklyData.count)
|
let averageHours = totalHours / Double(weeklyData.count)
|
||||||
let formatter = DateComponentsFormatter()
|
let formatter = DateComponentsFormatter()
|
||||||
formatter.allowedUnits = [.hour, .minute]
|
formatter.allowedUnits = [.hour, .minute]
|
||||||
formatter.unitsStyle = .abbreviated
|
formatter.unitsStyle = .abbreviated
|
||||||
// Convert hours to seconds for formatter
|
|
||||||
return formatter.string(from: TimeInterval(averageHours * 3600)) ?? "0m"
|
return formatter.string(from: TimeInterval(averageHours * 3600)) ?? "0m"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper property for days over goal
|
|
||||||
var daysOverGoalCount: Int {
|
var daysOverGoalCount: Int {
|
||||||
weeklyData.filter { $0 > goalHours }.count
|
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 {
|
struct SettingsView: View {
|
||||||
|
@StateObject private var viewModel = GreetingViewModel()
|
||||||
@State private var usageReminders = true
|
@State private var usageReminders = true
|
||||||
@State private var darkModeEnabled = false // Example state
|
@State private var darkModeEnabled = false
|
||||||
@State private var selectedLimit = "No Limit"
|
@State private var selectedLimit = "No Limit"
|
||||||
let appLimits = ["No Limit", "1 hour", "2 hours", "Custom"]
|
let appLimits = ["No Limit", "1 hour", "2 hours", "Custom"]
|
||||||
|
|
||||||
@ -149,7 +162,7 @@ struct SettingsView: View {
|
|||||||
Form {
|
Form {
|
||||||
Section("Notifications") {
|
Section("Notifications") {
|
||||||
Toggle("Usage Reminders", isOn: $usageReminders)
|
Toggle("Usage Reminders", isOn: $usageReminders)
|
||||||
Toggle("Goal Achievement Alerts", isOn: .constant(false)) // Added toggle
|
Toggle("Goal Achievement Alerts", isOn: .constant(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
Section("Usage Limits") {
|
Section("Usage Limits") {
|
||||||
@ -158,11 +171,11 @@ struct SettingsView: View {
|
|||||||
Text($0)
|
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") {
|
Section("Appearance") {
|
||||||
Toggle("Dark Mode", isOn: $darkModeEnabled) // Added toggle
|
Toggle("Dark Mode", isOn: $darkModeEnabled)
|
||||||
NavigationLink("Accent Color", destination: Text("Color Picker (Placeholder)"))
|
NavigationLink("Accent Color", destination: Text("Color Picker (Placeholder)"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,11 +188,21 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
NavigationLink("Privacy Policy", destination: Text("Privacy Policy Details (Placeholder)"))
|
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")
|
.navigationTitle("Settings")
|
||||||
|
.onAppear {
|
||||||
|
viewModel.loadGreeting()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.navigationViewStyle(StackNavigationViewStyle())
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user