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.layout.*
|
||||
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.*
|
||||
@ -26,6 +28,7 @@ import androidx.compose.material3.TextButton
|
||||
fun DesktopApp() {
|
||||
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
|
||||
@ -43,7 +46,7 @@ fun DesktopApp() {
|
||||
// Sidebar
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(200.dp)
|
||||
.width(if (isSidebarCollapsed) 60.dp else 200.dp) // 动态宽度
|
||||
.fillMaxHeight()
|
||||
.background(surfaceColor)
|
||||
.padding(16.dp)
|
||||
@ -52,6 +55,7 @@ fun DesktopApp() {
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
if (!isSidebarCollapsed) {
|
||||
Text(
|
||||
"Activity Analyzer",
|
||||
fontSize = 16.sp,
|
||||
@ -59,45 +63,50 @@ fun DesktopApp() {
|
||||
color = textColor
|
||||
)
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
|
||||
// Navigation items
|
||||
NavItem(
|
||||
icon = Icons.Default.Home,
|
||||
text = "Home",
|
||||
text = if (isSidebarCollapsed) "" else "Home", // 根据收缩状态显示文本
|
||||
isSelected = selectedItem == 0,
|
||||
onClick = { selectedItem = 0 },
|
||||
textColor = textColor,
|
||||
accentColor = accentColor
|
||||
accentColor = accentColor,
|
||||
isSidebarCollapsed = isSidebarCollapsed
|
||||
)
|
||||
NavItem(
|
||||
icon = Icons.Default.BarChart,
|
||||
text = "Analytics",
|
||||
text = if (isSidebarCollapsed) "" else "Analytics",
|
||||
isSelected = selectedItem == 1,
|
||||
onClick = { selectedItem = 1 },
|
||||
textColor = textColor,
|
||||
accentColor = accentColor
|
||||
accentColor = accentColor,
|
||||
isSidebarCollapsed = isSidebarCollapsed
|
||||
)
|
||||
NavItem(
|
||||
icon = Icons.Default.Dataset,
|
||||
text = "Reports",
|
||||
text = if (isSidebarCollapsed) "" else "Reports",
|
||||
isSelected = selectedItem == 2,
|
||||
onClick = { selectedItem = 2 },
|
||||
textColor = textColor,
|
||||
accentColor = accentColor
|
||||
accentColor = accentColor,
|
||||
isSidebarCollapsed = isSidebarCollapsed
|
||||
)
|
||||
NavItem(
|
||||
icon = Icons.Default.Settings,
|
||||
text = "Settings",
|
||||
text = if (isSidebarCollapsed) "" else "Settings",
|
||||
isSelected = selectedItem == 3,
|
||||
onClick = { selectedItem = 3 },
|
||||
textColor = textColor,
|
||||
accentColor = accentColor
|
||||
accentColor = accentColor,
|
||||
isSidebarCollapsed = isSidebarCollapsed
|
||||
)
|
||||
|
||||
// Spacer for alignment
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
// Theme switch
|
||||
if (!isSidebarCollapsed) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.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
|
||||
@ -147,18 +169,20 @@ fun NavItem(
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
textColor: Color,
|
||||
accentColor: Color
|
||||
accentColor: Color,
|
||||
isSidebarCollapsed: Boolean
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(if (isSelected) accentColor.copy(alpha = 0.1f) else Color.Transparent)
|
||||
.padding(12.dp)
|
||||
.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() }),
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
Row(
|
||||
@ -168,13 +192,19 @@ fun NavItem(
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
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,
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
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),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||||
) {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = "Welcome Back!",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
}
|
||||
|
||||
// Quick actions
|
||||
Card(
|
||||
@ -207,11 +239,13 @@ fun HomeScreen(textColor: Color, surfaceColor: Color) {
|
||||
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)
|
||||
) {
|
||||
@ -228,38 +262,38 @@ fun HomeScreen(textColor: Color, surfaceColor: Color) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
// // 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
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
|
||||
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.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 */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@ -21,6 +22,7 @@
|
||||
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>"; };
|
||||
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 */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -74,6 +76,7 @@
|
||||
7555FF82242A565900829871 /* ContentView.swift */,
|
||||
7555FF8C242A565B00829871 /* Info.plist */,
|
||||
2152FB032600AC8F00CF470E /* iOSApp.swift */,
|
||||
C2894CEB2DB7FC08006301A9 /* BottomBar.swift */,
|
||||
058557D7273AAEEB004C7B11 /* Preview Content */,
|
||||
);
|
||||
path = iosApp;
|
||||
@ -186,6 +189,7 @@
|
||||
files = (
|
||||
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
|
||||
7555FF83242A565900829871 /* ContentView.swift in Sources */,
|
||||
C2894CEC2DB7FC08006301A9 /* BottomBar.swift in Sources */,
|
||||
);
|
||||
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 Shared
|
||||
|
||||
struct ContentView: View {
|
||||
@State private var showContent = false
|
||||
struct ContentView: View { // This now represents the "Today" view
|
||||
// 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 {
|
||||
VStack {
|
||||
Button("Click me!") {
|
||||
withAnimation {
|
||||
showContent = !showContent
|
||||
}
|
||||
}
|
||||
NavigationView {
|
||||
// Use List for standard iOS table view appearance
|
||||
List {
|
||||
// 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 {
|
||||
VStack(spacing: 16) {
|
||||
Image(systemName: "swift")
|
||||
.font(.system(size: 200))
|
||||
// Progress Ring Visualization
|
||||
ZStack {
|
||||
Circle()
|
||||
.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)
|
||||
Text("SwiftUI: \(Greeting().greet())")
|
||||
}
|
||||
.transition(.move(edge: .top).combined(with: .opacity))
|
||||
.rotationEffect(Angle(degrees: 270.0))
|
||||
.animation(.linear, value: currentUsage)
|
||||
|
||||
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)
|
||||
.padding()
|
||||
.frame(width: 180, height: 180) // Slightly larger ring
|
||||
.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 {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
.preferredColorScheme(.dark) // Preview in dark mode too
|
||||
ContentView()
|
||||
.preferredColorScheme(.light)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import SwiftUI
|
||||
struct iOSApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
BottomBar()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user