Dev (#1)
This commit is contained in:
parent
9550889bdf
commit
dad89bcc81
179
.github/workflows/build.yml
vendored
179
.github/workflows/build.yml
vendored
@ -1,46 +1,159 @@
|
||||
name: Build and Release
|
||||
name: KMP Build & Package
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, dev ] # 在推送到 main 或 develop 分支时触发
|
||||
tags:
|
||||
- 'v*.*.*' # 仅在推送符合语义化版本的标签时触发
|
||||
- 'v*' # 在推送版本标签时触发 (例如 v1.0.0)
|
||||
pull_request:
|
||||
branches: [ main, dev ] # 在向 main 或 develop 分支发起 Pull Request 时触发
|
||||
workflow_dispatch: # 允许手动触发
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build All Targets
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false # 即使一个 job 失败,其他 job 也会继续运行
|
||||
matrix:
|
||||
os: [ ubuntu-latest, macos-latest, windows-latest ] # 根据你的目标平台选择操作系统
|
||||
include:
|
||||
# Android 构建 (通常在 Linux 环境下进行)
|
||||
- os: ubuntu-latest
|
||||
target: android
|
||||
description: "Build Android App"
|
||||
gradle_task: ":shared:assembleRelease :androidApp:assembleRelease" # 替换成你的 Android 打包任务
|
||||
artifact_name: "android-app"
|
||||
artifact_path: |
|
||||
shared/build/outputs/aar/*.aar
|
||||
androidApp/build/outputs/apk/release/*.apk
|
||||
androidApp/build/outputs/bundle/release/*.aab
|
||||
# iOS 构建 (必须在 macOS 环境下进行)
|
||||
- os: macos-latest
|
||||
target: ios
|
||||
description: "Build iOS Framework"
|
||||
# 注意: iOS 构建可能需要特定的 Xcode 版本,可以使用 actions/setup-xcode
|
||||
# gradle_task: ":shared:packForXCFramework" # 常见的 iOS framework 打包任务
|
||||
gradle_task: ":shared:assembleXCFramework" # 或者使用新的 Kotlin 插件的任务名
|
||||
artifact_name: "ios-framework"
|
||||
artifact_path: "shared/build/XCFrameworks/release/*.xcframework" # 检查你的实际输出路径
|
||||
# JVM/Desktop 构建 (可以在多个操作系统上运行)
|
||||
- os: ubuntu-latest # 为 Linux 构建
|
||||
target: jvm_linux
|
||||
description: "Build JVM/Desktop (Linux)"
|
||||
gradle_task: ":desktopApp:packageDistributionForCurrentOS" # 替换成你的 Desktop 打包任务
|
||||
artifact_name: "desktop-app-linux"
|
||||
artifact_path: "desktopApp/build/compose/binaries/main/app/*" # 检查你的实际输出路径
|
||||
- os: macos-latest # 为 macOS 构建
|
||||
target: jvm_macos
|
||||
description: "Build JVM/Desktop (macOS)"
|
||||
gradle_task: ":desktopApp:packageDistributionForCurrentOS"
|
||||
artifact_name: "desktop-app-macos"
|
||||
artifact_path: "desktopApp/build/compose/binaries/main/app/*" # 检查你的实际输出路径
|
||||
- os: windows-latest # 为 Windows 构建
|
||||
target: jvm_windows
|
||||
description: "Build JVM/Desktop (Windows)"
|
||||
gradle_task: ":desktopApp:packageDistributionForCurrentOS"
|
||||
artifact_name: "desktop-app-windows"
|
||||
artifact_path: "desktopApp/build/compose/binaries/main/app/*" # 检查你的实际输出路径
|
||||
# JavaScript 构建 (通常在 Linux 环境下进行)
|
||||
- os: ubuntu-latest
|
||||
target: js
|
||||
description: "Build JavaScript"
|
||||
gradle_task: ":shared:jsBrowserDistribution" # 替换成你的 JS 打包任务
|
||||
artifact_name: "js-app"
|
||||
artifact_path: "shared/build/distributions/*" # 检查你的实际输出路径
|
||||
# Linux Native 构建
|
||||
- os: ubuntu-latest
|
||||
target: linux_native
|
||||
description: "Build Linux Native"
|
||||
gradle_task: ":shared:linkReleaseExecutableLinuxX64" # 替换成你的 Linux Native 编译任务
|
||||
artifact_name: "linux-native-executable"
|
||||
artifact_path: "shared/build/bin/linuxX64/releaseExecutable/*.kexe" # 检查你的实际输出路径
|
||||
# macOS Native 构建 (也可以针对 arm64)
|
||||
- os: macos-latest
|
||||
target: macos_native
|
||||
description: "Build macOS Native"
|
||||
gradle_task: ":shared:linkReleaseExecutableMacosX64" # 或者 linkReleaseExecutableMacosArm64
|
||||
artifact_name: "macos-native-executable"
|
||||
artifact_path: "shared/build/bin/macosX64/releaseExecutable/*.kexe" # 检查你的实际输出路径
|
||||
# Windows Native 构建
|
||||
- os: windows-latest
|
||||
target: windows_native
|
||||
description: "Build Windows Native"
|
||||
gradle_task: ":shared:linkReleaseExecutableMingwX64" # 替换成你的 Windows Native 编译任务
|
||||
artifact_name: "windows-native-executable"
|
||||
artifact_path: "shared/build/bin/mingwX64/releaseExecutable/*.exe" # 检查你的实际输出路径
|
||||
|
||||
# 如果某些平台不需要在所有操作系统上构建,可以在这里排除
|
||||
# exclude:
|
||||
# - os: windows-latest
|
||||
# target: android
|
||||
# - os: ubuntu-latest
|
||||
# target: ios
|
||||
|
||||
name: ${{ matrix.description }} on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
# 检出代码
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# 设置 JDK 环境
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v3
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17 # KMP 通常建议使用较新的 JDK 版本
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
distribution: 'temurin' # 或者 'zulu', 'adopt' 等
|
||||
java-version: '17'
|
||||
|
||||
# 构建 Android APK
|
||||
- name: Build Android APK
|
||||
run: ./gradlew :androidApp:assembleRelease
|
||||
|
||||
# 构建 iOS Framework
|
||||
- name: Build iOS Framework
|
||||
run: ./gradlew :iosApp:build
|
||||
|
||||
# 构建 Desktop 可执行文件
|
||||
- name: Build Desktop Executable
|
||||
run: ./gradlew :desktopApp:packageRelease
|
||||
|
||||
# 上传构建产物到 GitHub Releases
|
||||
- name: Upload Release Assets
|
||||
uses: actions/upload-release-asset@v2
|
||||
|
||||
# 仅在 macOS runner 上为 iOS 构建设置 Xcode 版本 (如果需要特定版本)
|
||||
- name: Select Xcode version (for iOS builds)
|
||||
if: matrix.os == 'macos-latest' && matrix.target == 'ios'
|
||||
run: sudo xcode-select -s /Applications/Xcode_15.3.app/Contents/Developer # 替换成你需要的 Xcode 版本路径
|
||||
# 你也可以使用 action 如 maxim-lobanov/setup-xcode@v1
|
||||
|
||||
# 设置 Gradle (会自动处理 Gradle Wrapper)
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v3 # 使用 v3 版本
|
||||
with:
|
||||
gradle-version: wrapper # 默认使用 Gradle Wrapper
|
||||
# cache-read-only: ${{ github.ref != 'refs/heads/main' }} # 非 main 分支构建时,缓存只读 (可选)
|
||||
# cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }} # 加密缓存 (可选)
|
||||
|
||||
# Gradle 缓存 (KMP 项目的 .konan 目录和 Gradle 缓存很重要)
|
||||
- name: Cache Gradle packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
name: ${{github.ref_name}}-build
|
||||
path: |
|
||||
androidApp/build/outputs/apk/release/*.apk
|
||||
iosApp/build/bin/ios/*.framework
|
||||
desktopApp/build/outputs/*.exe
|
||||
label: "Build for ${{ github.ref_name }}"
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
- name: Cache Kotlin Native ($HOME/.konan)
|
||||
if: matrix.os == 'macos-latest' || matrix.os == 'ubuntu-latest' # .konan 缓存主要用于 Native 编译
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.konan
|
||||
key: ${{ runner.os }}-konan-${{ hashFiles('**/build.gradle.kts', '**/gradle.properties') }} # 或者更精确的 hash
|
||||
restore-keys: |
|
||||
${{ runner.os }}-konan-
|
||||
|
||||
# 授予 Gradle Wrapper 执行权限 (特别是对于 Windows)
|
||||
- name: Make gradlew executable
|
||||
if: runner.os != 'windows-latest'
|
||||
run: chmod +x ./gradlew
|
||||
- name: Make gradlew.bat executable (Windows)
|
||||
if: runner.os == 'windows-latest'
|
||||
run: cmd /c "chmod +x gradlew.bat" # 在 Windows 上,通常 gradlew.bat 默认就有执行权限,此步骤可能非必需
|
||||
|
||||
# 执行构建任务
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew ${{ matrix.gradle_task }} --no-daemon # --no-daemon 可以在 CI 环境中更稳定
|
||||
|
||||
# 上传构建产物
|
||||
# 注意: actions/upload-artifact@v4 的用法与 v3 不同
|
||||
- name: Upload Artifact - ${{ matrix.artifact_name }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact_name }}
|
||||
path: ${{ matrix.artifact_path }}
|
||||
if-no-files-found: error # 如果没有找到文件则报错
|
||||
@ -3,10 +3,12 @@ package com.grtsinry43.activityanalyzer
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@ -16,6 +18,7 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Preview
|
||||
@Composable
|
||||
fun AppAndroidPreview() {
|
||||
|
||||
@ -1,44 +1,192 @@
|
||||
package com.grtsinry43.activityanalyzer
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
|
||||
import activityanalyzer.composeapp.generated.resources.Res
|
||||
import activityanalyzer.composeapp.generated.resources.compose_multiplatform
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.grtsinry43.activityanalyzer.screens.MobileAboutScreen
|
||||
import com.grtsinry43.activityanalyzer.screens.MobileAnalyticsScreen
|
||||
import com.grtsinry43.activityanalyzer.screens.MobileHomeScreen
|
||||
import com.grtsinry43.activityanalyzer.screens.MobileProfileScreen
|
||||
import com.grtsinry43.activityanalyzer.screens.MobileReportsScreen
|
||||
import com.grtsinry43.activityanalyzer.screens.MobileSettingsScreen
|
||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
// --- Main App Composable ---
|
||||
@Composable
|
||||
@Preview
|
||||
@ExperimentalMaterial3Api
|
||||
fun App() {
|
||||
MaterialTheme {
|
||||
var showContent by remember { mutableStateOf(false) }
|
||||
var greetingText by remember { mutableStateOf("加载中...") }
|
||||
var isDarkTheme by remember { mutableStateOf(false) }
|
||||
val currentColors = if (isDarkTheme) AppThemes.DarkThemeColors else AppThemes.LightThemeColors
|
||||
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
greetingText = try {
|
||||
Greeting().greet()
|
||||
} catch (e: Exception) {
|
||||
"错误:${e.message}"
|
||||
}
|
||||
}
|
||||
// Navigation state
|
||||
var selectedNavItemIndex by remember { mutableStateOf(0) }
|
||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Button(onClick = { showContent = !showContent }) {
|
||||
Text("Click me!")
|
||||
val navItems = listOf(
|
||||
"Home" to Icons.Default.Home,
|
||||
"Analytics" to Icons.Default.BarChart,
|
||||
"Reports" to Icons.Default.Assessment,
|
||||
"Profile" to Icons.Default.AccountCircle,
|
||||
"Settings" to Icons.Default.Settings,
|
||||
"About" to Icons.Default.Info
|
||||
)
|
||||
|
||||
val currentScreenTitle = navItems[selectedNavItemIndex].first
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = if (isDarkTheme) darkColorScheme(
|
||||
primary = currentColors.accent,
|
||||
secondary = currentColors.accentVariant,
|
||||
background = currentColors.background,
|
||||
surface = currentColors.surface,
|
||||
onPrimary = currentColors.onAccent,
|
||||
onSecondary = currentColors.onAccent,
|
||||
onBackground = currentColors.onBackground,
|
||||
onSurface = currentColors.onSurface,
|
||||
) else lightColorScheme(
|
||||
primary = currentColors.accent,
|
||||
secondary = currentColors.accentVariant,
|
||||
background = currentColors.background,
|
||||
surface = currentColors.surface,
|
||||
onPrimary = currentColors.onAccent,
|
||||
onSecondary = currentColors.onAccent,
|
||||
onBackground = currentColors.onBackground,
|
||||
onSurface = currentColors.onSurface,
|
||||
)
|
||||
) {
|
||||
ModalNavigationDrawer(
|
||||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
ModalDrawerSheet(
|
||||
drawerContainerColor = currentColors.surface,
|
||||
drawerContentColor = currentColors.onSurface
|
||||
) {
|
||||
// Drawer Header with User Info
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(currentColors.accent.copy(alpha = 0.1f))
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.AccountCircle,
|
||||
contentDescription = "User Avatar",
|
||||
tint = currentColors.accent,
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.clip(CircleShape)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "grtsinry43", // Placeholder Nickname
|
||||
color = currentColors.onSurface,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Text(
|
||||
text = "grtsinry43@outlook.com", // Placeholder Email
|
||||
color = currentColors.secondaryText,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.height(12.dp))
|
||||
// Navigation Items
|
||||
navItems.forEachIndexed { index, item ->
|
||||
NavigationDrawerItem(
|
||||
icon = {
|
||||
Icon(
|
||||
item.second,
|
||||
contentDescription = item.first,
|
||||
tint = if (selectedNavItemIndex == index) currentColors.accent else currentColors.onSurface.copy(
|
||||
alpha = 0.7f
|
||||
)
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(
|
||||
item.first,
|
||||
color = if (selectedNavItemIndex == index) currentColors.accent else currentColors.onSurface.copy(
|
||||
alpha = 0.7f
|
||||
)
|
||||
)
|
||||
},
|
||||
selected = selectedNavItemIndex == index,
|
||||
onClick = {
|
||||
selectedNavItemIndex = index
|
||||
scope.launch { drawerState.close() }
|
||||
},
|
||||
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding),
|
||||
colors = NavigationDrawerItemDefaults.colors(
|
||||
selectedContainerColor = currentColors.accent.copy(alpha = 0.1f),
|
||||
unselectedContainerColor = Color.Transparent,
|
||||
selectedTextColor = currentColors.accent,
|
||||
unselectedTextColor = currentColors.onSurface.copy(alpha = 0.7f),
|
||||
selectedIconColor = currentColors.accent,
|
||||
unselectedIconColor = currentColors.onSurface.copy(alpha = 0.7f)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(showContent) {
|
||||
Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Image(painterResource(Res.drawable.compose_multiplatform), null)
|
||||
Text("Compose: $greetingText")
|
||||
) {
|
||||
Scaffold(
|
||||
containerColor = currentColors.background,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(currentScreenTitle, color = currentColors.onSurface) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { scope.launch { drawerState.open() } }) {
|
||||
Icon(
|
||||
Icons.Filled.Menu,
|
||||
contentDescription = "Open Navigation Drawer",
|
||||
tint = currentColors.onSurface
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = currentColors.surface,
|
||||
titleContentColor = currentColors.onSurface
|
||||
)
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Box(
|
||||
modifier = Modifier.padding(paddingValues).fillMaxSize()
|
||||
.background(currentColors.background)
|
||||
) {
|
||||
when (selectedNavItemIndex) {
|
||||
0 -> MobileHomeScreen(colors = currentColors)
|
||||
1 -> MobileAnalyticsScreen(colors = currentColors)
|
||||
2 -> MobileReportsScreen(colors = currentColors)
|
||||
3 -> MobileProfileScreen(colors = currentColors)
|
||||
4 -> MobileSettingsScreen(
|
||||
colors = currentColors,
|
||||
isDarkTheme = isDarkTheme,
|
||||
onThemeChange = { isDarkTheme = it }
|
||||
)
|
||||
|
||||
5 -> MobileAboutScreen(colors = currentColors)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
@ -14,633 +12,171 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import com.grtsinry43.activityanalyzer.components.*
|
||||
import com.grtsinry43.activityanalyzer.screens.AboutScreen
|
||||
import com.grtsinry43.activityanalyzer.screens.AnalyticsScreen
|
||||
import com.grtsinry43.activityanalyzer.screens.HomeScreen
|
||||
import com.grtsinry43.activityanalyzer.screens.ProfileScreen
|
||||
import com.grtsinry43.activityanalyzer.screens.ReportsScreen
|
||||
import com.grtsinry43.activityanalyzer.screens.SettingsScreen
|
||||
import com.grtsinry43.activityanalyzer.theme.AppThemes
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun DesktopApp() {
|
||||
var selectedItem by remember { mutableStateOf(0) }
|
||||
var isDarkTheme by remember { mutableStateOf(false) }
|
||||
var isSidebarCollapsed by remember { mutableStateOf(false) } // 新增状态变量
|
||||
var selectedItem by remember { mutableStateOf(0) } // 记住当前选中的导航项索引
|
||||
var isDarkTheme by remember { mutableStateOf(false) } // 记住当前是否为暗色主题
|
||||
var isSidebarCollapsed by remember { mutableStateOf(false) } // 记住侧边栏是否折叠
|
||||
|
||||
val backgroundColor = if (isDarkTheme) Color(0xFF1E1E1E) else Color(0xFFF5F5F5)
|
||||
val surfaceColor = if (isDarkTheme) Color(0xFF2D2D2D) else Color.White
|
||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||
val accentColor = Color(0xFF007AFF)
|
||||
val currentColors =
|
||||
if (isDarkTheme) AppThemes.DarkThemeColors else AppThemes.LightThemeColors // 根据主题选择颜色
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(backgroundColor)
|
||||
.fillMaxSize() // 填充整个可用空间
|
||||
.background(currentColors.background) // 设置背景色
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
modifier = Modifier.fillMaxSize() // 主行布局,填充整个可用空间
|
||||
) {
|
||||
// Sidebar
|
||||
// 侧边栏
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(if (isSidebarCollapsed) 60.dp else 200.dp) // 动态宽度
|
||||
.fillMaxHeight()
|
||||
.background(surfaceColor)
|
||||
.padding(16.dp)
|
||||
.width(if (isSidebarCollapsed) 70.dp else 240.dp) // 根据折叠状态调整宽度
|
||||
.fillMaxHeight() // 填充整个高度
|
||||
.background(currentColors.surface) // 设置侧边栏背景色
|
||||
.padding(
|
||||
start = 8.dp,
|
||||
end = 8.dp,
|
||||
top = 16.dp,
|
||||
bottom = 16.dp
|
||||
) // 设置内边距
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
modifier = Modifier.fillMaxHeight(), // 列布局,填充整个高度
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp) // 子项垂直间距
|
||||
) {
|
||||
if (!isSidebarCollapsed) {
|
||||
Text(
|
||||
"Activity Analyzer",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor
|
||||
"Activity Analyzer", // 应用标题
|
||||
fontSize = 18.sp, // 字体大小
|
||||
fontWeight = FontWeight.SemiBold, // 字体粗细
|
||||
color = currentColors.onSurface, // 文本颜色
|
||||
modifier = Modifier.padding(
|
||||
horizontal = 8.dp,
|
||||
vertical = 16.dp
|
||||
) // 标题内边距
|
||||
)
|
||||
} else {
|
||||
// 折叠时显示应用Logo图标
|
||||
Icon(
|
||||
Icons.Default.Analytics, // 应用Logo图标 (示例)
|
||||
contentDescription = "App Logo", // 内容描述
|
||||
tint = currentColors.accent, // 图标颜色
|
||||
modifier = Modifier
|
||||
.size(40.dp) // 图标大小
|
||||
.align(Alignment.CenterHorizontally) // 水平居中
|
||||
.padding(vertical = 16.dp) // 垂直内边距
|
||||
)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
|
||||
// Navigation items
|
||||
NavItem(
|
||||
icon = Icons.Default.Home,
|
||||
text = if (isSidebarCollapsed) "" else "Home", // 根据收缩状态显示文本
|
||||
isSelected = selectedItem == 0,
|
||||
onClick = { selectedItem = 0 },
|
||||
textColor = textColor,
|
||||
accentColor = accentColor,
|
||||
isSidebarCollapsed = isSidebarCollapsed
|
||||
)
|
||||
NavItem(
|
||||
icon = Icons.Default.BarChart,
|
||||
text = if (isSidebarCollapsed) "" else "Analytics",
|
||||
isSelected = selectedItem == 1,
|
||||
onClick = { selectedItem = 1 },
|
||||
textColor = textColor,
|
||||
accentColor = accentColor,
|
||||
isSidebarCollapsed = isSidebarCollapsed
|
||||
)
|
||||
NavItem(
|
||||
icon = Icons.Default.Dataset,
|
||||
text = if (isSidebarCollapsed) "" else "Reports",
|
||||
isSelected = selectedItem == 2,
|
||||
onClick = { selectedItem = 2 },
|
||||
textColor = textColor,
|
||||
accentColor = accentColor,
|
||||
isSidebarCollapsed = isSidebarCollapsed
|
||||
)
|
||||
NavItem(
|
||||
icon = Icons.Default.Settings,
|
||||
text = if (isSidebarCollapsed) "" else "Settings",
|
||||
isSelected = selectedItem == 3,
|
||||
onClick = { selectedItem = 3 },
|
||||
textColor = textColor,
|
||||
accentColor = accentColor,
|
||||
isSidebarCollapsed = isSidebarCollapsed
|
||||
// 导航项列表
|
||||
val navItems = listOf(
|
||||
"Home" to Icons.Default.Home, // 首页
|
||||
"Analytics" to Icons.Default.BarChart, // 分析页
|
||||
"Reports" to Icons.Default.Assessment, // 报告页 (图标已更改)
|
||||
"Profile" to Icons.Default.AccountCircle, // 个人资料页
|
||||
"Settings" to Icons.Default.Settings, // 设置页
|
||||
"About" to Icons.Default.Info // 关于页
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
// Theme switch
|
||||
if (!isSidebarCollapsed) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(if (isDarkTheme) Color(0xFF3D3D3D) else Color(0xFFE8E8E8))
|
||||
.padding(8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
"Dark Theme",
|
||||
color = textColor
|
||||
)
|
||||
Switch(
|
||||
checked = isDarkTheme,
|
||||
onCheckedChange = { isDarkTheme = it },
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = accentColor,
|
||||
checkedTrackColor = accentColor.copy(alpha = 0.5f)
|
||||
)
|
||||
)
|
||||
}
|
||||
navItems.forEachIndexed { index, (text, icon) ->
|
||||
NavItem(
|
||||
icon = icon,
|
||||
text = if (isSidebarCollapsed) "" else text, // 折叠时不显示文本
|
||||
isSelected = selectedItem == index, // 判断是否选中
|
||||
onClick = { selectedItem = index }, // 点击事件,更新选中项
|
||||
colors = currentColors,
|
||||
isSidebarCollapsed = isSidebarCollapsed
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f)) // 弹性空间,将后续内容推到底部
|
||||
|
||||
// User Profile Section
|
||||
// 用户资料区域 (侧边栏底部)
|
||||
if (!isSidebarCollapsed) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally // 水平居中对齐
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.AccountCircle, // 用户头像图标
|
||||
contentDescription = "User Avatar", // 内容描述
|
||||
tint = currentColors.accent, // 图标颜色
|
||||
modifier = Modifier
|
||||
.size(48.dp) // 图标大小
|
||||
.clip(CircleShape) // 圆形裁剪
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp)) // 垂直间距
|
||||
Text(
|
||||
text = "grtsinry43", // 用户昵称 (占位符)
|
||||
color = currentColors.onSurface, // 文本颜色
|
||||
fontSize = 14.sp, // 字体大小
|
||||
fontWeight = FontWeight.Medium // 字体粗细
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp)) // 垂直间距
|
||||
}
|
||||
|
||||
|
||||
// Collapse/Expand button
|
||||
// 折叠/展开侧边栏按钮
|
||||
IconButton(
|
||||
onClick = { isSidebarCollapsed = !isSidebarCollapsed },
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
onClick = { isSidebarCollapsed = !isSidebarCollapsed }, // 点击切换折叠状态
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally) // 水平居中
|
||||
.size(40.dp) // 按钮大小
|
||||
) {
|
||||
Icon(
|
||||
imageVector = if (isSidebarCollapsed) Icons.Default.ChevronRight else Icons.Default.ChevronLeft,
|
||||
contentDescription = "Toggle Sidebar",
|
||||
tint = textColor
|
||||
imageVector = if (isSidebarCollapsed) Icons.Default.MenuOpen else Icons.Default.Menu, // 根据状态选择图标
|
||||
contentDescription = "Toggle Sidebar", // 内容描述
|
||||
tint = currentColors.onSurface // 图标颜色
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main content
|
||||
// 主内容区域
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(24.dp)
|
||||
.fillMaxSize() // 填充整个可用空间
|
||||
.padding(24.dp) // 内边距
|
||||
) {
|
||||
// 根据选中的导航项显示不同的屏幕内容
|
||||
when (selectedItem) {
|
||||
0 -> HomeScreen(textColor = textColor, surfaceColor = surfaceColor)
|
||||
1 -> AnalyticsScreen(textColor = textColor, surfaceColor = surfaceColor)
|
||||
2 -> ReportsScreen(textColor = textColor, surfaceColor = surfaceColor)
|
||||
3 -> SettingsScreen(textColor = textColor, surfaceColor = surfaceColor)
|
||||
0 -> HomeScreen(colors = currentColors)
|
||||
1 -> AnalyticsScreen(colors = currentColors)
|
||||
2 -> ReportsScreen(colors = currentColors)
|
||||
3 -> ProfileScreen(colors = currentColors)
|
||||
4 -> SettingsScreen(
|
||||
colors = currentColors,
|
||||
isDarkTheme = isDarkTheme,
|
||||
onThemeChange = { isDarkTheme = it } // 主题切换回调
|
||||
)
|
||||
|
||||
5 -> AboutScreen(colors = currentColors)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavItem(
|
||||
icon: ImageVector,
|
||||
text: String,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
textColor: Color,
|
||||
accentColor: Color,
|
||||
isSidebarCollapsed: Boolean
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp)) // 更大的圆角
|
||||
.background(if (isSelected) accentColor.copy(alpha = 0.2f) else Color.Transparent)
|
||||
.padding(vertical = 12.dp, horizontal = if (isSidebarCollapsed) 8.dp else 16.dp)
|
||||
.clickable(
|
||||
onClick = onClick,
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = if (isSelected) accentColor else textColor,
|
||||
modifier = Modifier.size(if (isSidebarCollapsed) 32.dp else 24.dp) // 动态调整图标大小
|
||||
)
|
||||
if (!isSidebarCollapsed) {
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
color = if (isSelected) accentColor else textColor,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(textColor: Color, surfaceColor: Color) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||||
) {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = "Welcome Back!",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
}
|
||||
|
||||
// Quick actions
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.height(150.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = "Quick Actions",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
ActionButton(
|
||||
icon = Icons.Default.Add,
|
||||
text = "New Activity",
|
||||
onClick = {/* TODO */ }
|
||||
)
|
||||
ActionButton(
|
||||
icon = Icons.Default.Upload,
|
||||
text = "Import Data",
|
||||
onClick = {/* TODO */ }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // Recent activities
|
||||
// Card(
|
||||
// modifier = Modifier
|
||||
// .fillMaxWidth()
|
||||
// .clip(RoundedCornerShape(16.dp)),
|
||||
// colors = CardDefaults.cardColors(
|
||||
// containerColor = MaterialTheme.colorScheme.surface
|
||||
// )
|
||||
// ) {
|
||||
// Column(
|
||||
// modifier = Modifier.padding(16.dp),
|
||||
// verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
// ) {
|
||||
// Text(
|
||||
// text = "Recent Activities",
|
||||
// style = MaterialTheme.typography.titleMedium,
|
||||
// color = MaterialTheme.colorScheme.onSurface
|
||||
// )
|
||||
// ActivityItem(
|
||||
// title = "Activity Report Generated",
|
||||
// time = "2024-04-12 14:30",
|
||||
// icon = Icons.Default.Description
|
||||
// )
|
||||
// ActivityItem(
|
||||
// title = "Data Imported",
|
||||
// time = "2024-04-12 10:15",
|
||||
// icon = Icons.Default.Upload
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ActionButton(
|
||||
icon: ImageVector,
|
||||
text: String,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(Color(0xFF007AFF).copy(alpha = 0.1f))
|
||||
.clickable(onClick = onClick)
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFF007AFF)
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
color = Color(0xFF007AFF),
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ActivityItem(
|
||||
title: String,
|
||||
time: String,
|
||||
icon: ImageVector
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color(0xFFF5F5F5))
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFF007AFF)
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = title,
|
||||
fontSize = 16.sp,
|
||||
color = Color.Black
|
||||
)
|
||||
Text(
|
||||
text = time,
|
||||
fontSize = 14.sp,
|
||||
color = Color.Gray
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AnalyticsScreen(textColor: Color, surfaceColor: Color) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||||
) {
|
||||
Text(
|
||||
"Analytics",
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor
|
||||
)
|
||||
|
||||
// Analysis tools card
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(surfaceColor)
|
||||
.padding(24.dp)
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Text(
|
||||
"Analysis Tools",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
AnalysisToolButton(
|
||||
icon = Icons.Default.Info,
|
||||
text = "Time Analysis",
|
||||
onClick = {/* TODO */ }
|
||||
)
|
||||
AnalysisToolButton(
|
||||
icon = Icons.Default.Info,
|
||||
text = "Distribution Analysis",
|
||||
onClick = {/* TODO */ }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AnalysisToolButton(
|
||||
icon: ImageVector,
|
||||
text: String,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(Color(0xFF007AFF).copy(alpha = 0.1f))
|
||||
.clickable(onClick = onClick)
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFF007AFF)
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
color = Color(0xFF007AFF),
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ReportsScreen(textColor: Color, surfaceColor: Color) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||||
) {
|
||||
Text(
|
||||
"Reports",
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor
|
||||
)
|
||||
|
||||
// Report list
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(surfaceColor)
|
||||
.padding(24.dp)
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Text(
|
||||
"My Reports",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor
|
||||
)
|
||||
ReportItem(
|
||||
title = "Monthly Activity Report",
|
||||
period = "March 2024",
|
||||
icon = Icons.Default.Info
|
||||
)
|
||||
ReportItem(
|
||||
title = "Weekly Activity Analysis",
|
||||
period = "Week 12, 2024",
|
||||
icon = Icons.Default.Info
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ReportItem(
|
||||
title: String,
|
||||
period: String,
|
||||
icon: ImageVector
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color(0xFFF5F5F5))
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFF007AFF)
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = title,
|
||||
fontSize = 16.sp,
|
||||
color = Color.Black
|
||||
)
|
||||
Text(
|
||||
text = period,
|
||||
fontSize = 14.sp,
|
||||
color = Color.Gray
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = { /* TODO */ }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Info,
|
||||
contentDescription = "Download",
|
||||
tint = Color(0xFF007AFF)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsScreen(textColor: Color, surfaceColor: Color) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||||
) {
|
||||
Text(
|
||||
"Settings",
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = textColor
|
||||
)
|
||||
|
||||
// Settings options
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(surfaceColor)
|
||||
.padding(24.dp)
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
SettingItem(
|
||||
title = "Data Storage Location",
|
||||
subtitle = "Default Location",
|
||||
icon = Icons.Default.Info,
|
||||
onClick = {/* TODO */ }
|
||||
)
|
||||
SettingItem(
|
||||
title = "Auto Backup",
|
||||
subtitle = "Daily",
|
||||
icon = Icons.Default.Info,
|
||||
showSwitch = true
|
||||
)
|
||||
SettingItem(
|
||||
title = "Notification Settings",
|
||||
subtitle = "Enabled",
|
||||
icon = Icons.Default.Info,
|
||||
showSwitch = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingItem(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
icon: ImageVector,
|
||||
onClick: (() -> Unit)? = null,
|
||||
showSwitch: Boolean = false
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(Color(0xFFF5F5F5))
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = Color(0xFF007AFF)
|
||||
)
|
||||
Column {
|
||||
Text(
|
||||
text = title,
|
||||
fontSize = 16.sp,
|
||||
color = Color.Black
|
||||
)
|
||||
Text(
|
||||
text = subtitle,
|
||||
fontSize = 14.sp,
|
||||
color = Color.Gray
|
||||
)
|
||||
}
|
||||
}
|
||||
if (showSwitch) {
|
||||
Switch(
|
||||
checked = true,
|
||||
onCheckedChange = {/* TODO */ },
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = Color(0xFF007AFF),
|
||||
checkedTrackColor = Color(0xFF007AFF).copy(alpha = 0.5f)
|
||||
)
|
||||
)
|
||||
} else if (onClick != null) {
|
||||
TextButton(onClick = onClick) {
|
||||
Text(
|
||||
"Change",
|
||||
color = Color(0xFF007AFF)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Analytics // 用一个合适的图标作为托盘图标
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||
import androidx.compose.ui.graphics.toComposeImageBitmap
|
||||
import androidx.compose.ui.res.loadImageBitmap
|
||||
import androidx.compose.ui.res.useResource
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.*
|
||||
import java.awt.image.BufferedImage
|
||||
import javax.imageio.ImageIO
|
||||
|
||||
|
||||
fun main() = application {
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
title = "Activity Analyzer",
|
||||
) {
|
||||
DesktopApp()
|
||||
// 管理窗口状态,例如可见性、位置、大小
|
||||
val windowState = rememberWindowState(
|
||||
size = DpSize(1024.dp, 768.dp), // 设置窗口的初始大小
|
||||
// position = WindowPosition(Win) // 初始位置居中
|
||||
)
|
||||
|
||||
// 管理托盘状态
|
||||
val trayState = rememberTrayState()
|
||||
var isWindowVisible by remember { mutableStateOf(true) } // 控制窗口是否可见
|
||||
|
||||
// 尝试加载自定义托盘图标 (推荐使用 .ico 或 .png)
|
||||
// 请将 "tray_icon.png" 替换为你的图标文件名,并确保它在 resources 目录下
|
||||
// 如果加载失败,则使用默认图标
|
||||
val trayIconPainter = try {
|
||||
useResource("tray_icon.png") { stream ->
|
||||
BitmapPainter(loadImageBitmap(stream))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("警告:无法加载自定义托盘图标 'tray_icon.png'。将使用默认图标。错误: ${e.message}")
|
||||
null // 稍后会处理 null 情况
|
||||
}
|
||||
|
||||
// 系统托盘设置
|
||||
// 只有当窗口第一次变为不可见时,或者托盘图标加载成功时,才显示托盘图标
|
||||
// 这样可以避免在没有图标的情况下尝试创建托盘
|
||||
if (!isWindowVisible || trayIconPainter != null) {
|
||||
Tray(
|
||||
state = trayState,
|
||||
icon = trayIconPainter ?: BitmapPainter(createDefaultTrayIcon()), // 如果自定义图标加载失败,使用一个备用图标
|
||||
tooltip = "Activity Analyzer", // 鼠标悬停在托盘图标上时显示的提示文字
|
||||
menu = {
|
||||
// 托盘菜单项
|
||||
Item(
|
||||
if (isWindowVisible) "隐藏窗口" else "显示窗口",
|
||||
onClick = {
|
||||
isWindowVisible = !isWindowVisible
|
||||
if (isWindowVisible) {
|
||||
windowState.isMinimized = false // 如果窗口被最小化了,恢复它
|
||||
}
|
||||
}
|
||||
)
|
||||
Separator() // 分隔线
|
||||
Item(
|
||||
"退出应用",
|
||||
onClick = ::exitApplication // 点击后退出整个应用
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// 主窗口设置
|
||||
if (isWindowVisible) {
|
||||
Window(
|
||||
onCloseRequest = {
|
||||
// 点击关闭按钮时,可以选择隐藏到托盘而不是直接退出
|
||||
isWindowVisible = false
|
||||
// 或者,如果你希望点击关闭直接退出应用,则使用:
|
||||
// exitApplication()
|
||||
},
|
||||
state = windowState, // 应用窗口状态
|
||||
title = "Activity Analyzer",
|
||||
resizable = true, // 允许用户调整窗口大小 (默认为 true)
|
||||
// icon = painterResource("app_icon.png") // 可选:设置窗口左上角的图标和任务栏图标
|
||||
) {
|
||||
// 在这里设置窗口的最小尺寸
|
||||
// 这会影响用户能将窗口缩小到的最小程度
|
||||
window.minimumSize = java.awt.Dimension(600, 400)
|
||||
|
||||
// 你之前创建的 DesktopApp UI
|
||||
// Assuming DesktopApp is a Composable function defined elsewhere
|
||||
DesktopApp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建一个简单的默认托盘图标 (如果自定义图标加载失败)
|
||||
// 这是一个备选方案,实际项目中强烈建议使用合适的图片资源
|
||||
private fun createDefaultTrayIcon(): ImageBitmap { // Change return type to ImageBitmap
|
||||
// 使用 Compose 图标作为示例,实际中你应该加载一个图像文件
|
||||
// 这里我们创建一个简单的 BufferedImage 代替
|
||||
val width = 64
|
||||
val height = 64
|
||||
val image = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB)
|
||||
val g = image.createGraphics()
|
||||
try {
|
||||
// 简单绘制一个蓝色方块作为示例
|
||||
g.color = java.awt.Color.BLUE
|
||||
g.fillRect(0, 0, width, height)
|
||||
g.color = java.awt.Color.WHITE
|
||||
g.drawString("AA", 20, 40) // "Activity Analyzer" 缩写
|
||||
} finally {
|
||||
g.dispose()
|
||||
}
|
||||
// Convert BufferedImage to ImageBitmap before returning
|
||||
return image.toComposeImageBitmap()
|
||||
}
|
||||
@ -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 Shared
|
||||
|
||||
// MARK: - BottomBar
|
||||
struct BottomBar: View {
|
||||
// Use meaningful tab names
|
||||
@State private var selectedTab: Tab = .today
|
||||
|
||||
// Define Tabs with associated icons and names
|
||||
enum Tab: CaseIterable {
|
||||
case today, weekly, settings
|
||||
|
||||
@ -28,69 +28,61 @@ struct BottomBar: View {
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $selectedTab) {
|
||||
// Use ContentView for the "Today" tab
|
||||
ContentView()
|
||||
.tag(Tab.today)
|
||||
.tabItem { Label(Tab.today.title, systemImage: Tab.today.iconName) }
|
||||
|
||||
// Placeholder for Weekly view
|
||||
WeeklyView()
|
||||
.tag(Tab.weekly)
|
||||
.tabItem { Label(Tab.weekly.title, systemImage: Tab.weekly.iconName) }
|
||||
|
||||
// Placeholder for Settings view
|
||||
SettingsView()
|
||||
.tag(Tab.settings)
|
||||
.tabItem { Label(Tab.settings.title, systemImage: Tab.settings.iconName) }
|
||||
}
|
||||
.accentColor(.purple) // Example: Set a custom accent color for the tab bar
|
||||
.accentColor(.purple)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Weekly View ---
|
||||
// MARK: - WeeklyView
|
||||
struct WeeklyView: View {
|
||||
// Placeholder data for weekly usage (e.g., hours per day)
|
||||
let weeklyData: [Double] = [3.5, 4.2, 5.1, 2.8, 6.0, 7.5, 4.8]
|
||||
let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
||||
let goalHours: Double = 8.0 // Example daily goal
|
||||
let goalHours: Double = 8.0
|
||||
|
||||
// Calculate max value for chart scaling
|
||||
var maxValue: Double {
|
||||
(weeklyData.max() ?? goalHours) * 1.1 // Add 10% buffer
|
||||
(weeklyData.max() ?? goalHours) * 1.1
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
// Use List for standard iOS layout
|
||||
List {
|
||||
Section("Usage Trend") {
|
||||
// Bar Chart Visualization
|
||||
HStack(alignment: .bottom, spacing: 15) { // Increased spacing
|
||||
HStack(alignment: .bottom, spacing: 15) {
|
||||
ForEach(0..<weeklyData.count, id: \.self) { index in
|
||||
VStack(spacing: 4) { // Spacing within the bar stack
|
||||
VStack(spacing: 4) {
|
||||
Text(String(format: "%.1fh", weeklyData[index]))
|
||||
.font(.caption)
|
||||
.foregroundColor(.accentColor)
|
||||
.lineLimit(1)
|
||||
.rotationEffect(.degrees(-90)) // Rotate text for better fit if needed
|
||||
.offset(y: -25) // Adjust offset if rotated
|
||||
.frame(height: 50) // Give space for text
|
||||
.rotationEffect(.degrees(-90))
|
||||
.offset(y: -25)
|
||||
.frame(height: 50)
|
||||
|
||||
Rectangle()
|
||||
.fill(weeklyData[index] > goalHours ? Color.orange : Color.accentColor) // Highlight days over goal
|
||||
// Scale height relative to the max value
|
||||
.frame(height: max(1, CGFloat(weeklyData[index] / maxValue) * 150)) // Ensure min height, scale based on max
|
||||
.cornerRadius(5) // Rounded corners for bars
|
||||
.fill(weeklyData[index] > goalHours ? Color.orange : Color.accentColor)
|
||||
.frame(height: max(1, CGFloat(weeklyData[index] / maxValue) * 150))
|
||||
.cornerRadius(5)
|
||||
|
||||
Text(days[index])
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity) // Allow bars to take equal width
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
.frame(height: 220) // Adjust overall chart height
|
||||
.padding(.vertical) // Add padding around the chart
|
||||
.frame(height: 220)
|
||||
.padding(.vertical)
|
||||
}
|
||||
|
||||
Section("Summary") {
|
||||
@ -114,33 +106,54 @@ struct WeeklyView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(.insetGrouped) // Use inset grouped style
|
||||
.listStyle(.insetGrouped)
|
||||
.navigationTitle("Weekly Stats")
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
|
||||
// Helper function to calculate weekly average
|
||||
func calculateWeeklyAverage() -> String {
|
||||
let totalHours = weeklyData.reduce(0, +)
|
||||
let averageHours = totalHours / Double(weeklyData.count)
|
||||
let formatter = DateComponentsFormatter()
|
||||
formatter.allowedUnits = [.hour, .minute]
|
||||
formatter.unitsStyle = .abbreviated
|
||||
// Convert hours to seconds for formatter
|
||||
return formatter.string(from: TimeInterval(averageHours * 3600)) ?? "0m"
|
||||
}
|
||||
|
||||
// Helper property for days over goal
|
||||
var daysOverGoalCount: Int {
|
||||
weeklyData.filter { $0 > goalHours }.count
|
||||
}
|
||||
}
|
||||
|
||||
// --- Settings View ---
|
||||
// MARK: - GreetingViewModel
|
||||
class GreetingViewModel: ObservableObject {
|
||||
@Published var greetingText = "加载中..."
|
||||
private let greeting = Greeting()
|
||||
|
||||
func loadGreeting() {
|
||||
greetingText = "加载中..."
|
||||
|
||||
Task {
|
||||
do {
|
||||
let result = try await greeting.greet()
|
||||
await MainActor.run {
|
||||
self.greetingText = result
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
self.greetingText = "错误: \(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SettingsView
|
||||
struct SettingsView: View {
|
||||
@StateObject private var viewModel = GreetingViewModel()
|
||||
@State private var usageReminders = true
|
||||
@State private var darkModeEnabled = false // Example state
|
||||
@State private var darkModeEnabled = false
|
||||
@State private var selectedLimit = "No Limit"
|
||||
let appLimits = ["No Limit", "1 hour", "2 hours", "Custom"]
|
||||
|
||||
@ -149,7 +162,7 @@ struct SettingsView: View {
|
||||
Form {
|
||||
Section("Notifications") {
|
||||
Toggle("Usage Reminders", isOn: $usageReminders)
|
||||
Toggle("Goal Achievement Alerts", isOn: .constant(false)) // Added toggle
|
||||
Toggle("Goal Achievement Alerts", isOn: .constant(false))
|
||||
}
|
||||
|
||||
Section("Usage Limits") {
|
||||
@ -158,11 +171,11 @@ struct SettingsView: View {
|
||||
Text($0)
|
||||
}
|
||||
}
|
||||
NavigationLink("App Specific Limits", destination: Text("App Limits Detail (Placeholder)")) // Added link
|
||||
NavigationLink("App Specific Limits", destination: Text("App Limits Detail (Placeholder)"))
|
||||
}
|
||||
|
||||
Section("Appearance") {
|
||||
Toggle("Dark Mode", isOn: $darkModeEnabled) // Added toggle
|
||||
Toggle("Dark Mode", isOn: $darkModeEnabled)
|
||||
NavigationLink("Accent Color", destination: Text("Color Picker (Placeholder)"))
|
||||
}
|
||||
|
||||
@ -175,11 +188,21 @@ struct SettingsView: View {
|
||||
}
|
||||
NavigationLink("Privacy Policy", destination: Text("Privacy Policy Details (Placeholder)"))
|
||||
}
|
||||
Section("test") {
|
||||
Text("Hello From Kotlin Multiplatform: \n\(Greeting().greet()) ")
|
||||
|
||||
Section("Kotlin Multiplatform 测试") {
|
||||
Text("来自KMP的问候:\n\(viewModel.greetingText)")
|
||||
.padding(.vertical, 8)
|
||||
|
||||
Button("重新加载问候语") {
|
||||
viewModel.loadGreeting()
|
||||
}
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
.onAppear {
|
||||
viewModel.loadGreeting()
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user