From a63ed44e4955e4df8e633e4aced3e13ed5c5238b Mon Sep 17 00:00:00 2001 From: songyc macbook Date: Fri, 26 Jun 2026 01:14:51 +0900 Subject: [PATCH] remove LemonLime Project --- myApp/LemonLimeTracker/CLAUDE.md | 21 - .../AccentColor.colorset/Contents.json | 11 - .../AppIcon.appiconset/Contents.json | 35 -- .../IOS/Assets.xcassets/Contents.json | 6 - myApp/LemonLimeTracker/IOS/ContentView.swift | 24 - myApp/LemonLimeTracker/IOS/Core/Theme.swift | 21 - .../IOS/Core/TimerActivityAttributes.swift | 14 - .../IOS/LemonLimeTrackerApp.swift | 19 - .../IOS/Models/Category.swift | 18 - myApp/LemonLimeTracker/IOS/Models/Goal.swift | 35 -- .../IOS/Models/TaskItem.swift | 28 - .../LemonLimeTracker/IOS/Models/TaskLog.swift | 19 - .../IOS/Repositories/SwiftDataService.swift | 24 - .../Resources/en.lproj/Localizable.strings | 72 --- .../Resources/ko.lproj/Localizable.strings | 72 --- .../IOS/ViewModels/CategoryViewModel.swift | 45 -- .../IOS/ViewModels/DashboardViewModel.swift | 84 --- .../IOS/ViewModels/GoalViewModel.swift | 93 --- .../IOS/ViewModels/SettingsViewModel.swift | 76 --- .../IOS/ViewModels/TaskViewModel.swift | 157 ----- .../IOS/Views/CategoryView.swift | 177 ------ .../IOS/Views/DashboardView.swift | 160 ------ .../LemonLimeTracker/IOS/Views/GoalView.swift | 306 ---------- .../IOS/Views/LaunchScreenView.swift | 22 - .../IOS/Views/MainTabView.swift | 47 -- .../LemonLimeTracker/IOS/Views/RootView.swift | 24 - .../IOS/Views/SettingsView.swift | 107 ---- .../IOS/Views/TaskTrackerView.swift | 212 ------- .../LemonLimeTracker-Info.plist | 13 - .../project.pbxproj | 543 ------------------ .../contents.xcworkspacedata | 7 - .../UserInterfaceState.xcuserstate | Bin 35877 -> 0 bytes .../xcschemes/xcschememanagement.plist | 19 - .../AccentColor.colorset/Contents.json | 11 - .../AppIcon.appiconset/Contents.json | 35 -- .../Assets.xcassets/Contents.json | 6 - .../WidgetBackground.colorset/Contents.json | 11 - .../LemonLimeWidget/Info.plist | 11 - .../LemonLimeWidget/LemonLimeWidget.swift | 84 --- .../LemonLimeWidgetBundle.swift | 17 - .../LemonLimeWidgetLiveActivity.swift | 125 ---- 41 files changed, 2811 deletions(-) delete mode 100644 myApp/LemonLimeTracker/CLAUDE.md delete mode 100644 myApp/LemonLimeTracker/IOS/Assets.xcassets/AccentColor.colorset/Contents.json delete mode 100644 myApp/LemonLimeTracker/IOS/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 myApp/LemonLimeTracker/IOS/Assets.xcassets/Contents.json delete mode 100644 myApp/LemonLimeTracker/IOS/ContentView.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Core/Theme.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Core/TimerActivityAttributes.swift delete mode 100644 myApp/LemonLimeTracker/IOS/LemonLimeTrackerApp.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Models/Category.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Models/Goal.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Models/TaskItem.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Models/TaskLog.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Repositories/SwiftDataService.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Resources/en.lproj/Localizable.strings delete mode 100644 myApp/LemonLimeTracker/IOS/Resources/ko.lproj/Localizable.strings delete mode 100644 myApp/LemonLimeTracker/IOS/ViewModels/CategoryViewModel.swift delete mode 100644 myApp/LemonLimeTracker/IOS/ViewModels/DashboardViewModel.swift delete mode 100644 myApp/LemonLimeTracker/IOS/ViewModels/GoalViewModel.swift delete mode 100644 myApp/LemonLimeTracker/IOS/ViewModels/SettingsViewModel.swift delete mode 100644 myApp/LemonLimeTracker/IOS/ViewModels/TaskViewModel.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Views/CategoryView.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Views/DashboardView.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Views/GoalView.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Views/LaunchScreenView.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Views/MainTabView.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Views/RootView.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Views/SettingsView.swift delete mode 100644 myApp/LemonLimeTracker/IOS/Views/TaskTrackerView.swift delete mode 100644 myApp/LemonLimeTracker/LemonLimeTracker-Info.plist delete mode 100644 myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/project.pbxproj delete mode 100644 myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/project.xcworkspace/xcuserdata/ceuak.xcuserdatad/UserInterfaceState.xcuserstate delete mode 100644 myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/xcuserdata/ceuak.xcuserdatad/xcschemes/xcschememanagement.plist delete mode 100644 myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/AccentColor.colorset/Contents.json delete mode 100644 myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/Contents.json delete mode 100644 myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json delete mode 100644 myApp/LemonLimeTracker/LemonLimeWidget/Info.plist delete mode 100644 myApp/LemonLimeTracker/LemonLimeWidget/LemonLimeWidget.swift delete mode 100644 myApp/LemonLimeTracker/LemonLimeWidget/LemonLimeWidgetBundle.swift delete mode 100644 myApp/LemonLimeTracker/LemonLimeWidget/LemonLimeWidgetLiveActivity.swift diff --git a/myApp/LemonLimeTracker/CLAUDE.md b/myApp/LemonLimeTracker/CLAUDE.md deleted file mode 100644 index 00a18fe..0000000 --- a/myApp/LemonLimeTracker/CLAUDE.md +++ /dev/null @@ -1,21 +0,0 @@ -# LemonLimeTracker Project Guidelines - -## Core Rules & Constraints -- **NO Paid Entitlements**: NEVER add CloudKit, iCloud sync, App Groups, or Remote Notifications to Xcode Signing & Capabilities. Avoid build errors. -- **Persistence**: Use strictly local SwiftData. Do NOT use `NSPersistentCloudKitContainer`. - -## Architecture & Future Scope -- **Pattern**: Clean Architecture + MVVM. -- **Repository Pattern**: Strict separation of Data processing (SwiftData) and UI Views to allow easy future integration of CloudKit, Apple Watch, and Widgets. -- **Folder Structure**: - - `IOS/Models`: SwiftData Models (Task, Category, Goal, Log). - - `IOS/Repositories`: Data access layer (Protocols + Implementations). - - `IOS/ViewModels`: Business logic (`@Observable`). - - `IOS/Views`: SwiftUI Views. - - `IOS/Core`: App entry point, Constants, ActivityKit. - - `IOS/Resources`: Assets, Info.plist, Localizable.strings. - -## UI / Design System -- **Light Mode**: Green, Yellow, White. -- **Dark Mode**: Green, Yellow, Black. -- **Localization**: Default is Korean. Support English. diff --git a/myApp/LemonLimeTracker/IOS/Assets.xcassets/AccentColor.colorset/Contents.json b/myApp/LemonLimeTracker/IOS/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb87897..0000000 --- a/myApp/LemonLimeTracker/IOS/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/myApp/LemonLimeTracker/IOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/myApp/LemonLimeTracker/IOS/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 2305880..0000000 --- a/myApp/LemonLimeTracker/IOS/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "tinted" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/myApp/LemonLimeTracker/IOS/Assets.xcassets/Contents.json b/myApp/LemonLimeTracker/IOS/Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/myApp/LemonLimeTracker/IOS/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/myApp/LemonLimeTracker/IOS/ContentView.swift b/myApp/LemonLimeTracker/IOS/ContentView.swift deleted file mode 100644 index e245bef..0000000 --- a/myApp/LemonLimeTracker/IOS/ContentView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ContentView.swift -// LemonLimeTracker -// -// Created by 송예찬 on 6/18/26. -// - -import SwiftUI - -struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") - } - .padding() - } -} - -#Preview { - ContentView() -} diff --git a/myApp/LemonLimeTracker/IOS/Core/Theme.swift b/myApp/LemonLimeTracker/IOS/Core/Theme.swift deleted file mode 100644 index a62070a..0000000 --- a/myApp/LemonLimeTracker/IOS/Core/Theme.swift +++ /dev/null @@ -1,21 +0,0 @@ -import SwiftUI -import UIKit - -enum Theme { - static let green = Color(red: 0.18, green: 0.72, blue: 0.32) - static let yellow = Color(red: 0.96, green: 0.86, blue: 0.12) - - static let background = Color(UIColor { traits in - traits.userInterfaceStyle == .dark ? .black : .white - }) - - static let surface = Color(UIColor { traits in - traits.userInterfaceStyle == .dark - ? UIColor(red: 0.10, green: 0.10, blue: 0.10, alpha: 1) - : UIColor(red: 0.97, green: 0.97, blue: 0.97, alpha: 1) - }) - - static let primaryText = Color(UIColor { traits in - traits.userInterfaceStyle == .dark ? .white : .black - }) -} diff --git a/myApp/LemonLimeTracker/IOS/Core/TimerActivityAttributes.swift b/myApp/LemonLimeTracker/IOS/Core/TimerActivityAttributes.swift deleted file mode 100644 index 4b3f522..0000000 --- a/myApp/LemonLimeTracker/IOS/Core/TimerActivityAttributes.swift +++ /dev/null @@ -1,14 +0,0 @@ -#if os(iOS) -import ActivityKit -import Foundation - -struct TimerActivityAttributes: ActivityAttributes { - struct ContentState: Codable, Hashable { - var startDate: Date - var elapsedSeconds: Int - } - - var taskName: String - var taskIcon: String -} -#endif diff --git a/myApp/LemonLimeTracker/IOS/LemonLimeTrackerApp.swift b/myApp/LemonLimeTracker/IOS/LemonLimeTrackerApp.swift deleted file mode 100644 index 434cfec..0000000 --- a/myApp/LemonLimeTracker/IOS/LemonLimeTrackerApp.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// LemonLimeTrackerApp.swift -// LemonLimeTracker -// -// Created by 송예찬 on 6/18/26. -// - -import SwiftUI -import SwiftData - -@main -struct LemonLimeTrackerApp: App { - var body: some Scene { - WindowGroup { - RootView() - } - .modelContainer(SwiftDataService.shared.container) - } -} diff --git a/myApp/LemonLimeTracker/IOS/Models/Category.swift b/myApp/LemonLimeTracker/IOS/Models/Category.swift deleted file mode 100644 index 79339cb..0000000 --- a/myApp/LemonLimeTracker/IOS/Models/Category.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation -import SwiftData - -@Model -final class Category { - @Attribute(.unique) var id: UUID - var name: String - var colorHex: String - - @Relationship(deleteRule: .cascade, inverse: \TaskItem.category) - var tasks: [TaskItem] = [] - - init(id: UUID = UUID(), name: String, colorHex: String) { - self.id = id - self.name = name - self.colorHex = colorHex - } -} diff --git a/myApp/LemonLimeTracker/IOS/Models/Goal.swift b/myApp/LemonLimeTracker/IOS/Models/Goal.swift deleted file mode 100644 index 959e939..0000000 --- a/myApp/LemonLimeTracker/IOS/Models/Goal.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation -import SwiftData - -enum GoalFrequency: String, Codable { - case daily - case weekly - case monthly -} - -@Model -final class Goal { - @Attribute(.unique) var id: UUID - var targetType: String // "count" or "duration" - var conditions: Double // threshold value to meet the goal - var frequency: GoalFrequency - - var task: TaskItem? - var category: Category? - - init( - id: UUID = UUID(), - targetType: String, - conditions: Double, - frequency: GoalFrequency, - task: TaskItem? = nil, - category: Category? = nil - ) { - self.id = id - self.targetType = targetType - self.conditions = conditions - self.frequency = frequency - self.task = task - self.category = category - } -} diff --git a/myApp/LemonLimeTracker/IOS/Models/TaskItem.swift b/myApp/LemonLimeTracker/IOS/Models/TaskItem.swift deleted file mode 100644 index bbe039e..0000000 --- a/myApp/LemonLimeTracker/IOS/Models/TaskItem.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation -import SwiftData - -enum TaskType: String, Codable { - case count - case time -} - -@Model -final class TaskItem { - @Attribute(.unique) var id: UUID - var name: String - var icon: String - var type: TaskType - - var category: Category? - - @Relationship(deleteRule: .cascade, inverse: \TaskLog.task) - var logs: [TaskLog] = [] - - init(id: UUID = UUID(), name: String, icon: String, type: TaskType, category: Category? = nil) { - self.id = id - self.name = name - self.icon = icon - self.type = type - self.category = category - } -} diff --git a/myApp/LemonLimeTracker/IOS/Models/TaskLog.swift b/myApp/LemonLimeTracker/IOS/Models/TaskLog.swift deleted file mode 100644 index ccd5273..0000000 --- a/myApp/LemonLimeTracker/IOS/Models/TaskLog.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation -import SwiftData - -@Model -final class TaskLog { - @Attribute(.unique) var id: UUID - var date: Date - var duration: TimeInterval - var count: Int - - var task: TaskItem? - - init(id: UUID = UUID(), date: Date = .now, duration: TimeInterval = 0, count: Int = 0) { - self.id = id - self.date = date - self.duration = duration - self.count = count - } -} diff --git a/myApp/LemonLimeTracker/IOS/Repositories/SwiftDataService.swift b/myApp/LemonLimeTracker/IOS/Repositories/SwiftDataService.swift deleted file mode 100644 index b056f1e..0000000 --- a/myApp/LemonLimeTracker/IOS/Repositories/SwiftDataService.swift +++ /dev/null @@ -1,24 +0,0 @@ -import Foundation -import SwiftData - -@MainActor -final class SwiftDataService { - static let shared = SwiftDataService() - - let container: ModelContainer - - private init() { - let schema = Schema([ - Category.self, - TaskItem.self, - TaskLog.self, - Goal.self - ]) - let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) - do { - container = try ModelContainer(for: schema, configurations: config) - } catch { - fatalError("SwiftData container failed to initialize: \(error)") - } - } -} diff --git a/myApp/LemonLimeTracker/IOS/Resources/en.lproj/Localizable.strings b/myApp/LemonLimeTracker/IOS/Resources/en.lproj/Localizable.strings deleted file mode 100644 index 0d88802..0000000 --- a/myApp/LemonLimeTracker/IOS/Resources/en.lproj/Localizable.strings +++ /dev/null @@ -1,72 +0,0 @@ -/* Tab Bar */ -"tab.dashboard" = "Dashboard"; -"tab.category" = "Category"; -"tab.tasks" = "Tasks"; -"tab.goals" = "Goals"; -"tab.settings" = "Settings"; - -/* Task Tracker */ -"task.empty" = "No tasks added yet"; -"task.add" = "Add Task"; -"task.name" = "Name"; -"task.icon" = "Icon"; -"task.type" = "Type"; -"task.type.count" = "Count"; -"task.type.time" = "Time"; -"task.add.count" = "Add Count"; -"task.delete" = "Delete"; -"task.category" = "Category"; -"task.category.none" = "None"; - -/* Buttons */ -"button.cancel" = "Cancel"; -"button.add" = "Add"; - -/* Category */ -"category.empty" = "No categories yet"; -"category.add" = "Add Category"; -"category.name" = "Name"; -"category.color" = "Color"; -"category.delete" = "Delete"; -"category.task.count" = "tasks"; - -/* Goals */ -"goal.empty" = "No goals yet"; -"goal.add" = "Add Goal"; -"goal.chart.title" = "Goal Progress"; -"goal.progress" = "Progress"; -"goal.target" = "Target"; -"goal.target.task" = "Task"; -"goal.target.category" = "Category"; -"goal.no.target" = "None"; -"goal.condition" = "Condition"; -"goal.condition.count" = "Count"; -"goal.condition.time" = "Time"; -"goal.frequency" = "Frequency"; -"goal.frequency.daily" = "Daily"; -"goal.frequency.weekly" = "Weekly"; -"goal.frequency.monthly" = "Monthly"; -"goal.threshold" = "Target Value"; -"goal.delete" = "Delete"; - -/* Dashboard */ -"dashboard.progress.title" = "Today's Progress"; -"dashboard.goals.title" = "Top Goals"; -"dashboard.tasks.title" = "Remaining Tasks"; -"dashboard.tasks.empty" = "All tasks done for today!"; - -/* Settings */ -"settings.section.general" = "Preferences"; -"settings.language" = "Language"; -"settings.language.system" = "System"; -"settings.firstWeekday" = "Start of Week"; -"settings.firstWeekday.sunday" = "Sunday"; -"settings.firstWeekday.monday" = "Monday"; -"settings.dayStart" = "Start of Day"; -"settings.section.premium" = "Premium Features"; -"settings.premium.footnote" = "Premium Feature — coming soon"; -"settings.premium.cloudSync" = "Cloud Data Sync"; -"settings.premium.appleWatch" = "Apple Watch Support"; -"settings.premium.ipadMacSync" = "iPad & Mac Sync"; -"settings.premium.widgets" = "Home/Lock Screen Widgets"; -"settings.premium.siriShortcuts" = "Siri & Shortcuts"; diff --git a/myApp/LemonLimeTracker/IOS/Resources/ko.lproj/Localizable.strings b/myApp/LemonLimeTracker/IOS/Resources/ko.lproj/Localizable.strings deleted file mode 100644 index 6e2b31d..0000000 --- a/myApp/LemonLimeTracker/IOS/Resources/ko.lproj/Localizable.strings +++ /dev/null @@ -1,72 +0,0 @@ -/* Tab Bar */ -"tab.dashboard" = "대시보드"; -"tab.category" = "카테고리"; -"tab.tasks" = "작업 추적"; -"tab.goals" = "목표"; -"tab.settings" = "설정"; - -/* Task Tracker */ -"task.empty" = "등록된 작업이 없습니다"; -"task.add" = "작업 추가"; -"task.name" = "이름"; -"task.icon" = "아이콘"; -"task.type" = "유형"; -"task.type.count" = "횟수"; -"task.type.time" = "시간"; -"task.add.count" = "횟수 추가"; -"task.delete" = "삭제"; -"task.category" = "카테고리"; -"task.category.none" = "없음"; - -/* Buttons */ -"button.cancel" = "취소"; -"button.add" = "추가"; - -/* Category */ -"category.empty" = "카테고리가 없습니다"; -"category.add" = "카테고리 추가"; -"category.name" = "이름"; -"category.color" = "색상"; -"category.delete" = "삭제"; -"category.task.count" = "작업"; - -/* Goals */ -"goal.empty" = "등록된 목표가 없습니다"; -"goal.add" = "목표 추가"; -"goal.chart.title" = "목표 진행률"; -"goal.progress" = "진행률"; -"goal.target" = "대상"; -"goal.target.task" = "작업"; -"goal.target.category" = "카테고리"; -"goal.no.target" = "없음"; -"goal.condition" = "조건"; -"goal.condition.count" = "횟수"; -"goal.condition.time" = "시간"; -"goal.frequency" = "주기"; -"goal.frequency.daily" = "매일"; -"goal.frequency.weekly" = "매주"; -"goal.frequency.monthly" = "매월"; -"goal.threshold" = "목표값"; -"goal.delete" = "삭제"; - -/* Dashboard */ -"dashboard.progress.title" = "오늘의 진행률"; -"dashboard.goals.title" = "주요 목표"; -"dashboard.tasks.title" = "남은 작업"; -"dashboard.tasks.empty" = "오늘의 작업을 모두 완료했습니다!"; - -/* Settings */ -"settings.section.general" = "환경설정"; -"settings.language" = "언어"; -"settings.language.system" = "시스템"; -"settings.firstWeekday" = "주 시작일"; -"settings.firstWeekday.sunday" = "일요일"; -"settings.firstWeekday.monday" = "월요일"; -"settings.dayStart" = "하루 시작 시간"; -"settings.section.premium" = "프리미엄 기능"; -"settings.premium.footnote" = "프리미엄 기능 — 출시 예정"; -"settings.premium.cloudSync" = "클라우드 데이터 동기화"; -"settings.premium.appleWatch" = "Apple Watch 지원"; -"settings.premium.ipadMacSync" = "iPad 및 Mac 동기화"; -"settings.premium.widgets" = "홈/잠금 화면 위젯"; -"settings.premium.siriShortcuts" = "Siri 및 단축어"; diff --git a/myApp/LemonLimeTracker/IOS/ViewModels/CategoryViewModel.swift b/myApp/LemonLimeTracker/IOS/ViewModels/CategoryViewModel.swift deleted file mode 100644 index a7a858b..0000000 --- a/myApp/LemonLimeTracker/IOS/ViewModels/CategoryViewModel.swift +++ /dev/null @@ -1,45 +0,0 @@ -import Foundation -import Observation -import SwiftData - -@MainActor -@Observable -final class CategoryViewModel { - private(set) var categories: [Category] = [] - private var context: ModelContext? - - static let presetColors: [String] = [ - "#2EB852", "#F5DC1F", "#4A90D9", "#F5A623", - "#9B59B6", "#E74C3C", "#1ABC9C", "#F39C12" - ] - - func setup(context: ModelContext) { - self.context = context - fetchCategories() - } - - func fetchCategories() { - guard let context else { return } - let descriptor = FetchDescriptor(sortBy: [SortDescriptor(\.name)]) - categories = (try? context.fetch(descriptor)) ?? [] - } - - func addCategory(name: String, colorHex: String) { - guard let context else { return } - let category = Category(name: name, colorHex: colorHex) - context.insert(category) - try? context.save() - fetchCategories() - } - - func deleteCategory(_ category: Category) { - guard let context else { return } - context.delete(category) - try? context.save() - fetchCategories() - } - - func taskCount(for category: Category) -> Int { - category.tasks.count - } -} diff --git a/myApp/LemonLimeTracker/IOS/ViewModels/DashboardViewModel.swift b/myApp/LemonLimeTracker/IOS/ViewModels/DashboardViewModel.swift deleted file mode 100644 index 85a12a5..0000000 --- a/myApp/LemonLimeTracker/IOS/ViewModels/DashboardViewModel.swift +++ /dev/null @@ -1,84 +0,0 @@ -import Foundation -import Observation -import SwiftData - -@MainActor -@Observable -final class DashboardViewModel { - private(set) var dailyGoalProgress: [GoalProgress] = [] - private(set) var remainingTasks: [TaskItem] = [] - private(set) var completedTaskCount: Int = 0 - private(set) var totalTaskCount: Int = 0 - - private var context: ModelContext? - - var overallProgress: Double { - if !dailyGoalProgress.isEmpty { - let sum = dailyGoalProgress.reduce(0.0) { $0 + $1.ratio } - return sum / Double(dailyGoalProgress.count) - } - guard totalTaskCount > 0 else { return 0 } - return Double(completedTaskCount) / Double(totalTaskCount) - } - - var topGoalProgress: [GoalProgress] { - Array(dailyGoalProgress.sorted { $0.ratio < $1.ratio }.prefix(3)) - } - - func setup(context: ModelContext) { - self.context = context - refresh() - } - - func refresh() { - guard let context else { return } - - let goalDescriptor = FetchDescriptor() - let goals = (try? context.fetch(goalDescriptor)) ?? [] - let dailyGoals = goals.filter { $0.frequency == .daily } - dailyGoalProgress = dailyGoals.map { goal in - let current = todayValue(for: goal) - return GoalProgress(goal: goal, current: current, target: goal.conditions) - } - - let taskDescriptor = FetchDescriptor(sortBy: [SortDescriptor(\.name)]) - let tasks = (try? context.fetch(taskDescriptor)) ?? [] - totalTaskCount = tasks.count - remainingTasks = tasks.filter { !isCompletedToday($0) } - completedTaskCount = totalTaskCount - remainingTasks.count - } - - func addCount(to task: TaskItem) { - guard let context else { return } - let log = TaskLog(date: .now, duration: 0, count: 1) - log.task = task - context.insert(log) - try? context.save() - refresh() - } - - private func isCompletedToday(_ task: TaskItem) -> Bool { - let todayLogs = task.logs.filter { Calendar.current.isDateInToday($0.date) } - switch task.type { - case .count: - return todayLogs.reduce(0) { $0 + $1.count } > 0 - case .time: - return !todayLogs.isEmpty - } - } - - private func todayValue(for goal: Goal) -> Double { - let logs: [TaskLog] - if let task = goal.task { - logs = task.logs - } else if let category = goal.category { - logs = category.tasks.flatMap { $0.logs } - } else { - logs = [] - } - let todayLogs = logs.filter { Calendar.current.isDateInToday($0.date) } - return goal.targetType == "count" - ? Double(todayLogs.reduce(0) { $0 + $1.count }) - : todayLogs.reduce(0.0) { $0 + $1.duration } - } -} diff --git a/myApp/LemonLimeTracker/IOS/ViewModels/GoalViewModel.swift b/myApp/LemonLimeTracker/IOS/ViewModels/GoalViewModel.swift deleted file mode 100644 index 196b392..0000000 --- a/myApp/LemonLimeTracker/IOS/ViewModels/GoalViewModel.swift +++ /dev/null @@ -1,93 +0,0 @@ -import Foundation -import Observation -import SwiftData - -enum GoalTargetType: String, CaseIterable { - case task - case category -} - -struct GoalProgress: Identifiable { - var id: UUID { goal.id } - let goal: Goal - let current: Double - let target: Double - - var ratio: Double { target > 0 ? min(current / target, 1.0) : 0.0 } - var isCompleted: Bool { current >= target } - - var displayName: String { - if let task = goal.task { return "\(task.icon) \(task.name)" } - if let category = goal.category { return category.name } - return "—" - } - - var percentText: String { "\(Int(ratio * 100))%" } -} - -@MainActor -@Observable -final class GoalViewModel { - private(set) var goals: [Goal] = [] - private(set) var goalProgress: [GoalProgress] = [] - private var context: ModelContext? - - func setup(context: ModelContext) { - self.context = context - fetchGoals() - } - - func fetchGoals() { - guard let context else { return } - let descriptor = FetchDescriptor() - goals = (try? context.fetch(descriptor)) ?? [] - computeProgress() - } - - func addGoal(targetType: String, conditions: Double, frequency: GoalFrequency, task: TaskItem?, category: Category?) { - guard let context else { return } - let goal = Goal(targetType: targetType, conditions: conditions, frequency: frequency, task: task, category: category) - context.insert(goal) - try? context.save() - fetchGoals() - } - - func deleteGoal(_ goal: Goal) { - guard let context else { return } - context.delete(goal) - try? context.save() - fetchGoals() - } - - private func computeProgress() { - let now = Date.now - let cal = Calendar.current - goalProgress = goals.map { goal in - let logs = relevantLogs(for: goal, reference: now, calendar: cal) - let current: Double = goal.targetType == "count" - ? Double(logs.reduce(0) { $0 + $1.count }) - : logs.reduce(0.0) { $0 + $1.duration } - return GoalProgress(goal: goal, current: current, target: goal.conditions) - } - } - - private func relevantLogs(for goal: Goal, reference: Date, calendar: Calendar) -> [TaskLog] { - let allLogs: [TaskLog] - if let task = goal.task { - allLogs = task.logs - } else if let category = goal.category { - allLogs = category.tasks.flatMap { $0.logs } - } else { - return [] - } - return allLogs.filter { inPeriod($0.date, frequency: goal.frequency, reference: reference, calendar: calendar) } - } - - private func inPeriod(_ date: Date, frequency: GoalFrequency, reference: Date, calendar: Calendar) -> Bool { - switch frequency { - case .daily: return calendar.isDate(date, inSameDayAs: reference) - case .weekly: return calendar.isDate(date, equalTo: reference, toGranularity: .weekOfYear) - case .monthly: return calendar.isDate(date, equalTo: reference, toGranularity: .month) - } - } -} diff --git a/myApp/LemonLimeTracker/IOS/ViewModels/SettingsViewModel.swift b/myApp/LemonLimeTracker/IOS/ViewModels/SettingsViewModel.swift deleted file mode 100644 index cb5ec61..0000000 --- a/myApp/LemonLimeTracker/IOS/ViewModels/SettingsViewModel.swift +++ /dev/null @@ -1,76 +0,0 @@ -import Foundation -import Observation - -enum AppLanguage: String, CaseIterable, Identifiable { - case system - case english - case korean - - var id: String { rawValue } - - var displayName: String { - switch self { - case .system: return String(localized: "settings.language.system") - case .english: return "English" - case .korean: return "한국어" - } - } -} - -enum FirstWeekday: Int, CaseIterable, Identifiable { - case sunday = 1 - case monday = 2 - - var id: Int { rawValue } - - var displayName: String { - switch self { - case .sunday: return String(localized: "settings.firstWeekday.sunday") - case .monday: return String(localized: "settings.firstWeekday.monday") - } - } -} - -@MainActor -@Observable -final class SettingsViewModel { - private static let languageKey = "settings.language" - private static let firstWeekdayKey = "settings.firstWeekday" - private static let dayStartMinutesKey = "settings.dayStartMinutes" - - static let defaultDayStartMinutes = 4 * 60 // 04:00 AM - - var language: AppLanguage { - didSet { UserDefaults.standard.set(language.rawValue, forKey: Self.languageKey) } - } - - var firstWeekday: FirstWeekday { - didSet { UserDefaults.standard.set(firstWeekday.rawValue, forKey: Self.firstWeekdayKey) } - } - - /// Minutes since midnight marking when a "day" starts for goal/log purposes. - var dayStartMinutes: Int { - didSet { UserDefaults.standard.set(dayStartMinutes, forKey: Self.dayStartMinutesKey) } - } - - var dayStartDate: Date { - get { - Calendar.current.date(bySettingHour: dayStartMinutes / 60, minute: dayStartMinutes % 60, second: 0, of: .now) ?? .now - } - set { - let comps = Calendar.current.dateComponents([.hour, .minute], from: newValue) - dayStartMinutes = (comps.hour ?? 0) * 60 + (comps.minute ?? 0) - } - } - - init() { - let defaults = UserDefaults.standard - language = AppLanguage(rawValue: defaults.string(forKey: Self.languageKey) ?? "") ?? .system - firstWeekday = FirstWeekday(rawValue: defaults.integer(forKey: Self.firstWeekdayKey)) ?? .sunday - if let storedMinutes = defaults.object(forKey: Self.dayStartMinutesKey) as? Int { - dayStartMinutes = storedMinutes - } else { - dayStartMinutes = Self.defaultDayStartMinutes - } - } -} diff --git a/myApp/LemonLimeTracker/IOS/ViewModels/TaskViewModel.swift b/myApp/LemonLimeTracker/IOS/ViewModels/TaskViewModel.swift deleted file mode 100644 index 0923425..0000000 --- a/myApp/LemonLimeTracker/IOS/ViewModels/TaskViewModel.swift +++ /dev/null @@ -1,157 +0,0 @@ -import Foundation -import Observation -import SwiftData -#if os(iOS) -import ActivityKit -#endif - -@MainActor -@Observable -final class TaskViewModel { - private(set) var tasks: [TaskItem] = [] - private(set) var activeTimerStartDates: [UUID: Date] = [:] - private(set) var elapsedSeconds: [UUID: Int] = [:] - - private var context: ModelContext? - private var timerTasks: [UUID: Task] = [:] -#if os(iOS) - private var liveActivities: [UUID: Activity] = [:] -#endif - - func setup(context: ModelContext) { - self.context = context - fetchTasks() - } - - func fetchTasks() { - guard let context else { return } - let descriptor = FetchDescriptor(sortBy: [SortDescriptor(\.name)]) - tasks = (try? context.fetch(descriptor)) ?? [] - } - - func addTask(name: String, icon: String, type: TaskType, category: Category? = nil) { - guard let context else { return } - let item = TaskItem(name: name, icon: icon, type: type, category: category) - context.insert(item) - try? context.save() - fetchTasks() - } - - func deleteTask(_ task: TaskItem) { - stopTimer(for: task) - guard let context else { return } - context.delete(task) - try? context.save() - fetchTasks() - } - - func addCount(to task: TaskItem) { - guard let context else { return } - let log = TaskLog(date: .now, duration: 0, count: 1) - log.task = task - context.insert(log) - try? context.save() - } - - func toggleTimer(for task: TaskItem) { - if activeTimerStartDates[task.id] != nil { - stopTimer(for: task) - } else { - startTimer(for: task) - } - } - - func isTimerRunning(for taskID: UUID) -> Bool { - activeTimerStartDates[taskID] != nil - } - - func formattedElapsed(for taskID: UUID) -> String { - let s = elapsedSeconds[taskID] ?? 0 - let h = s / 3600 - let m = (s % 3600) / 60 - let sec = s % 60 - return h > 0 - ? String(format: "%d:%02d:%02d", h, m, sec) - : String(format: "%02d:%02d", m, sec) - } - - func todayCount(for task: TaskItem) -> Int { - task.logs - .filter { Calendar.current.isDateInToday($0.date) } - .reduce(0) { $0 + $1.count } - } - - // MARK: - Timer - - private func startTimer(for task: TaskItem) { - let startDate = Date.now - activeTimerStartDates[task.id] = startDate - elapsedSeconds[task.id] = 0 - startLiveActivity(for: task, startDate: startDate) - - let taskID = task.id - timerTasks[taskID] = Task { [weak self] in - while !Task.isCancelled { - try? await Task.sleep(for: .seconds(1)) - guard !Task.isCancelled, let self else { break } - let elapsed = Int(Date.now.timeIntervalSince(startDate)) - self.elapsedSeconds[taskID] = elapsed - await self.updateLiveActivity(taskID: taskID, elapsed: elapsed, startDate: startDate) - } - } - } - - private func stopTimer(for task: TaskItem) { - guard let startDate = activeTimerStartDates[task.id] else { return } - - timerTasks[task.id]?.cancel() - timerTasks.removeValue(forKey: task.id) - - let duration = Date.now.timeIntervalSince(startDate) - activeTimerStartDates.removeValue(forKey: task.id) - elapsedSeconds.removeValue(forKey: task.id) - - endLiveActivity(for: task) - - guard let context else { return } - let log = TaskLog(date: .now, duration: duration, count: 0) - log.task = task - context.insert(log) - try? context.save() - } - - // MARK: - ActivityKit - - private func startLiveActivity(for task: TaskItem, startDate: Date) { -#if os(iOS) - guard ActivityAuthorizationInfo().areActivitiesEnabled else { return } - let attrs = TimerActivityAttributes(taskName: task.name, taskIcon: task.icon) - let state = TimerActivityAttributes.ContentState(startDate: startDate, elapsedSeconds: 0) - let content = ActivityContent(state: state, staleDate: nil) - do { - let activity = try Activity.request(attributes: attrs, content: content) - liveActivities[task.id] = activity - } catch { - // Live Activity unavailable (simulator, denied, or OS < 16.2) - } -#endif - } - - private func updateLiveActivity(taskID: UUID, elapsed: Int, startDate: Date) async { -#if os(iOS) - guard let activity = liveActivities[taskID] else { return } - let state = TimerActivityAttributes.ContentState(startDate: startDate, elapsedSeconds: elapsed) - await activity.update(ActivityContent(state: state, staleDate: nil)) -#endif - } - - private func endLiveActivity(for task: TaskItem) { -#if os(iOS) - guard let activity = liveActivities[task.id] else { return } - liveActivities.removeValue(forKey: task.id) - Task { - await activity.end(nil, dismissalPolicy: .immediate) - } -#endif - } -} diff --git a/myApp/LemonLimeTracker/IOS/Views/CategoryView.swift b/myApp/LemonLimeTracker/IOS/Views/CategoryView.swift deleted file mode 100644 index 2b6369f..0000000 --- a/myApp/LemonLimeTracker/IOS/Views/CategoryView.swift +++ /dev/null @@ -1,177 +0,0 @@ -import SwiftUI -import SwiftData - -struct CategoryView: View { - @Environment(\.modelContext) private var modelContext - @State private var viewModel = CategoryViewModel() - @State private var showAddSheet = false - - var body: some View { - ZStack { - Theme.background.ignoresSafeArea() - Group { - if viewModel.categories.isEmpty { - emptyState - } else { - categoryList - } - } - } - .navigationTitle(String(localized: "tab.category")) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button { showAddSheet = true } label: { - Image(systemName: "plus") - .foregroundStyle(Theme.green) - } - } - } - .sheet(isPresented: $showAddSheet) { - AddCategorySheet { name, colorHex in - viewModel.addCategory(name: name, colorHex: colorHex) - } - } - .onAppear { viewModel.setup(context: modelContext) } - } - - private var emptyState: some View { - VStack(spacing: 16) { - Image(systemName: "folder.badge.plus") - .font(.system(size: 56)) - .foregroundStyle(Theme.green.opacity(0.6)) - Text(String(localized: "category.empty")) - .foregroundStyle(Theme.primaryText.opacity(0.6)) - } - } - - private var categoryList: some View { - List { - ForEach(viewModel.categories) { category in - CategoryRow(category: category, taskCount: viewModel.taskCount(for: category)) - .listRowBackground(Theme.surface) - .swipeActions(edge: .trailing, allowsFullSwipe: false) { - Button(role: .destructive) { - viewModel.deleteCategory(category) - } label: { - Label(String(localized: "category.delete"), systemImage: "trash") - } - } - } - } - .listStyle(.plain) - .scrollContentBackground(.hidden) - } -} - -// MARK: - Category Row - -private struct CategoryRow: View { - let category: Category - let taskCount: Int - - var body: some View { - HStack(spacing: 12) { - Circle() - .fill(Color(hex: category.colorHex)) - .frame(width: 14, height: 14) - - Text(category.name) - .font(.body) - .fontWeight(.medium) - .foregroundStyle(Theme.primaryText) - - Spacer() - - Text("\(taskCount) \(String(localized: "category.task.count"))") - .font(.caption) - .foregroundStyle(Theme.primaryText.opacity(0.5)) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(Theme.green.opacity(0.12), in: Capsule()) - } - .padding(.vertical, 6) - } -} - -// MARK: - Add Category Sheet - -private struct AddCategorySheet: View { - @Environment(\.dismiss) private var dismiss - @State private var name = "" - @State private var selectedColor = CategoryViewModel.presetColors[0] - - let onAdd: (String, String) -> Void - - var body: some View { - NavigationStack { - Form { - Section { - TextField(String(localized: "category.name"), text: $name) - } - - Section(String(localized: "category.color")) { - LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 4), spacing: 16) { - ForEach(CategoryViewModel.presetColors, id: \.self) { colorHex in - Circle() - .fill(Color(hex: colorHex)) - .frame(width: 42, height: 42) - .overlay(alignment: .center) { - if selectedColor == colorHex { - Image(systemName: "checkmark") - .font(.caption) - .fontWeight(.bold) - .foregroundStyle(.white) - } - } - .overlay( - Circle() - .strokeBorder( - selectedColor == colorHex ? Theme.primaryText.opacity(0.4) : Color.clear, - lineWidth: 2 - ) - ) - .onTapGesture { selectedColor = colorHex } - } - } - .padding(.vertical, 8) - } - } - .navigationTitle(String(localized: "category.add")) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button(String(localized: "button.cancel")) { dismiss() } - } - ToolbarItem(placement: .confirmationAction) { - Button(String(localized: "button.add")) { - let trimmed = name.trimmingCharacters(in: .whitespaces) - guard !trimmed.isEmpty else { return } - onAdd(trimmed, selectedColor) - dismiss() - } - .disabled(name.trimmingCharacters(in: .whitespaces).isEmpty) - } - } - } - } -} - -// MARK: - Color Hex Extension - -extension Color { - init(hex: String) { - let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) - var int: UInt64 = 0 - Scanner(string: hex).scanHexInt64(&int) - let r = Double((int >> 16) & 0xFF) / 255 - let g = Double((int >> 8) & 0xFF) / 255 - let b = Double(int & 0xFF) / 255 - self.init(red: r, green: g, blue: b) - } -} - -#Preview { - NavigationStack { - CategoryView() - } -} diff --git a/myApp/LemonLimeTracker/IOS/Views/DashboardView.swift b/myApp/LemonLimeTracker/IOS/Views/DashboardView.swift deleted file mode 100644 index d1c8d18..0000000 --- a/myApp/LemonLimeTracker/IOS/Views/DashboardView.swift +++ /dev/null @@ -1,160 +0,0 @@ -import SwiftUI -import SwiftData - -struct DashboardView: View { - @Environment(\.modelContext) private var modelContext - @State private var viewModel = DashboardViewModel() - - var body: some View { - ZStack { - Theme.background.ignoresSafeArea() - ScrollView { - VStack(spacing: 20) { - progressRing - if !viewModel.topGoalProgress.isEmpty { - topGoals - } - remainingTasksSection - } - .padding() - } - } - .navigationTitle(String(localized: "tab.dashboard")) - .onAppear { viewModel.setup(context: modelContext) } - } - - // MARK: - Progress Ring - - private var progressRing: some View { - VStack(spacing: 12) { - ZStack { - Circle() - .stroke(Theme.surface, lineWidth: 16) - Circle() - .trim(from: 0, to: viewModel.overallProgress) - .stroke(Theme.green, style: StrokeStyle(lineWidth: 16, lineCap: .round)) - .rotationEffect(.degrees(-90)) - .animation(.easeInOut(duration: 0.4), value: viewModel.overallProgress) - VStack(spacing: 4) { - Text("\(Int(viewModel.overallProgress * 100))%") - .font(.system(size: 34, weight: .bold, design: .rounded)) - .foregroundStyle(Theme.primaryText) - Text(String(localized: "dashboard.progress.title")) - .font(.caption) - .foregroundStyle(Theme.primaryText.opacity(0.6)) - } - } - .frame(width: 180, height: 180) - .padding(.top, 8) - } - } - - // MARK: - Top Goals - - private var topGoals: some View { - VStack(alignment: .leading, spacing: 10) { - Text(String(localized: "dashboard.goals.title")) - .font(.headline) - .foregroundStyle(Theme.primaryText) - - VStack(spacing: 10) { - ForEach(viewModel.topGoalProgress) { progress in - TopGoalCard(progress: progress) - } - } - } - .frame(maxWidth: .infinity, alignment: .leading) - } - - // MARK: - Remaining Tasks - - private var remainingTasksSection: some View { - VStack(alignment: .leading, spacing: 10) { - Text(String(localized: "dashboard.tasks.title")) - .font(.headline) - .foregroundStyle(Theme.primaryText) - - if viewModel.remainingTasks.isEmpty { - Text(String(localized: "dashboard.tasks.empty")) - .font(.subheadline) - .foregroundStyle(Theme.primaryText.opacity(0.5)) - .frame(maxWidth: .infinity, alignment: .center) - .padding(.vertical, 24) - } else { - VStack(spacing: 8) { - ForEach(viewModel.remainingTasks) { task in - RemainingTaskRow(task: task) { - viewModel.addCount(to: task) - } - } - } - } - } - .frame(maxWidth: .infinity, alignment: .leading) - } -} - -// MARK: - Top Goal Card - -private struct TopGoalCard: View { - let progress: GoalProgress - - var body: some View { - HStack(spacing: 12) { - VStack(alignment: .leading, spacing: 2) { - Text(progress.displayName) - .font(.body) - .fontWeight(.semibold) - .foregroundStyle(Theme.primaryText) - ProgressView(value: progress.ratio) - .tint(progress.isCompleted ? Theme.green : Theme.yellow) - } - Text(progress.percentText) - .font(.caption) - .fontWeight(.bold) - .foregroundStyle(progress.isCompleted ? Theme.green : Theme.yellow) - } - .padding() - .background(Theme.surface, in: RoundedRectangle(cornerRadius: 12)) - } -} - -// MARK: - Remaining Task Row - -private struct RemainingTaskRow: View { - let task: TaskItem - let onAddCount: () -> Void - - var body: some View { - HStack(spacing: 12) { - Text(task.icon) - .font(.title3) - .frame(width: 30) - - Text(task.name) - .font(.body) - .foregroundStyle(Theme.primaryText) - - Spacer() - - if task.type == .count { - Button(action: onAddCount) { - Image(systemName: "plus.circle.fill") - .foregroundStyle(Theme.green) - } - .buttonStyle(.plain) - } else { - Image(systemName: "clock") - .foregroundStyle(Theme.primaryText.opacity(0.4)) - } - } - .padding(12) - .background(Theme.surface, in: RoundedRectangle(cornerRadius: 10)) - } -} - -#Preview { - NavigationStack { - DashboardView() - } -} diff --git a/myApp/LemonLimeTracker/IOS/Views/GoalView.swift b/myApp/LemonLimeTracker/IOS/Views/GoalView.swift deleted file mode 100644 index 8d60d24..0000000 --- a/myApp/LemonLimeTracker/IOS/Views/GoalView.swift +++ /dev/null @@ -1,306 +0,0 @@ -import SwiftUI -import SwiftData -import Charts - -struct GoalView: View { - @Environment(\.modelContext) private var modelContext - @State private var viewModel = GoalViewModel() - @State private var showAddSheet = false - - var body: some View { - ZStack { - Theme.background.ignoresSafeArea() - Group { - if viewModel.goals.isEmpty { - emptyState - } else { - goalContent - } - } - } - .navigationTitle(String(localized: "tab.goals")) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button { showAddSheet = true } label: { - Image(systemName: "plus") - .foregroundStyle(Theme.green) - } - } - } - .sheet(isPresented: $showAddSheet) { - AddGoalSheet { targetType, conditions, frequency, task, category in - viewModel.addGoal(targetType: targetType, conditions: conditions, frequency: frequency, task: task, category: category) - } - } - .onAppear { viewModel.setup(context: modelContext) } - } - - private var emptyState: some View { - VStack(spacing: 16) { - Image(systemName: "flag.badge.ellipsis") - .font(.system(size: 56)) - .foregroundStyle(Theme.green.opacity(0.6)) - Text(String(localized: "goal.empty")) - .foregroundStyle(Theme.primaryText.opacity(0.6)) - } - } - - private var goalContent: some View { - ScrollView { - VStack(spacing: 16) { - progressChart - .padding(.horizontal) - goalCards - .padding(.horizontal) - } - .padding(.vertical) - } - } - - // MARK: - Chart - - private var progressChart: some View { - VStack(alignment: .leading, spacing: 10) { - Text(String(localized: "goal.chart.title")) - .font(.headline) - .foregroundStyle(Theme.primaryText) - - Chart(viewModel.goalProgress) { progress in - BarMark( - x: .value(String(localized: "goal.progress"), progress.ratio * 100), - y: .value("", progress.displayName) - ) - .foregroundStyle(progress.isCompleted ? Theme.green : Theme.yellow) - .cornerRadius(4) - } - .chartXScale(domain: 0.0...100.0) - .chartXAxis { - AxisMarks(values: [0.0, 25.0, 50.0, 75.0, 100.0]) { value in - AxisGridLine() - AxisValueLabel { - if let v = value.as(Double.self) { - Text("\(Int(v))%") - .font(.caption2) - .foregroundStyle(Theme.primaryText.opacity(0.5)) - } - } - } - } - .chartYAxis { - AxisMarks { _ in - AxisValueLabel() - } - } - .frame(height: CGFloat(viewModel.goalProgress.count) * 48 + 20) - } - .padding() - .background(Theme.surface, in: RoundedRectangle(cornerRadius: 12)) - } - - // MARK: - Goal Cards - - private var goalCards: some View { - VStack(spacing: 12) { - ForEach(viewModel.goalProgress) { progress in - GoalCard(progress: progress) { - viewModel.deleteGoal(progress.goal) - } - } - } - } -} - -// MARK: - Goal Card - -private struct GoalCard: View { - let progress: GoalProgress - let onDelete: () -> Void - - var body: some View { - VStack(alignment: .leading, spacing: 10) { - HStack(alignment: .top) { - VStack(alignment: .leading, spacing: 2) { - Text(progress.displayName) - .font(.body) - .fontWeight(.semibold) - .foregroundStyle(Theme.primaryText) - Text(subtitleText) - .font(.caption) - .foregroundStyle(Theme.primaryText.opacity(0.5)) - } - Spacer() - Button(role: .destructive, action: onDelete) { - Image(systemName: "trash") - .font(.caption) - .foregroundStyle(.red.opacity(0.7)) - } - .buttonStyle(.plain) - } - - ProgressView(value: progress.ratio) - .tint(progress.isCompleted ? Theme.green : Theme.yellow) - - HStack { - Text(currentText) - .font(.caption) - .foregroundStyle(Theme.primaryText.opacity(0.6)) - Spacer() - Text(progress.percentText) - .font(.caption) - .fontWeight(.bold) - .foregroundStyle(progress.isCompleted ? Theme.green : Theme.yellow) - } - } - .padding() - .background(Theme.surface, in: RoundedRectangle(cornerRadius: 12)) - } - - private var subtitleText: String { - let cond = progress.goal.targetType == "count" - ? String(localized: "goal.condition.count") - : String(localized: "goal.condition.time") - let freq: String - switch progress.goal.frequency { - case .daily: freq = String(localized: "goal.frequency.daily") - case .weekly: freq = String(localized: "goal.frequency.weekly") - case .monthly: freq = String(localized: "goal.frequency.monthly") - } - return "\(cond) · \(freq)" - } - - private var currentText: String { - if progress.goal.targetType == "count" { - return "\(Int(progress.current)) / \(Int(progress.target)) \(String(localized: "goal.condition.count"))" - } else { - let currentMins = Int(progress.current) / 60 - let targetMins = Int(progress.target) / 60 - return "\(currentMins)m / \(targetMins)m" - } - } -} - -// MARK: - Add Goal Sheet - -private struct AddGoalSheet: View { - @Environment(\.dismiss) private var dismiss - @Query(sort: \TaskItem.name) private var tasks: [TaskItem] - @Query(sort: \Category.name) private var categories: [Category] - - @State private var targetType: GoalTargetType = .task - @State private var selectedTask: TaskItem? - @State private var selectedCategory: Category? - @State private var conditionType: String = "count" - @State private var threshold: Double = 5 - @State private var frequency: GoalFrequency = .daily - - let onAdd: (String, Double, GoalFrequency, TaskItem?, Category?) -> Void - - var body: some View { - NavigationStack { - Form { - Section(header: Text(String(localized: "goal.target"))) { - Picker(String(localized: "goal.target"), selection: $targetType) { - Text(String(localized: "goal.target.task")).tag(GoalTargetType.task) - Text(String(localized: "goal.target.category")).tag(GoalTargetType.category) - } - .pickerStyle(.segmented) - .onChange(of: targetType) { _, _ in - selectedTask = nil - selectedCategory = nil - } - - if targetType == .task { - Picker(String(localized: "goal.target.task"), selection: $selectedTask) { - Text(String(localized: "goal.no.target")).tag(Optional.none) - ForEach(tasks) { task in - Text("\(task.icon) \(task.name)").tag(Optional(task)) - } - } - } else { - Picker(String(localized: "goal.target.category"), selection: $selectedCategory) { - Text(String(localized: "goal.no.target")).tag(Optional.none) - ForEach(categories) { cat in - Text(cat.name).tag(Optional(cat)) - } - } - } - } - - Section(header: Text(String(localized: "goal.condition"))) { - Picker(String(localized: "goal.condition"), selection: $conditionType) { - Text(String(localized: "goal.condition.count")).tag("count") - Text(String(localized: "goal.condition.time")).tag("duration") - } - .pickerStyle(.segmented) - .onChange(of: conditionType) { _, _ in threshold = 5 } - } - - Section(header: Text(String(localized: "goal.threshold"))) { - HStack { - Slider( - value: $threshold, - in: conditionType == "count" ? 1.0...100.0 : 5.0...240.0, - step: conditionType == "count" ? 1.0 : 5.0 - ) - .tint(Theme.green) - Text(thresholdText) - .font(.body) - .monospacedDigit() - .foregroundStyle(Theme.green) - .frame(width: 52, alignment: .trailing) - } - } - - Section(header: Text(String(localized: "goal.frequency"))) { - Picker(String(localized: "goal.frequency"), selection: $frequency) { - Text(String(localized: "goal.frequency.daily")).tag(GoalFrequency.daily) - Text(String(localized: "goal.frequency.weekly")).tag(GoalFrequency.weekly) - Text(String(localized: "goal.frequency.monthly")).tag(GoalFrequency.monthly) - } - .pickerStyle(.segmented) - } - } - .navigationTitle(String(localized: "goal.add")) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button(String(localized: "button.cancel")) { dismiss() } - } - ToolbarItem(placement: .confirmationAction) { - Button(String(localized: "button.add")) { - let storedConditions = conditionType == "duration" ? threshold * 60.0 : threshold - onAdd( - conditionType, - storedConditions, - frequency, - targetType == .task ? selectedTask : nil, - targetType == .category ? selectedCategory : nil - ) - dismiss() - } - .disabled(!canAdd) - } - } - } - } - - private var thresholdText: String { - if conditionType == "count" { - return "\(Int(threshold))" - } else { - let h = Int(threshold) / 60 - let m = Int(threshold) % 60 - return h > 0 ? "\(h)h\(m)m" : "\(Int(threshold))m" - } - } - - private var canAdd: Bool { - targetType == .task ? selectedTask != nil : selectedCategory != nil - } -} - -#Preview { - NavigationStack { - GoalView() - } -} diff --git a/myApp/LemonLimeTracker/IOS/Views/LaunchScreenView.swift b/myApp/LemonLimeTracker/IOS/Views/LaunchScreenView.swift deleted file mode 100644 index 7e198e0..0000000 --- a/myApp/LemonLimeTracker/IOS/Views/LaunchScreenView.swift +++ /dev/null @@ -1,22 +0,0 @@ -import SwiftUI - -struct LaunchScreenView: View { - var body: some View { - ZStack { - Theme.background - .ignoresSafeArea() - VStack(spacing: 12) { - Text("🍋🍈") - .font(.system(size: 60)) - Text("LemonLimeTracker") - .font(.largeTitle) - .fontWeight(.bold) - .foregroundStyle(Theme.green) - } - } - } -} - -#Preview { - LaunchScreenView() -} diff --git a/myApp/LemonLimeTracker/IOS/Views/MainTabView.swift b/myApp/LemonLimeTracker/IOS/Views/MainTabView.swift deleted file mode 100644 index 323770f..0000000 --- a/myApp/LemonLimeTracker/IOS/Views/MainTabView.swift +++ /dev/null @@ -1,47 +0,0 @@ -import SwiftUI - -struct MainTabView: View { - var body: some View { - TabView { - NavigationStack { - DashboardView() - } - .tabItem { - Label(String(localized: "tab.dashboard"), systemImage: "chart.bar.fill") - } - - NavigationStack { - CategoryView() - } - .tabItem { - Label(String(localized: "tab.category"), systemImage: "folder.fill") - } - - NavigationStack { - TaskTrackerView() - } - .tabItem { - Label(String(localized: "tab.tasks"), systemImage: "checkmark.circle.fill") - } - - NavigationStack { - GoalView() - } - .tabItem { - Label(String(localized: "tab.goals"), systemImage: "flag.fill") - } - - NavigationStack { - SettingsView() - } - .tabItem { - Label(String(localized: "tab.settings"), systemImage: "gearshape.fill") - } - } - .tint(Theme.green) - } -} - -#Preview { - MainTabView() -} diff --git a/myApp/LemonLimeTracker/IOS/Views/RootView.swift b/myApp/LemonLimeTracker/IOS/Views/RootView.swift deleted file mode 100644 index c535012..0000000 --- a/myApp/LemonLimeTracker/IOS/Views/RootView.swift +++ /dev/null @@ -1,24 +0,0 @@ -import SwiftUI - -struct RootView: View { - @State private var isLaunching = true - - var body: some View { - if isLaunching { - LaunchScreenView() - .onAppear { - DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { - withAnimation(.easeInOut(duration: 0.4)) { - isLaunching = false - } - } - } - } else { - MainTabView() - } - } -} - -#Preview { - RootView() -} diff --git a/myApp/LemonLimeTracker/IOS/Views/SettingsView.swift b/myApp/LemonLimeTracker/IOS/Views/SettingsView.swift deleted file mode 100644 index 4c81cc0..0000000 --- a/myApp/LemonLimeTracker/IOS/Views/SettingsView.swift +++ /dev/null @@ -1,107 +0,0 @@ -import SwiftUI - -struct SettingsView: View { - @State private var viewModel = SettingsViewModel() - - var body: some View { - Form { - Section(header: Text(String(localized: "settings.section.general"))) { - Picker(String(localized: "settings.language"), selection: $viewModel.language) { - ForEach(AppLanguage.allCases) { lang in - Text(lang.displayName).tag(lang) - } - } - - Picker(String(localized: "settings.firstWeekday"), selection: $viewModel.firstWeekday) { - ForEach(FirstWeekday.allCases) { day in - Text(day.displayName).tag(day) - } - } - - DatePicker( - String(localized: "settings.dayStart"), - selection: $viewModel.dayStartDate, - displayedComponents: .hourAndMinute - ) - .tint(Theme.green) - } - .listRowBackground(Theme.surface) - - Section { - ForEach(PremiumFeature.allCases) { feature in - PremiumFeatureRow(feature: feature) - } - } header: { - Text(String(localized: "settings.section.premium")) - } footer: { - Text(String(localized: "settings.premium.footnote")) - } - .listRowBackground(Theme.surface) - } - .scrollContentBackground(.hidden) - .background(Theme.background.ignoresSafeArea()) - .navigationTitle(String(localized: "tab.settings")) - } -} - -// MARK: - Premium Feature - -private enum PremiumFeature: String, CaseIterable, Identifiable { - case cloudSync - case appleWatch - case ipadMacSync - case widgets - case siriShortcuts - - var id: String { rawValue } - - var icon: String { - switch self { - case .cloudSync: return "icloud.fill" - case .appleWatch: return "applewatch" - case .ipadMacSync: return "ipad.and.macbook" - case .widgets: return "square.grid.2x2.fill" - case .siriShortcuts: return "mic.fill" - } - } - - var titleKey: String { - switch self { - case .cloudSync: return "settings.premium.cloudSync" - case .appleWatch: return "settings.premium.appleWatch" - case .ipadMacSync: return "settings.premium.ipadMacSync" - case .widgets: return "settings.premium.widgets" - case .siriShortcuts: return "settings.premium.siriShortcuts" - } - } -} - -private struct PremiumFeatureRow: View { - let feature: PremiumFeature - - var body: some View { - HStack(spacing: 12) { - Image(systemName: feature.icon) - .font(.body) - .foregroundStyle(Theme.primaryText.opacity(0.35)) - .frame(width: 24) - - Text(String(localized: String.LocalizationValue(feature.titleKey))) - .font(.body) - .foregroundStyle(Theme.primaryText.opacity(0.35)) - - Spacer() - - Image(systemName: "lock.fill") - .font(.caption) - .foregroundStyle(Theme.yellow.opacity(0.8)) - } - .opacity(0.7) - } -} - -#Preview { - NavigationStack { - SettingsView() - } -} diff --git a/myApp/LemonLimeTracker/IOS/Views/TaskTrackerView.swift b/myApp/LemonLimeTracker/IOS/Views/TaskTrackerView.swift deleted file mode 100644 index 2869bd5..0000000 --- a/myApp/LemonLimeTracker/IOS/Views/TaskTrackerView.swift +++ /dev/null @@ -1,212 +0,0 @@ -import SwiftUI -import SwiftData - -struct TaskTrackerView: View { - @Environment(\.modelContext) private var modelContext - @State private var viewModel = TaskViewModel() - @State private var showAddSheet = false - - var body: some View { - ZStack { - Theme.background.ignoresSafeArea() - Group { - if viewModel.tasks.isEmpty { - emptyState - } else { - taskList - } - } - } - .navigationTitle(String(localized: "tab.tasks")) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button { - showAddSheet = true - } label: { - Image(systemName: "plus") - .foregroundStyle(Theme.green) - } - } - } - .sheet(isPresented: $showAddSheet) { - AddTaskSheet { name, icon, type, category in - viewModel.addTask(name: name, icon: icon, type: type, category: category) - } - } - .onAppear { - viewModel.setup(context: modelContext) - } - } - - private var emptyState: some View { - VStack(spacing: 16) { - Image(systemName: "checkmark.circle.badge.plus") - .font(.system(size: 56)) - .foregroundStyle(Theme.green.opacity(0.6)) - Text(String(localized: "task.empty")) - .foregroundStyle(Theme.primaryText.opacity(0.6)) - } - } - - private var taskList: some View { - List { - ForEach(viewModel.tasks) { task in - TaskRow(task: task, viewModel: viewModel) - .listRowBackground(Theme.surface) - .swipeActions(edge: .trailing, allowsFullSwipe: false) { - Button(role: .destructive) { - viewModel.deleteTask(task) - } label: { - Label(String(localized: "task.delete"), systemImage: "trash") - } - } - .swipeActions(edge: .leading, allowsFullSwipe: true) { - if task.type == .count { - Button { - viewModel.addCount(to: task) - } label: { - Label(String(localized: "task.add.count"), systemImage: "plus") - } - .tint(Theme.green) - } - } - } - } - .listStyle(.plain) - .scrollContentBackground(.hidden) - } -} - -// MARK: - Task Row - -private struct TaskRow: View { - let task: TaskItem - let viewModel: TaskViewModel - - private var isRunning: Bool { viewModel.isTimerRunning(for: task.id) } - - var body: some View { - HStack(spacing: 12) { - Text(task.icon) - .font(.title2) - .frame(width: 36) - - VStack(alignment: .leading, spacing: 2) { - Text(task.name) - .font(.body) - .fontWeight(.medium) - .foregroundStyle(Theme.primaryText) - if let category = task.category { - Text(category.name) - .font(.caption2) - .foregroundStyle(Theme.green) - } - } - - Spacer() - - if task.type == .time { - timeControls - } else { - countBadge - } - } - .padding(.vertical, 4) - } - - private var timeControls: some View { - HStack(spacing: 8) { - if isRunning { - Text(viewModel.formattedElapsed(for: task.id)) - .font(.system(.subheadline, design: .monospaced)) - .foregroundStyle(Theme.green) - .contentTransition(.numericText()) - .animation(.linear(duration: 0.3), value: viewModel.elapsedSeconds[task.id]) - } - Button { - viewModel.toggleTimer(for: task) - } label: { - Image(systemName: isRunning ? "stop.fill" : "play.fill") - .font(.title3) - .foregroundStyle(isRunning ? Theme.yellow : Theme.green) - .frame(width: 36, height: 36) - .background( - Circle() - .fill(isRunning ? Theme.yellow.opacity(0.15) : Theme.green.opacity(0.15)) - ) - } - .buttonStyle(.plain) - } - } - - private var countBadge: some View { - let count = viewModel.todayCount(for: task) - return Text("×\(count)") - .font(.headline) - .monospacedDigit() - .foregroundStyle(count > 0 ? Theme.green : Theme.primaryText.opacity(0.3)) - } -} - -// MARK: - Add Task Sheet - -private struct AddTaskSheet: View { - @Environment(\.dismiss) private var dismiss - @Query private var categories: [Category] - - @State private var name = "" - @State private var icon = "⭐" - @State private var type: TaskType = .count - @State private var selectedCategory: Category? - - let onAdd: (String, String, TaskType, Category?) -> Void - - var body: some View { - NavigationStack { - Form { - Section { - TextField(String(localized: "task.name"), text: $name) - TextField(String(localized: "task.icon"), text: $icon) - } - Section { - Picker(String(localized: "task.type"), selection: $type) { - Text(String(localized: "task.type.count")).tag(TaskType.count) - Text(String(localized: "task.type.time")).tag(TaskType.time) - } - .pickerStyle(.segmented) - } - if !categories.isEmpty { - Section { - Picker(String(localized: "task.category"), selection: $selectedCategory) { - Text(String(localized: "task.category.none")).tag(Optional.none) - ForEach(categories) { cat in - Text(cat.name).tag(Optional(cat)) - } - } - } - } - } - .navigationTitle(String(localized: "task.add")) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button(String(localized: "button.cancel")) { dismiss() } - } - ToolbarItem(placement: .confirmationAction) { - Button(String(localized: "button.add")) { - let trimmed = name.trimmingCharacters(in: .whitespaces) - guard !trimmed.isEmpty else { return } - let resolvedIcon = icon.trimmingCharacters(in: .whitespaces).isEmpty ? "⭐" : icon - onAdd(trimmed, resolvedIcon, type, selectedCategory) - dismiss() - } - .disabled(name.trimmingCharacters(in: .whitespaces).isEmpty) - } - } - } - } -} - -#Preview { - TaskTrackerView() -} diff --git a/myApp/LemonLimeTracker/LemonLimeTracker-Info.plist b/myApp/LemonLimeTracker/LemonLimeTracker-Info.plist deleted file mode 100644 index c41d6c0..0000000 --- a/myApp/LemonLimeTracker/LemonLimeTracker-Info.plist +++ /dev/null @@ -1,13 +0,0 @@ - - - - - CFBundleLocalizations - - ko - en - - NSSupportsLiveActivities - - - diff --git a/myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/project.pbxproj b/myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/project.pbxproj deleted file mode 100644 index 01f3c85..0000000 --- a/myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/project.pbxproj +++ /dev/null @@ -1,543 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 77; - objects = { - -/* Begin PBXBuildFile section */ - 7280EB402FE41F11006B83D9 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7280EB3F2FE41F10006B83D9 /* WidgetKit.framework */; }; - 7280EB422FE41F11006B83D9 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7280EB412FE41F11006B83D9 /* SwiftUI.framework */; }; - 7280EB4F2FE41F12006B83D9 /* LemonLimeWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 7280EB3D2FE41F10006B83D9 /* LemonLimeWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 7280EB4D2FE41F12006B83D9 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 7280EAF02FE40D38006B83D9 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 7280EB3C2FE41F10006B83D9; - remoteInfo = LemonLimeWidgetExtension; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 7280EB542FE41F12006B83D9 /* Embed Foundation Extensions */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 13; - files = ( - 7280EB4F2FE41F12006B83D9 /* LemonLimeWidgetExtension.appex in Embed Foundation Extensions */, - ); - name = "Embed Foundation Extensions"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 7280EAF82FE40D38006B83D9 /* LemonLimeTracker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LemonLimeTracker.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 7280EB1E2FE41A14006B83D9 /* LemonLimeTracker-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "LemonLimeTracker-Info.plist"; sourceTree = ""; }; - 7280EB3D2FE41F10006B83D9 /* LemonLimeWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = LemonLimeWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; - 7280EB3F2FE41F10006B83D9 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; - 7280EB412FE41F11006B83D9 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; -/* End PBXFileReference section */ - -/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 7280EB502FE41F12006B83D9 /* Exceptions for "LemonLimeWidget" folder in "LemonLimeWidgetExtension" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - Info.plist, - ); - target = 7280EB3C2FE41F10006B83D9 /* LemonLimeWidgetExtension */; - }; - 7280EB5A2FE42219006B83D9 /* Exceptions for "IOS" folder in "LemonLimeWidgetExtension" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - Core/TimerActivityAttributes.swift, - ); - target = 7280EB3C2FE41F10006B83D9 /* LemonLimeWidgetExtension */; - }; -/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ - -/* Begin PBXFileSystemSynchronizedRootGroup section */ - 7280EAFA2FE40D38006B83D9 /* IOS */ = { - isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - 7280EB5A2FE42219006B83D9 /* Exceptions for "IOS" folder in "LemonLimeWidgetExtension" target */, - ); - path = IOS; - sourceTree = ""; - }; - 7280EB432FE41F11006B83D9 /* LemonLimeWidget */ = { - isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - 7280EB502FE41F12006B83D9 /* Exceptions for "LemonLimeWidget" folder in "LemonLimeWidgetExtension" target */, - ); - path = LemonLimeWidget; - sourceTree = ""; - }; -/* End PBXFileSystemSynchronizedRootGroup section */ - -/* Begin PBXFrameworksBuildPhase section */ - 7280EAF52FE40D38006B83D9 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 7280EB3A2FE41F10006B83D9 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 7280EB422FE41F11006B83D9 /* SwiftUI.framework in Frameworks */, - 7280EB402FE41F11006B83D9 /* WidgetKit.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 7280EAEF2FE40D38006B83D9 = { - isa = PBXGroup; - children = ( - 7280EB1E2FE41A14006B83D9 /* LemonLimeTracker-Info.plist */, - 7280EAFA2FE40D38006B83D9 /* IOS */, - 7280EB432FE41F11006B83D9 /* LemonLimeWidget */, - 7280EB3E2FE41F10006B83D9 /* Frameworks */, - 7280EAF92FE40D38006B83D9 /* Products */, - ); - sourceTree = ""; - }; - 7280EAF92FE40D38006B83D9 /* Products */ = { - isa = PBXGroup; - children = ( - 7280EAF82FE40D38006B83D9 /* LemonLimeTracker.app */, - 7280EB3D2FE41F10006B83D9 /* LemonLimeWidgetExtension.appex */, - ); - name = Products; - sourceTree = ""; - }; - 7280EB3E2FE41F10006B83D9 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 7280EB3F2FE41F10006B83D9 /* WidgetKit.framework */, - 7280EB412FE41F11006B83D9 /* SwiftUI.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 7280EAF72FE40D38006B83D9 /* LemonLimeTracker */ = { - isa = PBXNativeTarget; - buildConfigurationList = 7280EB032FE40D39006B83D9 /* Build configuration list for PBXNativeTarget "LemonLimeTracker" */; - buildPhases = ( - 7280EAF42FE40D38006B83D9 /* Sources */, - 7280EAF52FE40D38006B83D9 /* Frameworks */, - 7280EAF62FE40D38006B83D9 /* Resources */, - 7280EB542FE41F12006B83D9 /* Embed Foundation Extensions */, - ); - buildRules = ( - ); - dependencies = ( - 7280EB4E2FE41F12006B83D9 /* PBXTargetDependency */, - ); - fileSystemSynchronizedGroups = ( - 7280EAFA2FE40D38006B83D9 /* IOS */, - ); - name = LemonLimeTracker; - packageProductDependencies = ( - ); - productName = LemonLimeTracker; - productReference = 7280EAF82FE40D38006B83D9 /* LemonLimeTracker.app */; - productType = "com.apple.product-type.application"; - }; - 7280EB3C2FE41F10006B83D9 /* LemonLimeWidgetExtension */ = { - isa = PBXNativeTarget; - buildConfigurationList = 7280EB512FE41F12006B83D9 /* Build configuration list for PBXNativeTarget "LemonLimeWidgetExtension" */; - buildPhases = ( - 7280EB392FE41F10006B83D9 /* Sources */, - 7280EB3A2FE41F10006B83D9 /* Frameworks */, - 7280EB3B2FE41F10006B83D9 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - fileSystemSynchronizedGroups = ( - 7280EB432FE41F11006B83D9 /* LemonLimeWidget */, - ); - name = LemonLimeWidgetExtension; - packageProductDependencies = ( - ); - productName = LemonLimeWidgetExtension; - productReference = 7280EB3D2FE41F10006B83D9 /* LemonLimeWidgetExtension.appex */; - productType = "com.apple.product-type.app-extension"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 7280EAF02FE40D38006B83D9 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 2650; - LastUpgradeCheck = 2650; - TargetAttributes = { - 7280EAF72FE40D38006B83D9 = { - CreatedOnToolsVersion = 26.5; - }; - 7280EB3C2FE41F10006B83D9 = { - CreatedOnToolsVersion = 26.5; - }; - }; - }; - buildConfigurationList = 7280EAF32FE40D38006B83D9 /* Build configuration list for PBXProject "LemonLimeTracker" */; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ko, - ); - mainGroup = 7280EAEF2FE40D38006B83D9; - minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 77; - productRefGroup = 7280EAF92FE40D38006B83D9 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 7280EAF72FE40D38006B83D9 /* LemonLimeTracker */, - 7280EB3C2FE41F10006B83D9 /* LemonLimeWidgetExtension */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 7280EAF62FE40D38006B83D9 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 7280EB3B2FE41F10006B83D9 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 7280EAF42FE40D38006B83D9 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 7280EB392FE41F10006B83D9 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 7280EB4E2FE41F12006B83D9 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 7280EB3C2FE41F10006B83D9 /* LemonLimeWidgetExtension */; - targetProxy = 7280EB4D2FE41F12006B83D9 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 7280EB012FE40D39006B83D9 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.5; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 7280EB022FE40D39006B83D9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.5; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 7280EB042FE40D39006B83D9 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "LemonLimeTracker-Info.plist"; - INFOPLIST_KEY_CFBundleDisplayName = ""; - INFOPLIST_KEY_LSApplicationCategoryType = ""; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.yechan.LemonLimeTracker; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 7280EB052FE40D39006B83D9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "LemonLimeTracker-Info.plist"; - INFOPLIST_KEY_CFBundleDisplayName = ""; - INFOPLIST_KEY_LSApplicationCategoryType = ""; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.yechan.LemonLimeTracker; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 7280EB522FE41F12006B83D9 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = LemonLimeWidget/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = LemonLimeWidget; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.yechan.LemonLimeTracker.LemonLimeWidget; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 7280EB532FE41F12006B83D9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = LemonLimeWidget/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = LemonLimeWidget; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@executable_path/../../Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.yechan.LemonLimeTracker.LemonLimeWidget; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 7280EAF32FE40D38006B83D9 /* Build configuration list for PBXProject "LemonLimeTracker" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 7280EB012FE40D39006B83D9 /* Debug */, - 7280EB022FE40D39006B83D9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 7280EB032FE40D39006B83D9 /* Build configuration list for PBXNativeTarget "LemonLimeTracker" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 7280EB042FE40D39006B83D9 /* Debug */, - 7280EB052FE40D39006B83D9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 7280EB512FE41F12006B83D9 /* Build configuration list for PBXNativeTarget "LemonLimeWidgetExtension" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 7280EB522FE41F12006B83D9 /* Debug */, - 7280EB532FE41F12006B83D9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 7280EAF02FE40D38006B83D9 /* Project object */; -} diff --git a/myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/project.xcworkspace/xcuserdata/ceuak.xcuserdatad/UserInterfaceState.xcuserstate b/myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/project.xcworkspace/xcuserdata/ceuak.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 2853e57128c5a6cdd3c6f31020b51fa7096c942f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35877 zcmd?Sd3;RQ`#65jy?53-VX{ji`$jU^_dPR7Ci^;BCtD*Kq#_}cAXVI}l%i-+YENQU zrBt=5iYnToO7}%q)mn=xMfLlfxigbQ(0=;%`|HOmlgz!(S)ctm&pBt*zk6TOCZq1VwH zXg7Kj?Llv$z33fu7#%_Hp%dsM^cngJeT#lXKcREzJi3goq2JM;=r7WO96$~v<)kHP zMOu?Kqzmavx{>arHyK4HlBr|{nL`$kg=7g?N!F0HP|s3xsd?0V>N)CpY5}#7 z>ZI0E>!?lCW@;<7je3>ZNxebsruI^AQ}0j*sUy^T)G_Kf^)>Ykb(;E?`i?qBlQc!s zw15`UB3evKXerImGFndC(}QRS+MV{GJ!yYhK?l%LbTl19$I^+kiq4|5>00_Rx{e-3 z*V7HOjvh{rpdY74)6H}Xt*1NaC+JD^6q=={(aY)8^m=+Ly^Vf_euLgkze&GKzegXV zKcPRRuhM_eH|X2+9RVRg0tbP!z)RpQ2oMAcf&@{5Xn{(g7Gw#E1R6nwKr3h!j1x=} zOcp#Xm?4-ccwX>=V5y)>&@EUacuBBD@UGxJ!3n`ff-eML3ceEjDELWmMsQhhO>jf- zw~!DH5DpZ|g_c4qp|#LPXe)FOx(eNd?!uu$KcT-cNEj>(5k?AQg^9v)VTG_-*dS~Y zjutixCkUSuvcj3dXN1oR7YSbwzAW4>d_%ZjctH4$@B`sd;itkcg{Or-3(pGAiztyu zG(a>^Bo_@5IfxuZ9wJXsfG9*1AxaP>ii$)fqH(Q(nIqR&L1i@paUF!(O1DT~mcB0CBi$!G zB7IN#z4VOqg7kOkb?F}r!O)DDku#Qz1LMeeF^P0-K>HOx!Q7UpGUJF}B{o!Q6iXAUrjnD?1u%oog;%va3U z%s0$;%#X~^%&#&eBW09~mI-7+nMfv z3?K#)a>AOhE6dXaj?#7LweZ`h4NAT0o9guXF2a(qVo8?jB5Vj-mgbOvRn*G8Lc&xb zX{oB{fT$={NI-aMbXtH?t&Rv#sv^=-!c(F{LL$_)a+}Q5w6d1gQTj1;^}5uS`u5Sf z<~CEyj)W(n*g!ZDg9&HCg>WU@2zSDR6|h29#EMx7D`gp0wt?^>ya^w|7k)eAPu7AR zzz$>`;J3)|>0c|i#qBgUH?)k;)T?1|n)+6quDQniRNY!PT30Ji?A@K>x?w3T&26nM zO-;Ji+K9B6n25-f;DG3u@Zf-OWm;5#N*xvv5EB{^9Fh{EiVjYN@dQ8x8Fl*H#u2Tc z4jW4An%Z@Gb!*G$JWY{K-`3JvH>^o#P?f25ZFRMBA9D|&HC0_}S)IPLQQrtaWsPkk zHI2<9nso4{R_+Rb%KA1?%I4|n+dsKOPgN}~O~dM1Gfh?1$_MqlGc;p0w#JGiohXljl@GT-TnEz(NMKqxz6dQ>cB9@3F;)w(zkw_wv2_-9MEmQxY9gJ;ATr@wHekV#ZDZTnN$g|}5v&7;NEWanhi+*`wv5lK8`n6Z z4rYgcWd5pb)xjKD0#r#`V^d>WqfTGe*w8kT2g_{Mj{&o!rL_mlT(TEfT0^4=CM2q= zv{5%cRX41C#0bEtxdZ0vYvtCty3sAI9qFxgV@8?)C8k*@Cmtgd>xl|NOH>k7L^V-E z)UrO)*im@QmKt3X^jg}uz`3=24XTW&Pp-xWv$#7p7adfJO*`RQ=6_e zzd5(1p|NpzqpqP=?)`Acv}Vk34Vcl5d{qRK&&!<@VC=dvdR;@CZnPe^YZ}5d;%UOJ zlbFs9?IdQfepsC0F7nK*>;pQRm_sO5uV(#E67z`p#B;>+T3#$RgFGqc|I`77tn)Ov z?QOU(sg1zJEzNbU9o+!FfLK_Sr|EfCo~Ow-7~6b(3y2rGFboK=i4%hrZUNDg6mAnk zqazh=(UGB(CYgHtBC*WeTf|=CZ8naLXA{{Z zJX{-|$_zqU)d8fY18)cTHm6SCW~M~^xK?gg(mdQilJ^1+;_J{glw|gOW$Mo%;s{3A zTWowM@gAFiNes*AKJ{x_K;!|P!%R*aA}LxR6#aK+O)M#HZE0y^(MCpQ@8s^~ z>#yMT{U;U!2Ffk1tZi)VjH{##uG`zi4eB4_**O#}0RgW>@9j%1 zFEZ0wTU%P8l7K+yd~IrJF%OqEVN9z|kDX!z9t|U26BHcc_K#4ta)+L3it)_#)X@K( zVO9;bgEMVOID(iQ3NCXL=+7K*lQp1K>cK5;2WNOG@iOrtIJu{YPr%Xrf;dh5N}MAu zfU|oCTwDuqY#opbaz*ao*kYG95ha65n+h)MC^QE>hqj~RgyA2F_}|b@pMU6_iAs+{ zLv|qD%;MxE@v)g~KVg$u#Yr#_KZj0ySu1x09fk#50S+$|>F~S(zU#2CV^yp&U%vfb zA_n6_4kl!qvK~|x$YC&vbQO(2K!%yB)*DrVfuw5m_1F+AXwici(9#^HJbYC)`>)Ao zRCr4?1W=CM!kkjOL< z1Nraf&!>1P{0(s$EHxm7CfCeT2}IY^2B_%J$eypgn#9bgk1{q_TdJuI3+aI~%$x?yspb(q>{e^S=MO+Cigm<~ z#81Q-;%A_yU)WSOja9Shpr13=0U4bKLh>ap5ngO2o5gCua2pPK8l)nYPEdLMsFaq` zCYCVrzdgJ%2;1V;x@LVNAl=XlkOKCps&unR!?rZM<0Yq|IzbN6O!cG`lo-m7z#Sdg*woN!WOon~rn*fQ4nHA4F~-I{0U)Kl z6-u<_)U`L)kJRu2r${#(#@<|y)z3-ni6I6J=t44N!4|Sbtfp2TT;DP}utQfrvaUHW z2dviSoW{{QKtlZ}U2D$+Foj3x+T6Uo-9o_9{67>i5I?d))?lF+jTeosts9=$5DQE4 zH1LJ*mmM0^iR{@Dw&7Fch@8-1=&Q3q2DyP8@<5)*3pm~6-Q~2@8-zkl4}licVVmC8 zY-tvOf&%%gVcdGXZ-O@`s!1?0Lr$Y#IF2v!yI}O(A9jq0g)brcKb* zw_`by);zAUwWS$*oqQ|&hq2*wsnTtK1Po;FJi zyPOa&){h9SZ38z#PVh4siK4&=2W)-A;J&cg7n2){EuN-;cojPGJnd;@lY-1I!9xeae=b^AXM zMj}6+43ufs$S8wYXg%d`{vZE32j$kvhrn?AOnuMT`>Kd8l!u4@ui^PN3Q*y_Iv*9_ zzEng-*M|-d4G#_mEA3v>IV3bH+H5hZbVI?W_+J#=roVo_a^@wGh1|+!kfXD!yQl9-u|z5>9s_Js z0YMGjgbZ*II~i!@UNHc)hXxFk)#$LmGn+A3dv4nJTE`zykEN`OVF1Wl#y{P0Y7;!Q zweMo=G@1DtIf8c%#&@qhu@c@n$+{W4!Nn;ptvYz;VtO|*0^YenZFca~!(7`Ac9KkZ)Q@i*-X<4y1%-A6hulZU zhlTgj@e6M9nj2eCUa;^GPh`X5LLK8XYySl5@e*+Zl*!+S2208c*&#QOU%n_9g`y}F2MYp)upCeZ3j&R(3AOXhneC_u zv*BR)15Gwj%qTP#R27sMjhayl8pAfRquFM*g&ngNwGv*a4SvS4V?ool!jB%SDi2^B zaGT89vzPa3P(}uy1c!Wj0~x$6Rfpl7wE^W=$Mu-q5GlB)1bD+B`gmKnr(T?qcU_&V`}AbW>AfR z$c>@HlFa*5-Uk8@Xr^J)RINZ82*oC}60Jh3Q77s`-DnM3i`Jp_>=c$|Id&@h6g!Qb z&OXh~U}tVZ8$r5nMlYc)=w-AOZ9}i1?d&rk>u0lb*k{?L?C0zs><#uNo@zIf9nupQ z(|~i?W>i905&Nlhpb>P~(yME2)VJVxi>|f=P-LV+z9?_Wxf#E-H?=j6f!HRltDv=U zG&rs~bsa73ZQ9<@o@4k|rp67+j17HN0aya2*Sb1GeI|R-8mKo_H@pto;>|Tfe9+DK zr~t2C>amU4w?2JMZry~&(Tz`-?Kn%`Sktu{YR7`+6nY!&1J%AC)&UQI49e4(=Oahg zJi-ui?DfoG`N|PHi&sMj(Yuws-gcow#KPVWYUNh^T-4_1hPM^B6g7?**(N9YT1)Sv z571GNHpkJ2>_T=PyBLg^x$J^kd7zoH`e;(#UI|QX)I$_rH+oo8M{f(}BvN#vQ|M## z2|J&Cj(z?f>xDN~OvXVkTf+3HmkD9k>!;8en4+K2S@a8Xt!ywx>r0AqD%!yC)VI}* z9>XqTUtoz^`S=L|6L3s3prNOGu|YuqQ$MP9I1aWaHQi(ECYV*Wj{(dOl80FO$b{&a zi0F`*kjSX$sECM&kg(8*kX|5QCnrS3Lx=7 zdk|=tl`*X?kMljfj{d0RgAE2_w;SC+H-UC~J%ZR{0k-HHurZtl%MnIr;1)KzZ?ntT z29waZOA5fdLw}P5>Lf{$B58Ixy8=YTN_G{ydL7|Kibyg1mx6a^!FIA;@V^`W0-@jx z9#4By1NM6gV0ENOHx`0=Z6E-!VXUvjo=P?t8lYIJd$>6y3`$z^-}%)G1E1nxsh*tP7sZqku-A_ueU*-zNdaObR`GQCot z0lW>O-sm;in#35q>`_t>y(?&JZq_yQd8#+40n!60I!RA<16%h6=|lRGL&%||A2E>h zClzD>3MPZdU@`=n4uiiV$Vl)qOeEfi@A3>QMef5vLEtqU?2ZTO4R(BAu>+$9I&SL~ z@Pz<+vtgyf==|LaZ>=*dRrKjQ%y6GFxb@zw2X&fy*`ox_8XwqvJNqiT5vKA}_ zqmRrXqsbUv=8y^OR(6w7=Df_$R}zR2GMQA8Dt0sb61(M*ix6H|oFYpB31!HYtUzb+ zI^(c9y$+;jV5_bP9K~@uaOYlO7a914tRkzy^8}I8MOI->b{f+@tf{d+u(?h@I>00c z19&sDRvv4XoLGJHq-WyazyjTfI(@r7&k&x}X*xi2j5aCe2Gd#qSw}VzcALmyWIfqH z>d4{b2y!IZNIp)EVt2ByvAfvU**DnT?3?T!_APerCUP`bp0EZ{|QKNvm|XW;d~mf^$oy0-h()Ef#%%O2ES zWqaGmmR4w}|7fsaRkt)XfQx*;*%}YNbq>^QWbdaidrxEc8Ld8Y2KeygOyUCmVfVwt zWE^|Jst*tCO49s$j$6A_Cxjr?xs&a)Qq{OYZ{Q4F!MnV0W5Cm022Zi zG+L~9v0LuauY|r4Vg_}sf#dN4>`C$!;Mwiu4hRhewzoGn4C`pq>GS3C;XkL`33)B; z!2CO@>!&PhIl~_3`F1DyS}E{tNoHynxszCUl6)P?ztO+^-|Fvfi##&w7apH~?!!r| zqS)h}$qlOwZiG3oYnq|GY~ks-?PnDD58R8?eH zK)6a79-s_XhX%w%gsEauL!%->Qp5P}zDK^#uMzSz&`b4nksn~WgYy>pz2n8^F@!x2 zQzuW5pFnhh{D?eBo+3YHzhJ*)zhb{$Lw-trMt)9y!G6R3z@BF>u>R%PG{mN8g~2++ ze`$Hb;_Yl4yEL1tX6x6~%y;A&+|2jn59E*JPwZ*-TlPEl`!yhpE}&T;f-HdcCxF-i zb?Q_KMm>&LLwbO2JXZPoqPp=0ix#-hvF}@B2wqbUOF=;TeO?-hx3q#O4!qtt21pIc z+w}o4)Q&Zzsh_`*e?m$Jd4;@6UL$`euakd}H^`gpkL*wE8TM!PEc*-lD|-&7bm0Fu zrNi(a`j7u(Af)g|D3ten7UHiiO zJqW%aGqo7r@ylDr$`ua+&egTmHGq-O7aRwdUPg0g?Fj2AyZ}v<{qr3$`-SCq3qvS*5d>Olh=xg+%x$Jd7nBes7l;fEr(DiFg(xd2L8iqEJjNlPa3~= zYs%N49;50ZDnr##!#G57i0;B1XW%kGs;#+#9LPk`HJH|DT+55hZ;atOoaAH<;G5{= zacTmg*g}n>nyArKGu1+kp~h0Jl%8s%+Np8Wcn%3UB;t^mLlO>2ImB>C#vuz14dBo~ z4#_!W$swyPR0s70H4z(B)MWT!DUO;-Jw;6;d^u#zAs>!>9$Qu%xd?x}z>!Nh@)eGJ zha(xL%nLeLSeas=y8$&oWl-KgSsb)7>2917%65Ql;}6vClU z4ux?joI?>Dirhrq;H`J+HXjI~ASHuCQJ{^%fag#g9}3}6IQ;8h91Uzjzm(HW}=>octE}}JbF^94^l+B?W4&`zvk3;z!D&SDz2D+3kqs!?E zT1!{bRdh9nia4a<5R@q4P$`GXI8@1@Dl8K1`8Xw?KGox-=_~byl}}@YUfuxCZ(|d- zz>HfcYku$c-# z)i?|~4(vL^dxHf^k3<7jvl{;IQHH;9I+-dn#VDfljYWF;63IZ(zHyiaP-u^%}%H1|2M%)KgSDRdJ!*p=@+r!tuYDS$4r8^5er_v z-z%`R0Bry%PW*#TkQU%<8UBM`#s4>jwCD}=W-KK((wjI`$Dv_e^h@*>4%KsL_@k2& z+v%OWlz0_Oi3UvYyRekd^^+3vE2c@@L+`^vA_Rm4Xr)wxkWh8g`$0&&!=VvaNQ{JR z9r_S(-{Jn;7jSgP6L)8&rz{e``i1S1kq0rOz7L=u^as7*m$7_wVebCb-7dNGxdFWx z^*H?z&!{IbqdxwSew%vz8T}KXc!^p`e*x~&a%w644Yh>+)}W!6Q7;<2q$Y#A1Wprp zPt68TYK%#DgQOnx5`BjLnLbPZLjOviqtDY9=!^6v`ZE0+hgvzra%d`tp5qYE=1vYB z7+8e} zQb&Y_hNp(5285(SWHnr+N(+b%O-%_1PYVf;Oo>d3ic~3kw{VMZ0bJLH|J~-h*UEgT zzeoSgH$Apip8v4H4Qr}v9%UYFP;dlJsZSNj5FB7c916@uo<zIbNiJsm3Ue3#(%{2ZX>q$8nJx< zWBYj%wilVOy|M?}hcLDeb7+AH+wWs+e*oBq9}X?V*oF_lw$RkQlY&q1B%Bg_%%K-J zw75&~sQ|{aghR_7of!C9@GYLTrGnE~47`YE?Rz|HOZ&~5e37YRKMT&?$Icg=2O7D^ zq2-uJR$vPG4bXn2KibzNk3JX@mp*LEjWr9)f}M_G3i%yCulEO?vu403c|ykYtp~&( z=XJO`_fW`9!EGMfw=lL>J*3~pUJH>>1n~tSDWrt7P#_d?sFOoo9O~xK8V;>pD-;VQ zLaC4u$~d%+L+d%TfkQhu^eVO``KW=(|L47SNQE5N*wU^y1_k(sDPRFZbXUI zxULm){r?f%6xB5apiSvfP`lTCYOm#=;eaz=wQ0v{-%cCBjD7A6ZQrNT-f!ghR&6jq zg?2#FLVFHvWa~Z_Izq}BB%TSKgoB07LYE42mIQ}h;?QOefgQU2|KqTwCuB?yTs!u= zH71mM2)$w1MCd8>;t&|WFLw!jgutjU&>DxKPCkpQvipyv9PCkklAwK zw`cUqFSyT?z}8Y>s4xtinLZPFFZ#NL;lc>)aXcLC{`qgFfkg>n(W{pOB#aZr_kEBg z%qA3@d&h)?>W4*zUh5qg+APfB1)Q*eY!w#aB^_Z27IC{wBJK?q>{FbpVZosTSjzFc z0(ucTEc`)ch_ZbGEV$r>UW0jR@UZxq*5a84XZ3ZXKor(I%%2hJgd>69g~Np-IJBEX zZ*~bAg^zP+4~O1s_v&vT*o2b+baH>twI??JI!c!J)|z8UlkQF}xnmY=LQXgh4|FP4efvQ5(L=H7 zOTLflGZDiq;q1P+pDTRMARC4Av21(?8%Ylc-k7RbEbRJkno!tnB$V~MCKPVQWb&?w zOb(kg;fH2TDBOw>zl}qOOo-nBi0_{RWa`>(;ahk<-V_3=k8tR{F5zC`+Z=kILq{K( z78D*79>z1aRCow$!4F`@APwjj~cQh164S$B*> z$8q-0N#SRJ{?GfP|K&x0u4>t;{+8WE1tyJVgqTvk0?@DfgC1I!dvVX?yuBOlEI+?q z_4?2rO8Hj!1CQqKF`7^ObEkh1!p_3pQsaW~#(zZnO(WXx@Msqi811J_X#dQFcEiH1 zf!{?mpj{;3(8or!i^PC-kwhfLKOFjmL!a`WU;@eIro$XWmLeM%q{vES&7sdZ^hKA* zR%FMaFFEw}BSXK)N#tTczsMP*|Eqsy7?GD~$bBV+$WP>tCB-)!It}<21rh^ALH&{X z_pQ?}P5E8<#?!lsCF2_oV-rXe3ZP;AK{eS0@w-vRgl+Enjm*Hp2s8dgk)jv_{zcIk z|KI(KUMGn%{x9K1QKl$Ml-)br_ydQ2>1ffq!sD@8zG8@ z^MojR+(?Mcm=J$95#lceAkE?BiJQwX{jCJhRsBJeMr?8XrCs&y{GSe;JJ+(|QV;!g ziPrM;w+7SSm4B2!qRpb${v(!m8L|AP5zB95EMGHW`41D8|L(!^I~dCcIrO^;%ZIVJ zIYL~(KODM_{Vn(aVKcx)gdd7dVkDdpfoXGtLpQrbr$pd-{mCIn40@>R{4ZQ@QzKu9 zzQ&WbRP+@VHMd~WM5nR8b-SOcu|!s;LO+Vm-bdSrei8kOwas4~x@*ujqKkmSOZ`!} z@Y!2G@6F2a-El&F_)O{1FpTXh0D84QXmeHW>DrCit2U(HI6kFNwzvn|*F`sZY~KKE zlf*+hZtC@2F$L+45G`E^i6)B;`6M2O=%~#aF)bE|g<=s$!clkwIk0d@Z*w$XEEU`S zH#!kRKn`a{i5+=55xW4Lkd%>5NP&?~NSvg|htQ=Hi6_W^v18k6} z_{e&c;wjqx@yj<=C=6Q-?9n0D;JG>*54+c?tFglQaa!(rI|p;*(TP8M?* z4O7G{M_O~FO_zA8_$iLGNW=n`b7dv6`c~F{*O}ZTcZ^F(I~~v zV%qjG(e_XiZHM&G_IW(o3moZdqV3BD+7@4-t;ARG0s{$43HTHA-9S?ZZ-{SU>b@!d zlOz2&(!Wc5TYQHj6&xA($RwErNoZ(9LSji4@Ne8%iBvM+KFUrqP$I{Y45V(bL6S*q zh=CH@{zN|Ji#1c`W#nh9vHLJ@di0wPpzI`r0MwyB=z-9oyDr?woU-fgx|+4azP2|j zJIP>)tAV;DE||JQF?B!OgO+$p6tJ%kGAnULU_XwGFi}pFiE@(6 zlq1On%8@`PB8`+IDFn)Spf@flmsDbOR!FoQ8O@O~U6LwEHAlvBus7{dNkqvo$#6^o z{T7-eBPESk9L8~EJV-=I6QFf;f3yzozr95=20+L52kpO;#mF3zHpzHA&~{9>iT}bP zmrRmiKY@H(G6j=NGLX!}LkNNr2(fI8xmu*&=zFBhxuD>(OC(hh!ITvGmCnlDtlCmh9%p42EMnTY(BjEM#qzv_BTEhPTsnXlC>_`zdG}c@l3D?% zb$`$Yt`*mjl`!w8~|i8mX()1DAMP>W&eu{TC&MNW=acrlsLLrlrw5 zrls*1(^V!+*O)Lp+>B{yGGJN?+N0WtX=y58`u@Q*(}1$2c;#H0BhBT=T8@0IOPVh& z;K(|TYjAW(KWP65nrWaTq>uBs zZp66O0p|WecT2}eA?%NF-HLHN;$M{LkWTxLxSnpr^)p6XKZ|jV)4XuTE$mk|Y(C~l zoK(VlsM6;!uAk?~$4$6~EgbiaWSAykxpWoA^a|-pj%?z{(OuHj(oT+S=EyM^)3BB1 zK`8+LK8_(>C*8=;@&-K1Eih*fies3%wN<)<*Xs9#tfV`ouVG<7mLpq1-b>#AgzxT; z@cxmBlTuI(()*}}{_ze|ANETR@|b=HW4i5M^!j}%WTpR?+-m8kMofQc#Pn&5>2W4Z zPcUJc?ZNa97}Gy;NPK_9r#!AKYx43v>#TWrd|u*DcF z#@2vl#s;IA12jL}7Gspi=rj2PP+JI~!)8Z;Q*GwJ^9QDx2By;3+BY7|>Kpqdw0A;e6Ts&*p zOb$oR;K-R>OdgZZkf;z*n<#MA)JYy0E8|1#)FW*C6h z_Xq92xN2l-W;oM`2RZ_i2Tm6H#}!kig=yvKV=ShRdH*c%nF&lspQSVANoMjrX=SGF zOl4pJx03;)S4W`zYFmRqhH}fL1lv&1+FLHQNmUS~Ln3W9J|I0bDiz7E+ z@qmxK?6)nE-({#boJ|&SKLETt5f0jE=(}xE)xj4ul*6ax_p7)sJZ`|$;kC>Lh_^87 znDrdFf+JUUF&mjp90{H6#90OX4$FH;TEI!Vw4f4BIYd2OM!=DDfKeiXjX%Yt}9XAWc1cgV}?7wwrmABfB|rO&9YPvzH^+ za&Xqe|Fvh(uXmV(%)1rPGrfTdIC34FeUg?E1xF^SA#XJ-BpmWqL&5^0!-Jy(LZd=L zVw6!~>S$$DZR41cEpUQH>?pK;_Cjs%}^ zD@Sg_v-LlW6bdWDmBA4aVQB%Xv?ygjcuHhyK#V#Bl9*Ldp(&BUX<;d9<^87#@={=f zVQ_SCXjB0F4uN?M4oiuS3JwlUGnQ!4^D_$Vs6xWRBjJLDfUuYtxDO*zr4CSrgsK9R zk&z+M;c8`?Dkb#(?eLUz+Du8WL$qx*|L}YMp>T83FqjbI!=H=~S4`M#Z+>`|e<+)v z7<9H5A)aHd!|^uEdFBFhk-5ZNW`1L?Fjtvt%EnVxry*)3_HA-Tg+|d4)Ygtmw_cRj@*Ma4M#$F6zKVFj@-wQ`#JIetI#&;)0!Gb z;HxcQhccXN4vA{1kmXhn2S(Sm!Io*rIk)TmI8zT9v~W#F8^GW5qO1|l27=QJfQR`K zn$&E=xf9K>XSq+^rpz`tMT&>)y%)Q}T-f}Y?~%H0bYm0jyteJ#ADnXpyIJtL?*s2; zoBKRTY#7(pU>IM(J<$J(UP}y|2&r246b_9Hj3{j^z+9{4e}zOVLXF%bvz844?vdHZ zY-M&bK>k6Fe3v7^iaxwX<{)#FImrfdL8{ckEy=F>wy!TD70oqxyXd2Cm@yJ3V zoVr89!db(0MBQ*mAu9uq+m^Kk=H;uvgD@8cM>tQDQj9yI*45#&Bj6lue89QB7swzJ zNJ(ai9(LK7U!AlYkeJ+=1Q#6H;I>t1PF*RfaM_W(OP$VN!~^@ypmtNu_^O$fUN~>Wa-RCJlSJxP6qpP|IzCH%ovsS&Rt$}B_u3=F8KF=R;ok9y7 z2>c{59S#J>SEsBXR>7gb+u(ALcZhf4NZ|JjhXQ{LHwgYhTp{iviQ!U(!GtfVggXG2!u@|M;mp?8&=&~b>319b1qU(;NeRio?S68& z*UtvdIt(ZC;2yssvWy%?j)FVVUjHMpJcIGh*t6ZtFo8yu2!ld_=r zlU@iC8Rbd6@wf38<;{_w8jLnswk)UGY_fqZTGtJaa*2g7h4`CE4CTvU=gK;ep@p&{ zS&*z)2C-6*q@Q!-7a&YI@=K2VYMrc%{9INc)51Ab@ZxLsJV$=Rk*DFfs`FU9;uDG= zA~i8}{UZ?c34rizKM$!118Leiuul31NPO6Z0n#&>KVXM2m`Sn*-uT&cZmt$axw=Iz zCyL>U$p)g1=pfwSvkg9@h*%<=h;j=e3W!`@4~;Y&WDF$aKzI-eA_O3kh(frmvWln? zdI*OIBZLXU3}L>oSXc@d@l^|Jg~Q+~zS+Vq;a1^3xODHJ@R0C`@O`*=@3`=U@O$Bp z!ZX6N!e52wg%^dFg;#{vL=0TKS1Fn;dKIqGyDGL24}kQKIB}l108$=`#iinMxGt|s zTqAx=tQWV7$BR3}6XEK-DUdBN4KB}nM!a0SRs4?lQ@AGYFIe7mmW08jcuKewuSilX zDTOsPEv%x|NJhiecw^ymymrZWNe5h!H%aoG2tx4t&xU6mfTuWyK#)+M55L`&-4H|wZTuB#TAi(jm zRkHVF7iG6BC<}pw$U0sRVi94HX)(-VlEo~GXD#MgJa4hk;suLW z)j%t_-p0z- z%HGPs%ExMmm7kTuD$pv}D%2|6D&MNn>M5(0R(q}XSskXwK-sO+U7f(A8dZI`Pt?dn{&1N zvm0gig55H^Lv|n9owhq;ciCQSZ)@*j?`H2|?`7{}A7me5A7&q6A7vk7ueQ&$&$iFA zFR(ANue7hWuXPyZFu~y|hv^P89G-EQ?eMI_5{IP@%N9Va_JilJI_`AB>7>(Hr(d1UJ6&|T>~zKH zn$vZs8%}>Z-FEtGu-o9=!D9wb9NanhjlmxdzUqveZJj-wBb_syvz>FDOPz-~k8svI z&vc&U-0i&9dA;*S=grProVPlE=lrYlZ_Zbpe|P@F`KF7&MdTuJVO$2eIJyjWadGi? z32+H=32}*aNq5n>RJ+u=)Vb8VeB<)7%NhBuh8ss{|b-wFz*KXIXuKQiz zcRl9%q3cJkr(MsxUUa?edd2mc>vh)~ZnT@wP3$IhlerCWle<~Dg}NoW<+wfO*6KFR z?P<3KZcE*kyRCFv?Y7=+quXY;EpA)g4!V8qcFpaM+u!cUopK-Ip6Wi*{R#Ia?k~F^ za6j&T!Tpl^Z|+y!e|P`G{igdZ_dD))J?uRkJe)k7JzPE9Jv==W9$_949#I}K9&sKS z9$6ka9(f*>9@QST9(5kg9%DROJ=#1bdCc;d<1yD`zQ+oWwH~i{?C{v>vCCth$03go zJbv}`@eK3K@XYee@yzoq@GSBy_AK=*_tbh;d5-ned$xOy_w4YT=sC%AiYMp!l;?EM z8J=rAk9eN-l6VDpX}rdJz2LRe>!jCLUZ=gj^ZLQ-yw~que|X*Wy5)7p>#jH9ZQ*U@ zZR2g{?d0w3?dt9Bo#a)dXtIz8``+bi4obvg^=QE$L zeNOv)=ktTlFFrSXU3`6g6Ma*BwZ8Sf4Zg#D^}bK~PWEMer}|Fweb#rL?{mHjd>8pH z_Fe6}$#Bh>9rD_c*N5yLvS-NNA^V0L7;pemQ=5euaJ-zY@PjzgEBTejR=j{U-U%@_Wv2f!`v(#eOUOR{M4Nt?^su_p0B1 zzmNUC@jK^t#qXNmb-x>acl`-}(x3Jh`VaIU?CL2bO7VVN>!0sm{g=s(84)xXVuoc{#>ss6M5m-w&p@AU8XU+e#p|I7Z{{I~nR z>VL@pL;tV+zxDsY|BU}x|6dg%g^j{NF<9ZM@KX3FhA8|LDn)~0tYVI0p5l4MBE=HL zGQ~#TCW1fB^yi z0nq`q0W$(#4A>fQG~l~{8-ad-@qwDas=%><`oM949f3~uqu!3%A9XP5y{HeOjz^t{`Znr9)L+p=G!-q3mPE^< z2S!^(+eZ6D4~(ceaYAAK(R zeDuW_ix|5Y&ls;5?-<{ha5yU~HYOn^IYt#z98(=r6H^!ScuY&ovoT9!mc=ZOSsAlA zW?#%ZF^6K_i#ZzeVa&;xPhvig`6}jg%(a+5Vs6IVin$Z>cPxsfVg<3{SZS;*);4xf ztW&IWtWRuEY)EWaY(#8SY)q^=Haj*iwlKCdwj#DFwkCFB?8?~9vD;#|$L@^X9lIy? z?b!XXCt^>=ejEF9?60vGVlT)35&LKC?by3F>jJJ$;iFb?li1&`y$FuQMXh`$tnJN~cuzZ1xW;)J?{ zaS0s>PbN%Bn3^y>VP?YYgt-aNB`i$XnDA1<%L&^OwkPaN*p=``!kYNcbS( zc)~{srxLzSIFs;8!ufwiA{;kiDMJT zB~D13m^dkMPU4G+s}s8u*ClRD+?KdKacAP|iMtcuNj#DGW8#^_ONqCW1|-RooRd6~ zypnvA6iIgRlS$7dElgURv@~f&((0t{q;*LflU_>N znshMfaMF88A0!=1I+1iT>EonNlfFp$D(Rb~pObz~x{!1!=|(bIcj$~OUf(C zYsx>AHQZe~ zy`Zs}q)dkfh)r}N7#XiL$#V5r-B_Jg@B_bs%B{n5KB|D`& zr9NeNN@EJd|5930CZ(_`Po+Ga@=VH{l({K;Q!b_sPEAQ2n>ssnW$OCWO{rT_x2En( zeLwYB>WS1-sh_4rq$Q?hrxm1W(n`}R(`wS{((2PjrM0F_Oq-m>r9G9lGVPVL-DzK@ zeXmApd$pI^TkWGBqK;5Us-x5~YPGsr{g}F5JzU+WZc?|XTh;CA3F?XJ$?8SwCF-T> zoqB_MlX{DKtNIo7*XlFsU)1N-7uA2L|4v8gRJtHtlrBlPOLt0lNq0~8 zNgtZ7NDoXeOm9w~m_9Xqdiu=t+3C-xFHB#YzBGM#`r7pE>F=f=PCt?UP5N)?SJVH_ zpff}nl8gZvmKoL=wi#X-AsL|=VHpt_s*LoE%#56j!VFDDX-0X*h>W(3XENqw%*%K_ zV^PMEjAa=sGdeTYWUSBFlks-O{)~4r-px3Y@qWh9jN=&}Wt__RB;$0(_ZdHB{G4$) z<7URKj5`^3Gl@(xb6}=TrhTSkrfa50rgtXzeVHRN+cG;cCuUC0d@6H#=8Vi|GM8ko z&3q|yYv%UMotb+w-_AUcc`)-(=7*VIXI{>{l6fmjl;xN;IBRHDU{-KeSXOjaY*u_$ zVpeXJHmfqLDyt@IR2G}{RMyj3&t%QXnwRx_)}pK>SfzctgBhqvucj5<}~HB*^;w0=k=U7a^B22nDcJV;heKMzvoJFWw`@$t#WO12jx2D zy5zd&dgc1&#^)yGDsxkE({eL%vvPBC^KuJwHMu3Za7RjRL+=!2IC+(ERZHsQj4xxct=o^!%*+ocxmf zn*6%_`uySfBl926Z_4k;pPbL-Ps@KMe@_0q{O9st&i^R?bpDU|XYzl^znFhH|7!m4 z1yq5sz`DS(z`4M!z_Y-wAfO<)AhaO7AfX_upt_*8U}Qmi!OVhL1&a%o7pyGkELc~t zpPxwy>(Owy>_SzObpVrLeWIt#DG|(}m9z&Mur=IKS}u z!c~Q93fC8ID%@JQy>MsYuELKCZxqo*k|J5rfFkQ6yP`owjzvR@B8w7=l8aJ`)J3^P z1x1>olA^Mr$BIT5u|-phW*03f+FZ1yXm`=uMf-~m7QI*WLDBJ|6Gh(^UCSuBTdPXBEF#ysmh2@s{Fk#XF0474I(IQ~W{k=fyu3 z|6KfQ@rB~c#aD~}Dj`a!5@Cs?L{>7OWJpO|Noq-YNmfZtNp;C%CG{o4OBzdBOQw`e zE16L;t7Klu^CgQ)7MH9p=`LAUvZ3V7k|QPGlzdn6W694Yzm{AmxmNrEo!bX>;k= z(zeo$(kDx&lyapjOZS%^FFjTIY3b*s-;{n=`a|hYrPs=cGEted%%V(QW?$x5=3M4l z=3eGk7FCv2mQz+zHmt0@Y<$_YvS-R>m(49(P`0RSN!ik}jb*Quy;JsX*^#mj%8r+P zRQ7S%_hmnooh>_8cCqZYvTJ46%gJ*4a>sJ#a<_8Na-Z^{<%;s4^3d{#@~raQ^8E6` za!q+@d3m|EysEsmyso^yys5mUytTZod{X(-i?k)$a;;WduWiycYsY9?wQbsQ+NZQLwX?Nz zwF|T_XkXMW)9%%NrTs;FS$kD`U3*jecO_X#SBfenl~$Fmm7$g4l?jzumDQECl}(jn zEA^G*DkoM>s+>~ERnD(mQQ1|wrgDAdOO-EIZmZm0xxeyw<&Tv=SN>Xgq4IL&)ynIY zH!E*f-mOAac2y2lPF2oTu2mjYUR6F-L#q6%0;+Yb_+Ri9OTS@liTcU5Pr&Q)Ekx?FXo>Q=R=+Oc|Y zwRd%Bby{_Lb#Zk?b!Bx;b$zw2dSvzE)g9GOSI@3~wt9Z`qUy!fORJYxZ?1m5`dIae z>QmL9R)10bb@jK^KUANo{-yeS4N*hY2x>$%k{VgffEsy?RgGE3_3 zuHrb3+n0Uwl4&xA$tWgCqK=dh3oj@th!l~R5R;wT`&>Ss^Esaxhs?QL8`_$#hL;J( zw5%|e6NcABl2Q?hBv^)mp;?6HDne64)L}IJ>GelEw&yDb#iEHQ0nJ3S(Oi^@(oqIl zhH?=`3@N0Mk5;0ss1hARwWuC7phol)I)~1qc61S4K{xQDcnBVapT_Yx0nfpycmdA9 zS$Gj%inFo67O%!@@LF7e*W>r_2D}~b#z*ln{0%;d>+pB@G;YE_;uhSB|Hj?;I=+E> z@NIkt-^2an5fVazWCR&S!bmuYBC%u=i6c|U^JFSXA!%eGd6_ICOUW{_oV-O!NEz8n zD#$)^m>eP1ULj!vhUG>hg^N`Xr1(Sc`^ z0f;N0g|w8G(LJ=BR?-9XFs-6r(XZ(VdXAo_?erqOL_26Fy+W_jzi1cjrnl)`+Rp~D zp)8bzv2gYzi(rv#0(*|dv#BhJ&1AD#3gfJR6|y4sA=|+|W}mY!*j`q_Dp?h)V{Pnb z_B*=@;(s|BSo|r zC*s94ktAk_Ibxnj6$?c6Ks4MDdLS$QzqGFwYs3aoB#Om$Q6kF39#JkT#eQ*692d2s zUNneC(IhU2UO7mH$eLM5tXm8P=PVzojsrPLd0jasWVszS9zZBr#`r`n}HRfklw zYEvERvJ>r0a%MU6olGa&S?&}&rB1n1=^StlJ5|mJ=aloU^S$$f)9em+7Y@Yh_Pf*W5lG&_Nxl$LonYPEXMZdYVqsGxcmeSEuUNb*{#mYS2=<+US+q z>Q#ERUZ+d+0exEkqIH0n6u`Bxnw%bW%GysgrDqdKi^;F zuk$zhANZU6t$vBW)8FNP=I{0^{6l_?UzgXC*P8ch-amOgdH3v48*azgNE>CNZH$ez ziS|W1-OjRe>|8tFrdiK!w$--NcG+9@etsZ7W+47O`GFh-0uOe$eke%~CqMo_%bxdU diff --git a/myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/xcuserdata/ceuak.xcuserdatad/xcschemes/xcschememanagement.plist b/myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/xcuserdata/ceuak.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index cf0295c..0000000 --- a/myApp/LemonLimeTracker/LemonLimeTracker.xcodeproj/xcuserdata/ceuak.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,19 +0,0 @@ - - - - - SchemeUserState - - LemonLimeTracker.xcscheme_^#shared#^_ - - orderHint - 0 - - LemonLimeWidgetExtension.xcscheme_^#shared#^_ - - orderHint - 1 - - - - diff --git a/myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb87897..0000000 --- a/myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/AppIcon.appiconset/Contents.json b/myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 2305880..0000000 --- a/myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "tinted" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/Contents.json b/myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json deleted file mode 100644 index eb87897..0000000 --- a/myApp/LemonLimeTracker/LemonLimeWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "colors" : [ - { - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/myApp/LemonLimeTracker/LemonLimeWidget/Info.plist b/myApp/LemonLimeTracker/LemonLimeWidget/Info.plist deleted file mode 100644 index 0f118fb..0000000 --- a/myApp/LemonLimeTracker/LemonLimeWidget/Info.plist +++ /dev/null @@ -1,11 +0,0 @@ - - - - - NSExtension - - NSExtensionPointIdentifier - com.apple.widgetkit-extension - - - diff --git a/myApp/LemonLimeTracker/LemonLimeWidget/LemonLimeWidget.swift b/myApp/LemonLimeTracker/LemonLimeWidget/LemonLimeWidget.swift deleted file mode 100644 index f5d0984..0000000 --- a/myApp/LemonLimeTracker/LemonLimeWidget/LemonLimeWidget.swift +++ /dev/null @@ -1,84 +0,0 @@ -// -// LemonLimeWidget.swift -// LemonLimeWidget -// -// Created by 송예찬 on 6/18/26. -// - -import WidgetKit -import SwiftUI - -struct Provider: TimelineProvider { - func placeholder(in context: Context) -> SimpleEntry { - SimpleEntry(date: Date(), emoji: "😀") - } - - func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) { - let entry = SimpleEntry(date: Date(), emoji: "😀") - completion(entry) - } - - func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { - var entries: [SimpleEntry] = [] - - // Generate a timeline consisting of five entries an hour apart, starting from the current date. - let currentDate = Date() - for hourOffset in 0 ..< 5 { - let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! - let entry = SimpleEntry(date: entryDate, emoji: "😀") - entries.append(entry) - } - - let timeline = Timeline(entries: entries, policy: .atEnd) - completion(timeline) - } - -// func relevances() async -> WidgetRelevances { -// // Generate a list containing the contexts this widget is relevant in. -// } -} - -struct SimpleEntry: TimelineEntry { - let date: Date - let emoji: String -} - -struct LemonLimeWidgetEntryView : View { - var entry: Provider.Entry - - var body: some View { - VStack { - Text("Time:") - Text(entry.date, style: .time) - - Text("Emoji:") - Text(entry.emoji) - } - } -} - -struct LemonLimeWidget: Widget { - let kind: String = "LemonLimeWidget" - - var body: some WidgetConfiguration { - StaticConfiguration(kind: kind, provider: Provider()) { entry in - if #available(iOS 17.0, *) { - LemonLimeWidgetEntryView(entry: entry) - .containerBackground(.fill.tertiary, for: .widget) - } else { - LemonLimeWidgetEntryView(entry: entry) - .padding() - .background() - } - } - .configurationDisplayName("My Widget") - .description("This is an example widget.") - } -} - -#Preview(as: .systemSmall) { - LemonLimeWidget() -} timeline: { - SimpleEntry(date: .now, emoji: "😀") - SimpleEntry(date: .now, emoji: "🤩") -} diff --git a/myApp/LemonLimeTracker/LemonLimeWidget/LemonLimeWidgetBundle.swift b/myApp/LemonLimeTracker/LemonLimeWidget/LemonLimeWidgetBundle.swift deleted file mode 100644 index 8b40784..0000000 --- a/myApp/LemonLimeTracker/LemonLimeWidget/LemonLimeWidgetBundle.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// LemonLimeWidgetBundle.swift -// LemonLimeWidget -// -// Created by 송예찬 on 6/18/26. -// - -import WidgetKit -import SwiftUI - -@main -struct LemonLimeWidgetBundle: WidgetBundle { - var body: some Widget { - LemonLimeWidget() - LemonLimeWidgetLiveActivity() - } -} diff --git a/myApp/LemonLimeTracker/LemonLimeWidget/LemonLimeWidgetLiveActivity.swift b/myApp/LemonLimeTracker/LemonLimeWidget/LemonLimeWidgetLiveActivity.swift deleted file mode 100644 index 594a7e8..0000000 --- a/myApp/LemonLimeTracker/LemonLimeWidget/LemonLimeWidgetLiveActivity.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// LemonLimeWidgetLiveActivity.swift -// LemonLimeWidget -// -// Created by 송예찬 on 6/18/26. -// - -import ActivityKit -import WidgetKit -import SwiftUI - -private let timerEndDate = Date.distantFuture - -struct LemonLimeWidgetLiveActivity: Widget { - var body: some WidgetConfiguration { - ActivityConfiguration(for: TimerActivityAttributes.self) { context in - // MARK: Lock Screen / Banner - HStack(spacing: 14) { - Image(systemName: "timer") - .font(.title2.weight(.semibold)) - .foregroundStyle(.yellow) - - VStack(alignment: .leading, spacing: 3) { - Text(context.attributes.taskName) - .font(.subheadline.weight(.semibold)) - .foregroundStyle(.white) - .lineLimit(1) - - Text(timerInterval: context.state.startDate...timerEndDate, countsDown: false) - .font(.caption.monospacedDigit()) - .foregroundStyle(.yellow) - } - - Spacer() - - Text(context.attributes.taskIcon) - .font(.largeTitle) - } - .padding(.horizontal, 16) - .padding(.vertical, 12) - .activityBackgroundTint(Color(red: 0.08, green: 0.30, blue: 0.08)) - .activitySystemActionForegroundColor(.yellow) - - } dynamicIsland: { context in - DynamicIsland { - // MARK: Expanded – top row - DynamicIslandExpandedRegion(.leading) { - Label { - Text(context.attributes.taskName) - .font(.caption.weight(.semibold)) - .lineLimit(1) - } icon: { - Text(context.attributes.taskIcon) - .font(.caption) - } - .foregroundStyle(.green) - } - - DynamicIslandExpandedRegion(.trailing) { - Image(systemName: "circle.fill") - .font(.caption) - .foregroundStyle(.green) - .padding(.trailing, 4) - } - - // MARK: Expanded – center (large timer) - DynamicIslandExpandedRegion(.bottom) { - Text(timerInterval: context.state.startDate...timerEndDate, countsDown: false) - .font(.system(size: 44, weight: .bold, design: .monospaced)) - .foregroundStyle(.yellow) - .frame(maxWidth: .infinity, alignment: .center) - .padding(.top, 4) - } - - } compactLeading: { - Image(systemName: "timer") - .foregroundStyle(.green) - .font(.caption.weight(.semibold)) - - } compactTrailing: { - Text(timerInterval: context.state.startDate...timerEndDate, countsDown: false) - .monospacedDigit() - .font(.caption2.weight(.medium)) - .foregroundStyle(.yellow) - .frame(maxWidth: 52) - - } minimal: { - ZStack { - Circle() - .fill(LinearGradient( - colors: [.green, Color(red: 0.8, green: 0.9, blue: 0.0)], - startPoint: .topLeading, - endPoint: .bottomTrailing - )) - Text(context.attributes.taskIcon) - .font(.system(size: 11)) - } - } - .keylineTint(.green) - } - } -} - -// MARK: - Previews - -extension TimerActivityAttributes { - fileprivate static var preview: TimerActivityAttributes { - TimerActivityAttributes(taskName: "운동하기", taskIcon: "🏃") - } -} - -extension TimerActivityAttributes.ContentState { - fileprivate static var running: TimerActivityAttributes.ContentState { - TimerActivityAttributes.ContentState( - startDate: Date().addingTimeInterval(-125), - elapsedSeconds: 125 - ) - } -} - -#Preview("Notification", as: .content, using: TimerActivityAttributes.preview) { - LemonLimeWidgetLiveActivity() -} contentStates: { - TimerActivityAttributes.ContentState.running -}