feat: Implement Bottom Navigation and Basic UI for iOS and Desktop
- Introduce a bottom navigation bar with "Today," "Weekly," and "Settings" tabs for the iOS app. - Implement a sidebar navigation for the desktop app with Home, Analytics, Reports, and Settings. - Add Weekly stats with a bar chart and summary for the iOS app. - Add Settings view with basic settings options for iOS app. - Add a Progress Ring for displaying "Today" stats in iOS app. - Add app usage data list to "Today" tab in iOS app. - Implement dark theme toggle for desktop app. - Implement collapsible sidebar in desktop app. - Refine UI of navigation items for desktop app. - Add basic placeholders for the desktop app UI.
This commit is contained in:
parent
1c61774c04
commit
cfa6f13cdd
@ -4,6 +4,8 @@ import androidx.compose.foundation.*
|
|||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicText
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
@ -26,6 +28,7 @@ import androidx.compose.material3.TextButton
|
|||||||
fun DesktopApp() {
|
fun DesktopApp() {
|
||||||
var selectedItem by remember { mutableStateOf(0) }
|
var selectedItem by remember { mutableStateOf(0) }
|
||||||
var isDarkTheme by remember { mutableStateOf(false) }
|
var isDarkTheme by remember { mutableStateOf(false) }
|
||||||
|
var isSidebarCollapsed by remember { mutableStateOf(false) } // 新增状态变量
|
||||||
|
|
||||||
val backgroundColor = if (isDarkTheme) Color(0xFF1E1E1E) else Color(0xFFF5F5F5)
|
val backgroundColor = if (isDarkTheme) Color(0xFF1E1E1E) else Color(0xFFF5F5F5)
|
||||||
val surfaceColor = if (isDarkTheme) Color(0xFF2D2D2D) else Color.White
|
val surfaceColor = if (isDarkTheme) Color(0xFF2D2D2D) else Color.White
|
||||||
@ -43,7 +46,7 @@ fun DesktopApp() {
|
|||||||
// Sidebar
|
// Sidebar
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(200.dp)
|
.width(if (isSidebarCollapsed) 60.dp else 200.dp) // 动态宽度
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.background(surfaceColor)
|
.background(surfaceColor)
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
@ -52,6 +55,7 @@ fun DesktopApp() {
|
|||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
|
if (!isSidebarCollapsed) {
|
||||||
Text(
|
Text(
|
||||||
"Activity Analyzer",
|
"Activity Analyzer",
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
@ -59,45 +63,50 @@ fun DesktopApp() {
|
|||||||
color = textColor
|
color = textColor
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(32.dp))
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
}
|
||||||
|
|
||||||
// Navigation items
|
// Navigation items
|
||||||
NavItem(
|
NavItem(
|
||||||
icon = Icons.Default.Home,
|
icon = Icons.Default.Home,
|
||||||
text = "Home",
|
text = if (isSidebarCollapsed) "" else "Home", // 根据收缩状态显示文本
|
||||||
isSelected = selectedItem == 0,
|
isSelected = selectedItem == 0,
|
||||||
onClick = { selectedItem = 0 },
|
onClick = { selectedItem = 0 },
|
||||||
textColor = textColor,
|
textColor = textColor,
|
||||||
accentColor = accentColor
|
accentColor = accentColor,
|
||||||
|
isSidebarCollapsed = isSidebarCollapsed
|
||||||
)
|
)
|
||||||
NavItem(
|
NavItem(
|
||||||
icon = Icons.Default.BarChart,
|
icon = Icons.Default.BarChart,
|
||||||
text = "Analytics",
|
text = if (isSidebarCollapsed) "" else "Analytics",
|
||||||
isSelected = selectedItem == 1,
|
isSelected = selectedItem == 1,
|
||||||
onClick = { selectedItem = 1 },
|
onClick = { selectedItem = 1 },
|
||||||
textColor = textColor,
|
textColor = textColor,
|
||||||
accentColor = accentColor
|
accentColor = accentColor,
|
||||||
|
isSidebarCollapsed = isSidebarCollapsed
|
||||||
)
|
)
|
||||||
NavItem(
|
NavItem(
|
||||||
icon = Icons.Default.Dataset,
|
icon = Icons.Default.Dataset,
|
||||||
text = "Reports",
|
text = if (isSidebarCollapsed) "" else "Reports",
|
||||||
isSelected = selectedItem == 2,
|
isSelected = selectedItem == 2,
|
||||||
onClick = { selectedItem = 2 },
|
onClick = { selectedItem = 2 },
|
||||||
textColor = textColor,
|
textColor = textColor,
|
||||||
accentColor = accentColor
|
accentColor = accentColor,
|
||||||
|
isSidebarCollapsed = isSidebarCollapsed
|
||||||
)
|
)
|
||||||
NavItem(
|
NavItem(
|
||||||
icon = Icons.Default.Settings,
|
icon = Icons.Default.Settings,
|
||||||
text = "Settings",
|
text = if (isSidebarCollapsed) "" else "Settings",
|
||||||
isSelected = selectedItem == 3,
|
isSelected = selectedItem == 3,
|
||||||
onClick = { selectedItem = 3 },
|
onClick = { selectedItem = 3 },
|
||||||
textColor = textColor,
|
textColor = textColor,
|
||||||
accentColor = accentColor
|
accentColor = accentColor,
|
||||||
|
isSidebarCollapsed = isSidebarCollapsed
|
||||||
)
|
)
|
||||||
|
|
||||||
// Spacer for alignment
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
// Theme switch
|
// Theme switch
|
||||||
|
if (!isSidebarCollapsed) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@ -121,6 +130,19 @@ fun DesktopApp() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collapse/Expand button
|
||||||
|
IconButton(
|
||||||
|
onClick = { isSidebarCollapsed = !isSidebarCollapsed },
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isSidebarCollapsed) Icons.Default.ChevronRight else Icons.Default.ChevronLeft,
|
||||||
|
contentDescription = "Toggle Sidebar",
|
||||||
|
tint = textColor
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main content
|
// Main content
|
||||||
@ -147,18 +169,20 @@ fun NavItem(
|
|||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
textColor: Color,
|
textColor: Color,
|
||||||
accentColor: Color
|
accentColor: Color,
|
||||||
|
isSidebarCollapsed: Boolean
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(RoundedCornerShape(12.dp)) // 更大的圆角
|
||||||
.background(if (isSelected) accentColor.copy(alpha = 0.1f) else Color.Transparent)
|
.background(if (isSelected) accentColor.copy(alpha = 0.2f) else Color.Transparent)
|
||||||
.padding(12.dp)
|
.padding(vertical = 12.dp, horizontal = if (isSidebarCollapsed) 8.dp else 16.dp)
|
||||||
.clickable(
|
.clickable(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
indication = null,
|
indication = null,
|
||||||
interactionSource = remember { MutableInteractionSource() }),
|
interactionSource = remember { MutableInteractionSource() }
|
||||||
|
),
|
||||||
contentAlignment = Alignment.CenterStart
|
contentAlignment = Alignment.CenterStart
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@ -168,13 +192,19 @@ fun NavItem(
|
|||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = if (isSelected) accentColor else textColor
|
tint = if (isSelected) accentColor else textColor,
|
||||||
|
modifier = Modifier.size(if (isSidebarCollapsed) 32.dp else 24.dp) // 动态调整图标大小
|
||||||
)
|
)
|
||||||
|
if (!isSidebarCollapsed) {
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
|
style = MaterialTheme.typography.titleMedium.copy(
|
||||||
color = if (isSelected) accentColor else textColor,
|
color = if (isSelected) accentColor else textColor,
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp,
|
||||||
|
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,11 +217,13 @@ fun HomeScreen(textColor: Color, surfaceColor: Color) {
|
|||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(24.dp)
|
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||||||
) {
|
) {
|
||||||
|
SelectionContainer {
|
||||||
Text(
|
Text(
|
||||||
text = "Welcome Back!",
|
text = "Welcome Back!",
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
color = MaterialTheme.colorScheme.onBackground
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Quick actions
|
// Quick actions
|
||||||
Card(
|
Card(
|
||||||
@ -207,11 +239,13 @@ fun HomeScreen(textColor: Color, surfaceColor: Color) {
|
|||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
|
SelectionContainer {
|
||||||
Text(
|
Text(
|
||||||
text = "Quick Actions",
|
text = "Quick Actions",
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
|
}
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
@ -228,38 +262,38 @@ fun HomeScreen(textColor: Color, surfaceColor: Color) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Recent activities
|
// // Recent activities
|
||||||
Card(
|
// Card(
|
||||||
modifier = Modifier
|
// modifier = Modifier
|
||||||
.fillMaxWidth()
|
// .fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(16.dp)),
|
// .clip(RoundedCornerShape(16.dp)),
|
||||||
colors = CardDefaults.cardColors(
|
// colors = CardDefaults.cardColors(
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
// containerColor = MaterialTheme.colorScheme.surface
|
||||||
)
|
// )
|
||||||
) {
|
// ) {
|
||||||
Column(
|
// Column(
|
||||||
modifier = Modifier.padding(16.dp),
|
// modifier = Modifier.padding(16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
// verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
// ) {
|
||||||
Text(
|
// Text(
|
||||||
text = "Recent Activities",
|
// text = "Recent Activities",
|
||||||
style = MaterialTheme.typography.titleMedium,
|
// style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
// color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
// )
|
||||||
ActivityItem(
|
// ActivityItem(
|
||||||
title = "Activity Report Generated",
|
// title = "Activity Report Generated",
|
||||||
time = "2024-04-12 14:30",
|
// time = "2024-04-12 14:30",
|
||||||
icon = Icons.Default.Description
|
// icon = Icons.Default.Description
|
||||||
)
|
// )
|
||||||
ActivityItem(
|
// ActivityItem(
|
||||||
title = "Data Imported",
|
// title = "Data Imported",
|
||||||
time = "2024-04-12 10:15",
|
// time = "2024-04-12 10:15",
|
||||||
icon = Icons.Default.Upload
|
// icon = Icons.Default.Upload
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
|
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
|
||||||
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
|
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
|
||||||
7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
|
7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
|
||||||
|
C2894CEC2DB7FC08006301A9 /* BottomBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2894CEB2DB7FC08006301A9 /* BottomBar.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@ -21,6 +22,7 @@
|
|||||||
7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
||||||
|
C2894CEB2DB7FC08006301A9 /* BottomBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomBar.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -74,6 +76,7 @@
|
|||||||
7555FF82242A565900829871 /* ContentView.swift */,
|
7555FF82242A565900829871 /* ContentView.swift */,
|
||||||
7555FF8C242A565B00829871 /* Info.plist */,
|
7555FF8C242A565B00829871 /* Info.plist */,
|
||||||
2152FB032600AC8F00CF470E /* iOSApp.swift */,
|
2152FB032600AC8F00CF470E /* iOSApp.swift */,
|
||||||
|
C2894CEB2DB7FC08006301A9 /* BottomBar.swift */,
|
||||||
058557D7273AAEEB004C7B11 /* Preview Content */,
|
058557D7273AAEEB004C7B11 /* Preview Content */,
|
||||||
);
|
);
|
||||||
path = iosApp;
|
path = iosApp;
|
||||||
@ -186,6 +189,7 @@
|
|||||||
files = (
|
files = (
|
||||||
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
|
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
|
||||||
7555FF83242A565900829871 /* ContentView.swift in Sources */,
|
7555FF83242A565900829871 /* ContentView.swift in Sources */,
|
||||||
|
C2894CEC2DB7FC08006301A9 /* BottomBar.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
192
iosApp/iosApp/BottomBar.swift
Normal file
192
iosApp/iosApp/BottomBar.swift
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import Shared
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
switch self {
|
||||||
|
case .today: return "Today"
|
||||||
|
case .weekly: return "Weekly"
|
||||||
|
case .settings: return "Settings"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var iconName: String {
|
||||||
|
switch self {
|
||||||
|
case .today: return "chart.bar.xaxis"
|
||||||
|
case .weekly: return "calendar.day.timeline.leading"
|
||||||
|
case .settings: return "gear"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Weekly View ---
|
||||||
|
struct WeeklyView: View {
|
||||||
|
// Placeholder data for weekly usage (e.g., hours per day)
|
||||||
|
let weeklyData: [Double] = [3.5, 4.2, 5.1, 2.8, 6.0, 7.5, 4.8]
|
||||||
|
let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
||||||
|
let goalHours: Double = 8.0 // Example daily goal
|
||||||
|
|
||||||
|
// Calculate max value for chart scaling
|
||||||
|
var maxValue: Double {
|
||||||
|
(weeklyData.max() ?? goalHours) * 1.1 // Add 10% buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
ForEach(0..<weeklyData.count, id: \.self) { index in
|
||||||
|
VStack(spacing: 4) { // Spacing within the bar stack
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Text(days[index])
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity) // Allow bars to take equal width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(height: 220) // Adjust overall chart height
|
||||||
|
.padding(.vertical) // Add padding around the chart
|
||||||
|
}
|
||||||
|
|
||||||
|
Section("Summary") {
|
||||||
|
HStack {
|
||||||
|
Text("Weekly Average")
|
||||||
|
Spacer()
|
||||||
|
Text(calculateWeeklyAverage())
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
Text("Daily Goal")
|
||||||
|
Spacer()
|
||||||
|
Text(String(format: "%.1fh", goalHours))
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
Text("Days Over Goal")
|
||||||
|
Spacer()
|
||||||
|
Text("\(daysOverGoalCount)")
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listStyle(.insetGrouped) // Use inset grouped style
|
||||||
|
.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 ---
|
||||||
|
struct SettingsView: View {
|
||||||
|
@State private var usageReminders = true
|
||||||
|
@State private var darkModeEnabled = false // Example state
|
||||||
|
@State private var selectedLimit = "No Limit"
|
||||||
|
let appLimits = ["No Limit", "1 hour", "2 hours", "Custom"]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
Form {
|
||||||
|
Section("Notifications") {
|
||||||
|
Toggle("Usage Reminders", isOn: $usageReminders)
|
||||||
|
Toggle("Goal Achievement Alerts", isOn: .constant(false)) // Added toggle
|
||||||
|
}
|
||||||
|
|
||||||
|
Section("Usage Limits") {
|
||||||
|
Picker("Daily Limit", selection: $selectedLimit) {
|
||||||
|
ForEach(appLimits, id: \.self) {
|
||||||
|
Text($0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NavigationLink("App Specific Limits", destination: Text("App Limits Detail (Placeholder)")) // Added link
|
||||||
|
}
|
||||||
|
|
||||||
|
Section("Appearance") {
|
||||||
|
Toggle("Dark Mode", isOn: $darkModeEnabled) // Added toggle
|
||||||
|
NavigationLink("Accent Color", destination: Text("Color Picker (Placeholder)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
Section("About") {
|
||||||
|
HStack {
|
||||||
|
Text("Version")
|
||||||
|
Spacer()
|
||||||
|
Text("1.0.0")
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
NavigationLink("Privacy Policy", destination: Text("Privacy Policy Details (Placeholder)"))
|
||||||
|
}
|
||||||
|
Section("test") {
|
||||||
|
Text("Hello From Kotlin Multiplatform: \n\(Greeting().greet()) ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Settings")
|
||||||
|
}
|
||||||
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BottomBar_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
BottomBar()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,33 +1,132 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Shared
|
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View { // This now represents the "Today" view
|
||||||
@State private var showContent = false
|
// Placeholder goal for visualization
|
||||||
|
let dailyGoal: Double = 8 * 60 * 60 // 8 hours in seconds
|
||||||
|
// Placeholder current usage in seconds
|
||||||
|
let currentUsage: Double = 4.5 * 60 * 60 // 4.5 hours
|
||||||
|
|
||||||
|
// Placeholder App Usage Data
|
||||||
|
let appUsageData: [(name: String, time: String, icon: String, percentage: Double)] = [
|
||||||
|
("Social Media App", "1h 15m", "message.fill", 0.28),
|
||||||
|
("Video Streaming", "55m", "play.tv.fill", 0.20),
|
||||||
|
("Game", "40m", "gamecontroller.fill", 0.15),
|
||||||
|
("Browser", "30m", "safari.fill", 0.11),
|
||||||
|
("Music", "25m", "music.note", 0.09),
|
||||||
|
("News Reader", "15m", "newspaper.fill", 0.06)
|
||||||
|
]
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
NavigationView {
|
||||||
Button("Click me!") {
|
// Use List for standard iOS table view appearance
|
||||||
withAnimation {
|
List {
|
||||||
showContent = !showContent
|
// Section for the Progress Ring
|
||||||
}
|
Section {
|
||||||
}
|
VStack(spacing: 10) { // Adjusted spacing
|
||||||
|
Text("Total Usage Today")
|
||||||
|
.font(.title2)
|
||||||
|
.fontWeight(.semibold) // Slightly bolder title
|
||||||
|
.frame(maxWidth: .infinity, alignment: .center) // Center align
|
||||||
|
|
||||||
if showContent {
|
// Progress Ring Visualization
|
||||||
VStack(spacing: 16) {
|
ZStack {
|
||||||
Image(systemName: "swift")
|
Circle()
|
||||||
.font(.system(size: 200))
|
.stroke(lineWidth: 20) // Slightly thicker ring
|
||||||
|
.opacity(0.1)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
|
||||||
|
Circle()
|
||||||
|
.trim(from: 0.0, to: min(currentUsage / dailyGoal, 1.0))
|
||||||
|
.stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round)) // Thicker ring
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
Text("SwiftUI: \(Greeting().greet())")
|
.rotationEffect(Angle(degrees: 270.0))
|
||||||
}
|
.animation(.linear, value: currentUsage)
|
||||||
.transition(.move(edge: .top).combined(with: .opacity))
|
|
||||||
|
VStack {
|
||||||
|
Text(formattedTime(seconds: currentUsage))
|
||||||
|
.font(.largeTitle)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
Text("of 8h Goal")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary) // Use secondary color
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
.frame(width: 180, height: 180) // Slightly larger ring
|
||||||
.padding()
|
.padding(.vertical) // Add vertical padding
|
||||||
|
}
|
||||||
|
.listRowInsets(EdgeInsets()) // Remove default insets for this row
|
||||||
|
.listRowBackground(Color.clear) // Make background clear if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Section for Most Used Apps
|
||||||
|
Section("Most Used Apps") {
|
||||||
|
ForEach(appUsageData, id: \.name) { app in
|
||||||
|
AppUsageRow(
|
||||||
|
appName: app.name,
|
||||||
|
usageTime: app.time,
|
||||||
|
iconName: app.icon,
|
||||||
|
usagePercentage: app.percentage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listStyle(.insetGrouped) // Use inset grouped style for modern look
|
||||||
|
.navigationTitle("Today's Usage")
|
||||||
|
// No need for explicit background color, List handles it
|
||||||
|
}
|
||||||
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to format seconds into hours/minutes
|
||||||
|
func formattedTime(seconds: Double) -> String {
|
||||||
|
let formatter = DateComponentsFormatter()
|
||||||
|
formatter.allowedUnits = [.hour, .minute]
|
||||||
|
formatter.unitsStyle = .abbreviated
|
||||||
|
return formatter.string(from: TimeInterval(seconds)) ?? "0m"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper view for displaying app usage rows
|
||||||
|
struct AppUsageRow: View {
|
||||||
|
let appName: String
|
||||||
|
let usageTime: String
|
||||||
|
let iconName: String
|
||||||
|
let usagePercentage: Double
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack(spacing: 15) { // Add spacing between elements
|
||||||
|
Image(systemName: iconName)
|
||||||
|
.font(.title3) // Slightly larger icon
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.frame(width: 36, height: 36) // Slightly larger frame
|
||||||
|
.background(Color.accentColor)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 8)) // Use rounded rectangle for icon background
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 2) { // Reduced spacing in text stack
|
||||||
|
Text(appName)
|
||||||
|
.font(.body)
|
||||||
|
ProgressView(value: usagePercentage)
|
||||||
|
.progressViewStyle(LinearProgressViewStyle(tint: .accentColor))
|
||||||
|
.frame(height: 5) // Slightly thicker bar
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer() // Pushes time to the right
|
||||||
|
|
||||||
|
Text(usageTime)
|
||||||
|
.font(.body) // Match body font
|
||||||
|
.foregroundColor(.secondary) // Use secondary color for less emphasis
|
||||||
|
}
|
||||||
|
.padding(.vertical, 6) // Adjust vertical padding within the row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the preview, it now shows the "Today" screen UI
|
||||||
struct ContentView_Previews: PreviewProvider {
|
struct ContentView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ContentView()
|
ContentView()
|
||||||
|
.preferredColorScheme(.dark) // Preview in dark mode too
|
||||||
|
ContentView()
|
||||||
|
.preferredColorScheme(.light)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import SwiftUI
|
|||||||
struct iOSApp: App {
|
struct iOSApp: App {
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView()
|
BottomBar()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user