diff --git a/myApp/TallyFlow/CLAUDE.md b/myApp/TallyFlow/CLAUDE.md deleted file mode 100644 index 60e08aa..0000000 --- a/myApp/TallyFlow/CLAUDE.md +++ /dev/null @@ -1,63 +0,0 @@ -# TallyFlow - -## Product summary -TallyFlow is an iOS productivity tracker for measuring tasks by either time or count. -It supports hierarchical tags (super tag / sub tag), flexible goals, historical statistics, widgets, Live Activities, and a paired watchOS experience. - -## Source root -- iOS source root folder: `IOS/` -- Do not assume the default inner app folder name is `TallyFlow`; use `IOS/` as the app source root. - -## Non-negotiable constraints -- Use SwiftUI. -- Use SwiftData as the local persistence layer. -- Keep the project buildable after every milestone. -- Do not scan or refactor the entire project. -- Read only the files explicitly listed in each prompt. -- Edit only the files explicitly listed in each prompt. -- Do not mention competitor app names in code comments, docs, or commit messages. -- Korean is the default app language; English must be supported. -- Theme must support Light and Dark modes with custom green/yellow palettes, not generic system default vibes. -- All date aggregation must respect configurable week start day and configurable custom day start time. -- Time tracking must support multiple concurrent running timer tasks. -- A task can have multiple tags. -- Selecting a sub tag implies its parent super tag is also associated logically. - -## Architecture rules -- Separate app shell, domain models, services, and feature views. -- Create a single source of truth for: - 1. task definitions, - 2. tag hierarchy, - 3. goals, - 4. tracking sessions / count logs, - 5. calendar boundary calculations. -- Avoid business logic inside SwiftUI view bodies. -- Put theme colors/tokens in dedicated files. -- Keep milestone 1 free of widget/watch/live activity target work; only prepare the architecture so those can be added later. - -## UX rules -- Tab order must be: - 1. Main - 2. Tasks - 3. Tags - 4. Goals - 5. Stats - 6. Settings -- Add a splash/loading screen that shows the app name. -- Main tab should be scaffolded for: - - today / week / month summary cards, - - task action buttons, - - future layout-edit mode. -- Settings must be scaffolded for: - - theme mode, - - language, - - week start day, - - custom day start time, - - premium section entry. - -## Coding style -- Prefer small SwiftUI views. -- Prefer explicit model names over vague names. -- Add TODO markers only where the next milestone will extend logic. -- Do not add placeholder lorem ipsum text. -- Do not introduce third-party dependencies in milestone 1. diff --git a/myApp/TallyFlow/IOS/App/RootTabView.swift b/myApp/TallyFlow/IOS/App/RootTabView.swift deleted file mode 100644 index 57e1234..0000000 --- a/myApp/TallyFlow/IOS/App/RootTabView.swift +++ /dev/null @@ -1,37 +0,0 @@ -import SwiftData -import SwiftUI - -struct RootTabView: View { - var body: some View { - TabView { - Tab(String(localized: "tab.main"), systemImage: "house.fill") { - MainTabView() - } - Tab(String(localized: "tab.tasks"), systemImage: "checkmark.circle.fill") { - TasksTabView() - } - Tab(String(localized: "tab.tags"), systemImage: "tag.fill") { - TagsTabView() - } - Tab(String(localized: "tab.goals"), systemImage: "target") { - GoalsTabView() - } - Tab(String(localized: "tab.stats"), systemImage: "chart.bar.fill") { - StatsTabView() - } - Tab(String(localized: "tab.settings"), systemImage: "gearshape.fill") { - SettingsTabView() - } - } - .tint(Color.tfPrimary) - } -} - -#Preview { - RootTabView() - .modelContainer( - for: [TaskItem.self, TagEntity.self, GoalEntity.self, TrackingRecord.self], - inMemory: true - ) - .environment(AppPreferences()) -} diff --git a/myApp/TallyFlow/IOS/App/SplashView.swift b/myApp/TallyFlow/IOS/App/SplashView.swift deleted file mode 100644 index 3374d66..0000000 --- a/myApp/TallyFlow/IOS/App/SplashView.swift +++ /dev/null @@ -1,25 +0,0 @@ -import SwiftUI - -struct SplashView: View { - var body: some View { - ZStack { - Color.tfBackground.ignoresSafeArea() - VStack(spacing: 14) { - Image(systemName: "chart.bar.fill") - .font(.system(size: 60, weight: .semibold)) - .foregroundStyle(Color.tfPrimary) - Text("TallyFlow") - .font(.system(size: 38, weight: .bold, design: .rounded)) - .foregroundStyle(Color.tfOnBackground) - Text(String(localized: "splash.tagline")) - .font(.subheadline) - .foregroundStyle(Color.tfMuted) - } - } - } -} - -#Preview { - SplashView() - .environment(AppPreferences()) -} diff --git a/myApp/TallyFlow/IOS/Assets.xcassets/AccentColor.colorset/Contents.json b/myApp/TallyFlow/IOS/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index eb87897..0000000 --- a/myApp/TallyFlow/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/TallyFlow/IOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/myApp/TallyFlow/IOS/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 2305880..0000000 --- a/myApp/TallyFlow/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/TallyFlow/IOS/Assets.xcassets/Contents.json b/myApp/TallyFlow/IOS/Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/myApp/TallyFlow/IOS/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/myApp/TallyFlow/IOS/ContentView.swift b/myApp/TallyFlow/IOS/ContentView.swift deleted file mode 100644 index a57acd7..0000000 --- a/myApp/TallyFlow/IOS/ContentView.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// ContentView.swift -// TallyFlow -// -// Created by 송예찬 on 6/26/26. -// -import SwiftData -import SwiftUI - -struct ContentView: View { - @Environment(AppPreferences.self) private var preferences - @State private var isShowingSplash = true - - var body: some View { - Group { - if isShowingSplash { - SplashView() - .transition(.opacity) - .onAppear { - DispatchQueue.main.asyncAfter(deadline: .now() + 1.8) { - withAnimation(.easeOut(duration: 0.4)) { - isShowingSplash = false - } - } - } - } else { - RootTabView() - .transition(.opacity) - } - } - .preferredColorScheme(preferences.themeMode.colorScheme) - } -} - -#Preview { - ContentView() - .modelContainer( - for: [TaskItem.self, TagEntity.self, GoalEntity.self, TrackingRecord.self], - inMemory: true - ) - .environment(AppPreferences()) -} diff --git a/myApp/TallyFlow/IOS/Domain/Models/GoalEntity.swift b/myApp/TallyFlow/IOS/Domain/Models/GoalEntity.swift deleted file mode 100644 index 6b17fd1..0000000 --- a/myApp/TallyFlow/IOS/Domain/Models/GoalEntity.swift +++ /dev/null @@ -1,73 +0,0 @@ -import Foundation -import SwiftData - -enum GoalUnit: String, Codable, CaseIterable { - case seconds - case count -} - -enum GoalPeriod: String, Codable, CaseIterable { - case daily - case weekly - case monthly -} - -// Cannot be stored directly in SwiftData; use targetType computed property on GoalEntity. -enum TargetType { - case duration(min: Double, max: Double?) - case count(min: Int, max: Int?) -} - -@Model -final class GoalEntity { - var id: UUID - var title: String - var targetValue: Double - var targetMax: Double? - var unit: GoalUnit - var period: GoalPeriod - var createdAt: Date - var isActive: Bool - - var task: TaskItem? - var tag: TagEntity? - - var targetType: TargetType { - switch unit { - case .seconds: - return .duration(min: targetValue, max: targetMax) - case .count: - return .count(min: Int(targetValue), max: targetMax.map(Int.init)) - } - } - - init( - id: UUID = UUID(), - title: String, - targetValue: Double, - targetMax: Double? = nil, - unit: GoalUnit = .seconds, - period: GoalPeriod = .daily, - createdAt: Date = .now, - isActive: Bool = true - ) { - self.id = id - self.title = title - self.targetValue = targetValue - self.targetMax = targetMax - self.unit = unit - self.period = period - self.createdAt = createdAt - self.isActive = isActive - } -} - -extension GoalPeriod { - var localizedName: String { - switch self { - case .daily: return String(localized: "goal.period.daily") - case .weekly: return String(localized: "goal.period.weekly") - case .monthly: return String(localized: "goal.period.monthly") - } - } -} diff --git a/myApp/TallyFlow/IOS/Domain/Models/TagEntity.swift b/myApp/TallyFlow/IOS/Domain/Models/TagEntity.swift deleted file mode 100644 index 5530435..0000000 --- a/myApp/TallyFlow/IOS/Domain/Models/TagEntity.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation -import SwiftData - -@Model -final class TagEntity { - var id: UUID - var name: String - var colorHex: String - var createdAt: Date - var sortOrder: Int - - var parent: TagEntity? - - @Relationship(deleteRule: .cascade, inverse: \TagEntity.parent) - var subTags: [TagEntity] = [] - - // Inverse is declared on TaskItem.tags - var tasks: [TaskItem] = [] - - var isSubTag: Bool { parent != nil } - - init( - id: UUID = UUID(), - name: String, - colorHex: String = "#52A878", - createdAt: Date = .now, - sortOrder: Int = 0 - ) { - self.id = id - self.name = name - self.colorHex = colorHex - self.createdAt = createdAt - self.sortOrder = sortOrder - } -} diff --git a/myApp/TallyFlow/IOS/Domain/Models/TaskItem.swift b/myApp/TallyFlow/IOS/Domain/Models/TaskItem.swift deleted file mode 100644 index a881a91..0000000 --- a/myApp/TallyFlow/IOS/Domain/Models/TaskItem.swift +++ /dev/null @@ -1,42 +0,0 @@ -import Foundation -import SwiftData - -enum TaskType: String, Codable, CaseIterable { - case timer - case counter -} - -@Model -final class TaskItem { - var id: UUID - var name: String - var icon: String - var taskType: TaskType - var createdAt: Date - var isArchived: Bool - var sortOrder: Int - - @Relationship(deleteRule: .nullify, inverse: \TagEntity.tasks) - var tags: [TagEntity] = [] - - @Relationship(deleteRule: .cascade, inverse: \TrackingRecord.task) - var trackingRecords: [TrackingRecord] = [] - - init( - id: UUID = UUID(), - name: String, - icon: String = "star", - taskType: TaskType = .timer, - createdAt: Date = .now, - isArchived: Bool = false, - sortOrder: Int = 0 - ) { - self.id = id - self.name = name - self.icon = icon - self.taskType = taskType - self.createdAt = createdAt - self.isArchived = isArchived - self.sortOrder = sortOrder - } -} diff --git a/myApp/TallyFlow/IOS/Domain/Models/TrackingRecord.swift b/myApp/TallyFlow/IOS/Domain/Models/TrackingRecord.swift deleted file mode 100644 index a70d026..0000000 --- a/myApp/TallyFlow/IOS/Domain/Models/TrackingRecord.swift +++ /dev/null @@ -1,43 +0,0 @@ -import Foundation -import SwiftData - -enum TrackingRecordType: String, Codable { - case timerSession - case countLog -} - -@Model -final class TrackingRecord { - var id: UUID - var recordType: TrackingRecordType - var startTime: Date? - var endTime: Date? - var timestamp: Date? - var duration: TimeInterval - var createdAt: Date - - // Inverse is declared on TaskItem.trackingRecords - var task: TaskItem? - - var isRunning: Bool { recordType == .timerSession && startTime != nil && endTime == nil } - - init( - id: UUID = UUID(), - task: TaskItem? = nil, - recordType: TrackingRecordType, - startTime: Date? = nil, - endTime: Date? = nil, - timestamp: Date? = nil, - duration: TimeInterval = 0, - createdAt: Date = .now - ) { - self.id = id - self.task = task - self.recordType = recordType - self.startTime = startTime - self.endTime = endTime - self.timestamp = timestamp - self.duration = duration - self.createdAt = createdAt - } -} diff --git a/myApp/TallyFlow/IOS/Domain/Services/CalendarBoundaryService.swift b/myApp/TallyFlow/IOS/Domain/Services/CalendarBoundaryService.swift deleted file mode 100644 index 21a19a0..0000000 --- a/myApp/TallyFlow/IOS/Domain/Services/CalendarBoundaryService.swift +++ /dev/null @@ -1,46 +0,0 @@ -import Foundation - -final class CalendarBoundaryService { - private let preferences: AppPreferences - - init(preferences: AppPreferences) { - self.preferences = preferences - } - - private var calendar: Calendar { - var cal = Calendar.current - cal.firstWeekday = preferences.weekStartDay - return cal - } - - func customDayStart(for date: Date) -> Date { - // TODO: apply dayStartHour / dayStartMinute boundary shift in milestone 2 - var comps = calendar.dateComponents([.year, .month, .day], from: date) - comps.hour = preferences.dayStartHour - comps.minute = preferences.dayStartMinute - comps.second = 0 - return calendar.date(from: comps) ?? date - } - - func dayRange(for date: Date) -> DateInterval { - let start = customDayStart(for: date) - let end = calendar.date(byAdding: .day, value: 1, to: start) ?? start - return DateInterval(start: start, end: end) - } - - func weekRange(for date: Date) -> DateInterval { - // TODO: align to custom day-start boundary in milestone 2 - guard let interval = calendar.dateInterval(of: .weekOfYear, for: date) else { - return DateInterval(start: date, duration: 7 * 86_400) - } - return interval - } - - func monthRange(for date: Date) -> DateInterval { - // TODO: align to custom day-start boundary in milestone 2 - guard let interval = calendar.dateInterval(of: .month, for: date) else { - return DateInterval(start: date, duration: 30 * 86_400) - } - return interval - } -} diff --git a/myApp/TallyFlow/IOS/Domain/Services/TrackingEngine.swift b/myApp/TallyFlow/IOS/Domain/Services/TrackingEngine.swift deleted file mode 100644 index ec09f92..0000000 --- a/myApp/TallyFlow/IOS/Domain/Services/TrackingEngine.swift +++ /dev/null @@ -1,62 +0,0 @@ -import Foundation -import SwiftData - -@MainActor -@Observable -final class TrackingEngine { - private(set) var activeSessions: [UUID: Date] = [:] - private(set) var elapsedTimes: [UUID: TimeInterval] = [:] - - private var tickTask: Task? - - init() { - tickTask = Task { - while !Task.isCancelled { - try? await Task.sleep(for: .seconds(1)) - self.tick() - } - } - } - - private func tick() { - let now = Date.now - for (id, start) in activeSessions { - elapsedTimes[id] = now.timeIntervalSince(start) - } - } - - func isRunning(_ task: TaskItem) -> Bool { - activeSessions[task.id] != nil - } - - func elapsed(for task: TaskItem) -> TimeInterval { - elapsedTimes[task.id] ?? 0 - } - - func startTimer(for task: TaskItem, context: ModelContext) { - guard !isRunning(task) else { return } - let now = Date.now - let record = TrackingRecord(task: task, recordType: .timerSession, startTime: now) - context.insert(record) - task.trackingRecords.append(record) - activeSessions[task.id] = now - elapsedTimes[task.id] = 0 - } - - func stopTimer(for task: TaskItem, context: ModelContext) { - guard let startTime = activeSessions[task.id] else { return } - let now = Date.now - if let record = task.trackingRecords.first(where: { $0.isRunning }) { - record.endTime = now - record.duration = now.timeIntervalSince(startTime) - } - activeSessions.removeValue(forKey: task.id) - elapsedTimes.removeValue(forKey: task.id) - } - - func incrementCounter(for task: TaskItem, context: ModelContext) { - let record = TrackingRecord(task: task, recordType: .countLog, timestamp: .now) - context.insert(record) - task.trackingRecords.append(record) - } -} diff --git a/myApp/TallyFlow/IOS/Features/Goals/GoalsTabView.swift b/myApp/TallyFlow/IOS/Features/Goals/GoalsTabView.swift deleted file mode 100644 index 6628e0b..0000000 --- a/myApp/TallyFlow/IOS/Features/Goals/GoalsTabView.swift +++ /dev/null @@ -1,248 +0,0 @@ -import SwiftUI -import SwiftData - -struct GoalsTabView: View { - @Query(sort: \GoalEntity.createdAt) private var goals: [GoalEntity] - @State private var showAddSheet = false - - var body: some View { - NavigationStack { - Group { - if goals.isEmpty { - EmptyStateView( - icon: "target", - message: String(localized: "goals.empty") - ) - } else { - List(goals) { goal in - GoalRowView(goal: goal) - } - .scrollContentBackground(.hidden) - } - } - .background(Color.tfBackground) - .navigationTitle(String(localized: "tab.goals")) - .toolbar { - ToolbarItem(placement: .primaryAction) { - Button { showAddSheet = true } label: { - Image(systemName: "plus") - } - .tint(Color.tfPrimary) - } - } - .sheet(isPresented: $showAddSheet) { - AddGoalSheet() - } - } - } -} - -// MARK: – Goal row - -private struct GoalRowView: View { - let goal: GoalEntity - - var body: some View { - HStack { - VStack(alignment: .leading, spacing: 2) { - Text(goal.title) - .foregroundStyle(Color.tfOnBackground) - HStack(spacing: 4) { - Text(goal.period.localizedName) - Text("·") - Text(targetDescription) - } - .font(.caption) - .foregroundStyle(Color.tfMuted) - } - Spacer() - Text(goal.isActive - ? String(localized: "goal.active") - : String(localized: "goal.inactive")) - .font(.caption) - .padding(.horizontal, 8) - .padding(.vertical, 3) - .background( - goal.isActive - ? Color.tfPrimary.opacity(0.15) - : Color.tfMuted.opacity(0.15) - ) - .foregroundStyle(goal.isActive ? Color.tfPrimary : Color.tfMuted) - .clipShape(Capsule()) - } - .padding(.vertical, 4) - } - - private var targetDescription: String { - switch goal.targetType { - case .duration(let min, let max): - if let max { - return "\(min.hhmmss) – \(max.hhmmss)" - } - return min.hhmmss - case .count(let min, let max): - if let max { return "\(min) – \(max)" } - return "\(min)" - } - } -} - -// MARK: – Add goal sheet - -private struct AddGoalSheet: View { - @Environment(\.modelContext) private var context - @Environment(\.dismiss) private var dismiss - - @Query(sort: \TaskItem.sortOrder) private var allTasks: [TaskItem] - @Query(sort: \TagEntity.sortOrder) private var allTags: [TagEntity] - - @State private var title = "" - @State private var selectedTaskID: UUID? = nil - @State private var selectedTagID: UUID? = nil - @State private var period: GoalPeriod = .daily - @State private var unit: GoalUnit = .seconds - @State private var targetMin: Double = 1800 - @State private var hasMax = false - @State private var targetMaxValue: Double = 3600 - - private var activeTasks: [TaskItem] { allTasks.filter { !$0.isArchived } } - private var canSave: Bool { !title.trimmingCharacters(in: .whitespaces).isEmpty } - - var body: some View { - NavigationStack { - Form { - Section(String(localized: "goal.form.section.title")) { - TextField(String(localized: "goal.form.title.placeholder"), text: $title) - } - - Section(String(localized: "goal.form.section.scope")) { - Picker(String(localized: "goal.form.task"), selection: $selectedTaskID) { - Text(String(localized: "goal.form.none")).tag(Optional.none) - ForEach(activeTasks) { task in - Label(task.name, systemImage: task.icon).tag(Optional(task.id)) - } - } - Picker(String(localized: "goal.form.tag"), selection: $selectedTagID) { - Text(String(localized: "goal.form.none")).tag(Optional.none) - ForEach(allTags) { tag in - Text(tag.name).tag(Optional(tag.id)) - } - } - } - - Section(String(localized: "goal.form.section.period")) { - Picker("", selection: $period) { - ForEach(GoalPeriod.allCases, id: \.self) { p in - Text(p.localizedName).tag(p) - } - } - .pickerStyle(.segmented) - .labelsHidden() - } - - Section(String(localized: "goal.form.section.target")) { - Picker("", selection: $unit) { - Text(String(localized: "goal.unit.duration")).tag(GoalUnit.seconds) - Text(String(localized: "goal.unit.count")).tag(GoalUnit.count) - } - .pickerStyle(.segmented) - .labelsHidden() - .onChange(of: unit) { _, newUnit in - targetMin = newUnit == .seconds ? 1800 : 1 - targetMaxValue = newUnit == .seconds ? 3600 : 5 - } - - minValueStepper - - Toggle(String(localized: "goal.form.has_max"), isOn: $hasMax) - - if hasMax { - maxValueStepper - } - } - - Section { - Toggle(String(localized: "goal.active"), isOn: .constant(true)) - } - } - .navigationTitle(String(localized: "goal.add.title")) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button(String(localized: "action.cancel")) { dismiss() } - } - ToolbarItem(placement: .confirmationAction) { - Button(String(localized: "action.save")) { save() } - .disabled(!canSave) - .tint(Color.tfPrimary) - } - } - } - } - - @ViewBuilder - private var minValueStepper: some View { - if unit == .seconds { - Stepper( - value: $targetMin, - in: 60...86400, - step: 300 - ) { - Text("\(String(localized: "goal.form.min")): \(targetMin.hhmmss)") - .font(.subheadline.monospacedDigit()) - } - } else { - Stepper( - value: $targetMin, - in: 1...9999, - step: 1 - ) { - Text("\(String(localized: "goal.form.min")): \(Int(targetMin))") - .font(.subheadline) - } - } - } - - @ViewBuilder - private var maxValueStepper: some View { - if unit == .seconds { - Stepper( - value: $targetMaxValue, - in: targetMin...86400 * 7, - step: 300 - ) { - Text("\(String(localized: "goal.form.max")): \(targetMaxValue.hhmmss)") - .font(.subheadline.monospacedDigit()) - } - } else { - Stepper( - value: $targetMaxValue, - in: targetMin...9999, - step: 1 - ) { - Text("\(String(localized: "goal.form.max")): \(Int(targetMaxValue))") - .font(.subheadline) - } - } - } - - private func save() { - let goal = GoalEntity( - title: title.trimmingCharacters(in: .whitespaces), - targetValue: targetMin, - targetMax: hasMax ? targetMaxValue : nil, - unit: unit, - period: period - ) - goal.task = activeTasks.first { $0.id == selectedTaskID } - goal.tag = allTags.first { $0.id == selectedTagID } - context.insert(goal) - dismiss() - } -} - -#Preview { - GoalsTabView() - .modelContainer(for: [GoalEntity.self, TaskItem.self, TagEntity.self], inMemory: true) - .environment(AppPreferences()) -} diff --git a/myApp/TallyFlow/IOS/Features/Main/MainTabView.swift b/myApp/TallyFlow/IOS/Features/Main/MainTabView.swift deleted file mode 100644 index 3759252..0000000 --- a/myApp/TallyFlow/IOS/Features/Main/MainTabView.swift +++ /dev/null @@ -1,217 +0,0 @@ -import SwiftUI -import SwiftData - -// Module-level so GoalsTabView (same module) can reuse this formatter. -extension TimeInterval { - var hhmmss: String { - let s = Int(self) - return String(format: "%02d:%02d:%02d", s / 3600, (s % 3600) / 60, s % 60) - } -} - -struct MainTabView: View { - @Query(sort: \TaskItem.sortOrder) private var tasks: [TaskItem] - @State private var engine = TrackingEngine() - - private var activeTasks: [TaskItem] { tasks.filter { !$0.isArchived } } - - var body: some View { - NavigationStack { - ScrollView { - VStack(spacing: 20) { - SummaryCardsSection() - if !activeTasks.isEmpty { - TaskListSection(tasks: activeTasks) - } - LayoutEditEntryBanner() - } - .padding() - } - .background(Color.tfBackground) - .navigationTitle(String(localized: "tab.main")) - } - .environment(engine) - } -} - -// MARK: – Summary cards - -private struct SummaryCardsSection: View { - var body: some View { - VStack(spacing: 12) { - SummaryCard(period: String(localized: "main.period.today"), icon: "sun.max.fill") - SummaryCard(period: String(localized: "main.period.week"), icon: "calendar.badge.clock") - SummaryCard(period: String(localized: "main.period.month"), icon: "calendar") - } - } -} - -private struct SummaryCard: View { - let period: String - let icon: String - - var body: some View { - HStack(spacing: 12) { - Image(systemName: icon) - .font(.title3) - .foregroundStyle(Color.tfPrimary) - .frame(width: 28) - Text(period) - .font(.headline) - .foregroundStyle(Color.tfOnBackground) - Spacer() - Text("—") - .font(.subheadline) - .foregroundStyle(Color.tfMuted) - } - .padding() - .background(Color.tfSurface) - .clipShape(RoundedRectangle(cornerRadius: 12)) - .shadow(color: Color.tfSeparator, radius: 2, x: 0, y: 1) - } -} - -// MARK: – Task list - -private struct TaskListSection: View { - let tasks: [TaskItem] - - var body: some View { - VStack(alignment: .leading, spacing: 10) { - Text("작업") - .font(.footnote.weight(.semibold)) - .foregroundStyle(Color.tfMuted) - .textCase(.uppercase) - ForEach(tasks) { task in - TaskActionCard(task: task) - } - } - } -} - -private struct TaskActionCard: View { - @Environment(\.modelContext) private var context - @Environment(TrackingEngine.self) private var engine - let task: TaskItem - - @State private var counterPulse = false - - private var primaryColor: Color { - task.tags.first.map { Color(hex: $0.colorHex) } ?? Color.tfPrimary - } - - var body: some View { - HStack(spacing: 12) { - Image(systemName: task.icon) - .font(.title3) - .foregroundStyle(primaryColor) - .frame(width: 28) - - VStack(alignment: .leading, spacing: 2) { - Text(task.name) - .font(.subheadline.weight(.semibold)) - .foregroundStyle(Color.tfOnBackground) - Text(statusLabel) - .font(.caption.monospacedDigit()) - .foregroundStyle(engine.isRunning(task) ? primaryColor : Color.tfMuted) - .contentTransition(.numericText()) - .animation(.linear(duration: 0.2), value: statusLabel) - } - - Spacer() - - actionButton - } - .padding() - .background(Color.tfSurface) - .clipShape(RoundedRectangle(cornerRadius: 12)) - .shadow(color: Color.tfSeparator, radius: 2, x: 0, y: 1) - } - - private var statusLabel: String { - switch task.taskType { - case .timer: - return engine.isRunning(task) - ? engine.elapsed(for: task).hhmmss - : String(localized: "task.type.timer") - case .counter: - return String(localized: "task.type.counter") - } - } - - @ViewBuilder - private var actionButton: some View { - switch task.taskType { - case .timer: - let running = engine.isRunning(task) - Button { toggleTimer() } label: { - Image(systemName: running ? "stop.fill" : "play.fill") - .font(.title3) - .foregroundStyle(.white) - .frame(width: 44, height: 44) - .background(running ? Color.red : primaryColor) - .clipShape(Circle()) - } - .buttonStyle(.plain) - case .counter: - Button { logCount() } label: { - Image(systemName: "plus") - .font(.title3) - .foregroundStyle(.white) - .frame(width: 44, height: 44) - .background(primaryColor) - .clipShape(Circle()) - .scaleEffect(counterPulse ? 1.3 : 1.0) - } - .buttonStyle(.plain) - } - } - - private func toggleTimer() { - if engine.isRunning(task) { - engine.stopTimer(for: task, context: context) - } else { - engine.startTimer(for: task, context: context) - } - } - - private func logCount() { - engine.incrementCounter(for: task, context: context) - withAnimation(.spring(duration: 0.15, bounce: 0.6)) { counterPulse = true } - Task { - try? await Task.sleep(for: .milliseconds(350)) - withAnimation(.spring(duration: 0.2)) { counterPulse = false } - } - } -} - -// MARK: – Layout-edit mode entry - -private struct LayoutEditEntryBanner: View { - var body: some View { - HStack(spacing: 10) { - Image(systemName: "square.grid.2x2") - .foregroundStyle(Color.tfAccent) - Text(String(localized: "main.layout_edit.placeholder")) - .font(.footnote) - .foregroundStyle(Color.tfMuted) - Spacer() - Image(systemName: "chevron.right") - .font(.footnote.weight(.semibold)) - .foregroundStyle(Color.tfMuted) - } - .padding() - .background(Color.tfSurface) - .clipShape(RoundedRectangle(cornerRadius: 10)) - .overlay( - RoundedRectangle(cornerRadius: 10) - .stroke(Color.tfSeparator, lineWidth: 1) - ) - } -} - -#Preview { - MainTabView() - .modelContainer(for: [TaskItem.self, TagEntity.self, TrackingRecord.self], inMemory: true) - .environment(AppPreferences()) -} diff --git a/myApp/TallyFlow/IOS/Features/Settings/SettingsTabView.swift b/myApp/TallyFlow/IOS/Features/Settings/SettingsTabView.swift deleted file mode 100644 index 982e865..0000000 --- a/myApp/TallyFlow/IOS/Features/Settings/SettingsTabView.swift +++ /dev/null @@ -1,62 +0,0 @@ -import SwiftUI - -struct SettingsTabView: View { - @Environment(AppPreferences.self) private var preferences - - var body: some View { - @Bindable var prefs = preferences - NavigationStack { - Form { - Section(String(localized: "settings.section.appearance")) { - Picker(String(localized: "settings.theme"), selection: $prefs.themeMode) { - ForEach(ThemeMode.allCases) { mode in - Text(mode.localizedName).tag(mode) - } - } - } - - Section(String(localized: "settings.section.language")) { - Picker(String(localized: "settings.language"), selection: $prefs.languageCode) { - Text("한국어").tag("ko") - Text("English").tag("en") - } - } - - Section(String(localized: "settings.section.calendar")) { - Picker(String(localized: "settings.week_start"), selection: $prefs.weekStartDay) { - Text(String(localized: "weekday.sunday")).tag(1) - Text(String(localized: "weekday.monday")).tag(2) - Text(String(localized: "weekday.saturday")).tag(7) - } - Stepper(value: $prefs.dayStartHour, in: 0...23) { - HStack { - Text(String(localized: "settings.day_start")) - Spacer() - Text(String(format: "%02d:00", prefs.dayStartHour)) - .foregroundStyle(Color.tfMuted) - } - } - } - - Section { - NavigationLink { - // TODO: premium paywall in milestone 3+ - Text(String(localized: "premium.coming_soon")) - .foregroundStyle(Color.tfMuted) - } label: { - Label(String(localized: "settings.premium"), systemImage: "star.fill") - .foregroundStyle(Color.tfAccent) - } - } - } - .scrollContentBackground(.hidden) - .background(Color.tfBackground) - .navigationTitle(String(localized: "tab.settings")) - } - } -} - -#Preview { - SettingsTabView() - .environment(AppPreferences()) -} diff --git a/myApp/TallyFlow/IOS/Features/Stats/StatsTabView.swift b/myApp/TallyFlow/IOS/Features/Stats/StatsTabView.swift deleted file mode 100644 index 7e15450..0000000 --- a/myApp/TallyFlow/IOS/Features/Stats/StatsTabView.swift +++ /dev/null @@ -1,19 +0,0 @@ -import SwiftUI - -struct StatsTabView: View { - var body: some View { - NavigationStack { - EmptyStateView( - icon: "chart.bar", - message: String(localized: "stats.empty") - ) - .background(Color.tfBackground) - .navigationTitle(String(localized: "tab.stats")) - } - } -} - -#Preview { - StatsTabView() - .environment(AppPreferences()) -} diff --git a/myApp/TallyFlow/IOS/Features/Tags/TagsTabView.swift b/myApp/TallyFlow/IOS/Features/Tags/TagsTabView.swift deleted file mode 100644 index 8b86719..0000000 --- a/myApp/TallyFlow/IOS/Features/Tags/TagsTabView.swift +++ /dev/null @@ -1,211 +0,0 @@ -import SwiftUI -import SwiftData - -struct TagsTabView: View { - @Environment(\.modelContext) private var context - @Query(sort: \TagEntity.sortOrder) private var allTags: [TagEntity] - @State private var showAddSheet = false - @State private var editTarget: TagEntity? - - private var superTags: [TagEntity] { allTags.filter { $0.parent == nil } } - - var body: some View { - NavigationStack { - Group { - if superTags.isEmpty { - EmptyStateView( - icon: "tag", - message: String(localized: "tags.empty") - ) - } else { - List { - ForEach(superTags) { tag in - TagRowView(tag: tag, onEdit: { editTarget = $0 }) - .swipeActions(edge: .trailing, allowsFullSwipe: false) { - Button(role: .destructive) { - context.delete(tag) - } label: { - Label("삭제", systemImage: "trash") - } - Button { - editTarget = tag - } label: { - Label("편집", systemImage: "pencil") - } - .tint(.blue) - } - } - } - .scrollContentBackground(.hidden) - } - } - .background(Color.tfBackground) - .navigationTitle(String(localized: "tab.tags")) - .toolbar { - ToolbarItem(placement: .primaryAction) { - Button { showAddSheet = true } label: { - Image(systemName: "plus") - } - .tint(Color.tfPrimary) - } - } - .sheet(isPresented: $showAddSheet) { - TagFormView(superTags: superTags) - } - .sheet(item: $editTarget) { tag in - TagFormView(existing: tag, superTags: superTags) - } - } - } -} - -// MARK: – Row - -private struct TagRowView: View { - @Environment(\.modelContext) private var context - let tag: TagEntity - let onEdit: (TagEntity) -> Void - - var body: some View { - VStack(alignment: .leading, spacing: 6) { - HStack(spacing: 8) { - Circle() - .fill(Color(hex: tag.colorHex)) - .frame(width: 10, height: 10) - Text(tag.name) - .foregroundStyle(Color.tfOnBackground) - Spacer() - if !tag.subTags.isEmpty { - Text(String(format: String(localized: "tags.subtag_count"), tag.subTags.count)) - .font(.caption) - .foregroundStyle(Color.tfMuted) - } - } - ForEach(tag.subTags) { sub in - HStack(spacing: 8) { - Spacer().frame(width: 14) - Circle() - .fill(Color(hex: sub.colorHex)) - .frame(width: 8, height: 8) - Text(sub.name) - .font(.subheadline) - .foregroundStyle(Color.tfMuted) - Spacer() - } - .contentShape(Rectangle()) - .contextMenu { - Button("편집") { onEdit(sub) } - Button("삭제", role: .destructive) { context.delete(sub) } - } - } - } - .padding(.vertical, 4) - } -} - -// MARK: – Form - -struct TagFormView: View { - @Environment(\.modelContext) private var context - @Environment(\.dismiss) private var dismiss - - var existing: TagEntity? = nil - var superTags: [TagEntity] - - @State private var name = "" - @State private var selectedColor = "#52A878" - @State private var selectedParent: TagEntity? = nil - - private let colorPresets = [ - "#52A878", "#1E5C40", "#4CBF80", - "#C99728", "#E8B84B", "#D64545", - "#4A90D9", "#9B59B6", "#E67E22", - "#1ABC9C" - ] - - private var isEditing: Bool { existing != nil } - private var isSaveDisabled: Bool { name.trimmingCharacters(in: .whitespaces).isEmpty } - private var availableParents: [TagEntity] { - superTags.filter { $0.id != existing?.id } - } - - var body: some View { - NavigationStack { - Form { - Section("이름") { - TextField("태그 이름", text: $name) - } - - Section("색상") { - LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 5), spacing: 12) { - ForEach(colorPresets, id: \.self) { hex in - Circle() - .fill(Color(hex: hex)) - .frame(width: 36, height: 36) - .overlay { - if hex == selectedColor { - Image(systemName: "checkmark") - .font(.system(size: 14, weight: .bold)) - .foregroundStyle(.white) - } - } - .onTapGesture { selectedColor = hex } - } - } - .padding(.vertical, 4) - } - - Section("상위 태그") { - Picker("상위 태그", selection: $selectedParent) { - Text("없음 (슈퍼 태그)").tag(nil as TagEntity?) - ForEach(availableParents) { tag in - Text(tag.name).tag(tag as TagEntity?) - } - } - .pickerStyle(.menu) - } - } - .navigationTitle(isEditing ? "태그 편집" : "태그 추가") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button("취소") { dismiss() } - } - ToolbarItem(placement: .confirmationAction) { - Button("저장") { save() } - .disabled(isSaveDisabled) - } - } - .onAppear { loadExisting() } - } - } - - private func loadExisting() { - guard let tag = existing else { return } - name = tag.name - selectedColor = tag.colorHex - selectedParent = tag.parent - } - - private func save() { - let trimmed = name.trimmingCharacters(in: .whitespaces) - guard !trimmed.isEmpty else { return } - - if let tag = existing { - tag.name = trimmed - tag.colorHex = selectedColor - tag.parent = selectedParent - } else { - let newTag = TagEntity(name: trimmed, colorHex: selectedColor) - newTag.parent = selectedParent - context.insert(newTag) - } - dismiss() - } -} - -#Preview { - TagsTabView() - .modelContainer(for: [TagEntity.self, TaskItem.self], inMemory: true) - .environment(AppPreferences()) -} diff --git a/myApp/TallyFlow/IOS/Features/Tasks/TasksTabView.swift b/myApp/TallyFlow/IOS/Features/Tasks/TasksTabView.swift deleted file mode 100644 index 7418cd0..0000000 --- a/myApp/TallyFlow/IOS/Features/Tasks/TasksTabView.swift +++ /dev/null @@ -1,260 +0,0 @@ -import SwiftUI -import SwiftData - -struct TasksTabView: View { - @Environment(\.modelContext) private var context - @Query(sort: \TaskItem.sortOrder) private var tasks: [TaskItem] - @State private var showAddSheet = false - @State private var editTarget: TaskItem? - - private var activeTasks: [TaskItem] { tasks.filter { !$0.isArchived } } - - var body: some View { - NavigationStack { - Group { - if activeTasks.isEmpty { - EmptyStateView( - icon: "checkmark.circle", - message: String(localized: "tasks.empty") - ) - } else { - List { - ForEach(activeTasks) { task in - TaskRowView(task: task) - .swipeActions(edge: .trailing, allowsFullSwipe: false) { - Button(role: .destructive) { - context.delete(task) - } label: { - Label("삭제", systemImage: "trash") - } - Button { - editTarget = task - } label: { - Label("편집", systemImage: "pencil") - } - .tint(.blue) - } - } - } - .scrollContentBackground(.hidden) - } - } - .background(Color.tfBackground) - .navigationTitle(String(localized: "tab.tasks")) - .toolbar { - ToolbarItem(placement: .primaryAction) { - Button { showAddSheet = true } label: { - Image(systemName: "plus") - } - .tint(Color.tfPrimary) - } - } - .sheet(isPresented: $showAddSheet) { - TaskFormView() - } - .sheet(item: $editTarget) { task in - TaskFormView(existing: task) - } - } - } -} - -// MARK: – Row - -private struct TaskRowView: View { - let task: TaskItem - - private var primaryColor: Color { - task.tags.first.map { Color(hex: $0.colorHex) } ?? Color.tfPrimary - } - - var body: some View { - HStack(spacing: 12) { - Image(systemName: task.icon) - .foregroundStyle(primaryColor) - .frame(width: 28) - VStack(alignment: .leading, spacing: 2) { - Text(task.name) - .foregroundStyle(Color.tfOnBackground) - HStack(spacing: 4) { - Text(task.taskType == .timer ? "타이머" : "카운터") - .font(.caption) - .foregroundStyle(Color.tfMuted) - if let firstTag = task.tags.first { - Text("·") - .font(.caption) - .foregroundStyle(Color.tfMuted) - Text(firstTag.name) - .font(.caption) - .foregroundStyle(Color(hex: firstTag.colorHex)) - } - } - } - Spacer() - Image(systemName: task.taskType == .timer ? "timer" : "number") - .foregroundStyle(primaryColor) - } - .padding(.vertical, 4) - } -} - -// MARK: – Form - -struct TaskFormView: View { - @Environment(\.modelContext) private var context - @Environment(\.dismiss) private var dismiss - @Query(sort: \TagEntity.sortOrder) private var allTags: [TagEntity] - - var existing: TaskItem? = nil - - @State private var name = "" - @State private var icon = "star" - @State private var taskType: TaskType = .timer - @State private var selectedTagIDs: Set = [] - - private let iconPresets = [ - "star", "heart", "bolt", "flame", - "book.fill", "pencil", "laptopcomputer", "dumbbell.fill", - "music.note", "camera.fill", "gamecontroller.fill", "fork.knife", - "figure.walk", "bicycle", "car.fill", "airplane", - "moon.stars.fill", "sun.max.fill", "drop.fill", "leaf.fill" - ] - - private var isEditing: Bool { existing != nil } - private var isSaveDisabled: Bool { name.trimmingCharacters(in: .whitespaces).isEmpty } - - var body: some View { - NavigationStack { - Form { - Section("이름") { - TextField("작업 이름", text: $name) - } - - Section("아이콘") { - LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 5), spacing: 12) { - ForEach(iconPresets, id: \.self) { sym in - Image(systemName: sym) - .font(.title2) - .foregroundStyle(sym == icon ? Color.tfPrimary : Color.tfMuted) - .frame(width: 44, height: 44) - .background( - RoundedRectangle(cornerRadius: 8) - .fill(sym == icon ? Color.tfPrimary.opacity(0.15) : Color.clear) - ) - .onTapGesture { icon = sym } - } - } - .padding(.vertical, 4) - } - - Section("유형") { - Picker("유형", selection: $taskType) { - Text("타이머").tag(TaskType.timer) - Text("카운터").tag(TaskType.counter) - } - .pickerStyle(.segmented) - } - - if !allTags.isEmpty { - Section("태그") { - ForEach(allTags) { tag in - TagSelectionRow( - tag: tag, - isSelected: selectedTagIDs.contains(tag.id), - onToggle: { toggleTag(tag) } - ) - } - } - } - } - .navigationTitle(isEditing ? "작업 편집" : "작업 추가") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .cancellationAction) { - Button("취소") { dismiss() } - } - ToolbarItem(placement: .confirmationAction) { - Button("저장") { save() } - .disabled(isSaveDisabled) - } - } - .onAppear { loadExisting() } - } - } - - private func loadExisting() { - guard let task = existing else { return } - name = task.name - icon = task.icon - taskType = task.taskType - selectedTagIDs = Set(task.tags.map { $0.id }) - } - - private func toggleTag(_ tag: TagEntity) { - if selectedTagIDs.contains(tag.id) { - selectedTagIDs.remove(tag.id) - } else { - selectedTagIDs.insert(tag.id) - // Selecting a sub tag implicitly includes its parent super tag - if let parent = tag.parent { - selectedTagIDs.insert(parent.id) - } - } - } - - private func save() { - let trimmed = name.trimmingCharacters(in: .whitespaces) - guard !trimmed.isEmpty else { return } - - let selectedTags = allTags.filter { selectedTagIDs.contains($0.id) } - - if let task = existing { - task.name = trimmed - task.icon = icon - task.taskType = taskType - task.tags = selectedTags - } else { - let newTask = TaskItem(name: trimmed, icon: icon, taskType: taskType) - newTask.tags = selectedTags - context.insert(newTask) - } - dismiss() - } -} - -private struct TagSelectionRow: View { - let tag: TagEntity - let isSelected: Bool - let onToggle: () -> Void - - var body: some View { - HStack(spacing: 8) { - if tag.isSubTag { - Spacer().frame(width: 14) - } - Circle() - .fill(Color(hex: tag.colorHex)) - .frame(width: 10, height: 10) - Text(tag.name) - .foregroundStyle(Color.tfOnBackground) - if tag.isSubTag, let parent = tag.parent { - Text("(\(parent.name))") - .font(.caption) - .foregroundStyle(Color.tfMuted) - } - Spacer() - if isSelected { - Image(systemName: "checkmark") - .foregroundStyle(Color.tfPrimary) - } - } - .contentShape(Rectangle()) - .onTapGesture { onToggle() } - } -} - -#Preview { - TasksTabView() - .modelContainer(for: [TaskItem.self, TagEntity.self, TrackingRecord.self], inMemory: true) - .environment(AppPreferences()) -} diff --git a/myApp/TallyFlow/IOS/Item.swift b/myApp/TallyFlow/IOS/Item.swift deleted file mode 100644 index c468542..0000000 --- a/myApp/TallyFlow/IOS/Item.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// Item.swift -// TallyFlow -// -// Created by 송예찬 on 6/26/26. -// - -import Foundation -import SwiftData - -@Model -final class Item { - var timestamp: Date - - init(timestamp: Date) { - self.timestamp = timestamp - } -} diff --git a/myApp/TallyFlow/IOS/Localizable.xcstrings b/myApp/TallyFlow/IOS/Localizable.xcstrings deleted file mode 100644 index 42f1b95..0000000 --- a/myApp/TallyFlow/IOS/Localizable.xcstrings +++ /dev/null @@ -1,870 +0,0 @@ -{ - "sourceLanguage" : "ko", - "strings" : { - "" : { - - }, - "—" : { - "comment" : "A separator between the summary card and the task action section.", - "isCommentAutoGenerated" : true - }, - "·" : { - "comment" : "A period used to separate two items.", - "isCommentAutoGenerated" : true - }, - "(%@)" : { - "comment" : "A tag that shows the name of the parent tag in parentheses.", - "isCommentAutoGenerated" : true - }, - "%@: %@" : { - "comment" : "A stepper that lets the user select a minimum value for a goal. The value is displayed in a formatted time string. The argument is the string “goal.form.min”.", - "isCommentAutoGenerated" : true, - "localizations" : { - "ko" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@: %2$@" - } - } - } - }, - "%@: %lld" : { - "comment" : "A label displaying the minimum value for a goal. The argument is the string “goal.form.min”.", - "isCommentAutoGenerated" : true, - "localizations" : { - "ko" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@: %2$lld" - } - } - } - }, - "action.cancel" : { - "comment" : "The label of a button that cancels an action.", - "isCommentAutoGenerated" : true - }, - "action.save" : { - "comment" : "The label of a button that saves the goal.", - "isCommentAutoGenerated" : true - }, - "English" : { - "comment" : "A language option in the settings.", - "isCommentAutoGenerated" : true - }, - "goal.active" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Active" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "활성" - } - } - } - }, - "goal.add.title" : { - "comment" : "The title of the add goal sheet.", - "isCommentAutoGenerated" : true - }, - "goal.form.has_max" : { - "comment" : "A toggle that allows users to specify a maximum value for a goal.", - "isCommentAutoGenerated" : true - }, - "goal.form.max" : { - "comment" : "A stepper that lets the user select a maximum value for a goal. The value is displayed in a formatted time string.", - "isCommentAutoGenerated" : true - }, - "goal.form.min" : { - "comment" : "A label displaying the minimum value of a goal. The value is formatted as a time.", - "isCommentAutoGenerated" : true - }, - "goal.form.none" : { - "comment" : "A placeholder for a picker that allows the user to select a task.", - "isCommentAutoGenerated" : true - }, - "goal.form.section.period" : { - "comment" : "A section in the goal form where the user can select the period for the goal.", - "isCommentAutoGenerated" : true - }, - "goal.form.section.scope" : { - "comment" : "A section in the goal form that allows the user to select a task and a tag.", - "isCommentAutoGenerated" : true - }, - "goal.form.section.target" : { - "comment" : "A section in the goal form that allows the user to set the target value.", - "isCommentAutoGenerated" : true - }, - "goal.form.section.title" : { - "comment" : "A section in the goal form.", - "isCommentAutoGenerated" : true - }, - "goal.form.tag" : { - "comment" : "A label for the tag picker in the goal form.", - "isCommentAutoGenerated" : true - }, - "goal.form.task" : { - "comment" : "A label for the task picker in the goal form.", - "isCommentAutoGenerated" : true - }, - "goal.form.title.placeholder" : { - "comment" : "A placeholder text for the goal's title.", - "isCommentAutoGenerated" : true - }, - "goal.inactive" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Inactive" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "비활성" - } - } - } - }, - "goal.period.daily" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Daily" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "매일" - } - } - } - }, - "goal.period.monthly" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Monthly" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "매월" - } - } - } - }, - "goal.period.weekly" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Weekly" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "매주" - } - } - } - }, - "goal.unit.count" : { - "comment" : "A unit of measurement for a goal.", - "isCommentAutoGenerated" : true - }, - "goal.unit.duration" : { - "comment" : "A unit of time for a goal.", - "isCommentAutoGenerated" : true - }, - "goals.empty" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No goals yet" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "목표가 없습니다" - } - } - } - }, - "main.actions.log_count" : { - "extractionState" : "stale", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Log Count" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "횟수 기록" - } - } - } - }, - "main.actions.start_timer" : { - "extractionState" : "stale", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Start Timer" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "타이머 시작" - } - } - } - }, - "main.actions.title" : { - "extractionState" : "stale", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Quick Actions" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "빠른 실행" - } - } - } - }, - "main.layout_edit.placeholder" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Edit Layout (coming soon)" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "레이아웃 편집 (준비 중)" - } - } - } - }, - "main.period.month" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This Month" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "이번 달" - } - } - } - }, - "main.period.today" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Today" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "오늘" - } - } - } - }, - "main.period.week" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "This Week" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "이번 주" - } - } - } - }, - "premium.coming_soon" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Coming Soon" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "준비 중입니다" - } - } - } - }, - "settings.day_start" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Day Start Time" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "하루 시작 시간" - } - } - } - }, - "settings.language" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Language" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "언어" - } - } - } - }, - "settings.premium" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Premium" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "프리미엄" - } - } - } - }, - "settings.section.appearance" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Appearance" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "외관" - } - } - } - }, - "settings.section.calendar" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Calendar" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "캘린더" - } - } - } - }, - "settings.section.language" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Language" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "언어" - } - } - } - }, - "settings.theme" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Theme" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "테마" - } - } - } - }, - "settings.week_start" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Week Start Day" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "주 시작일" - } - } - } - }, - "splash.tagline" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Track. Grow." - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "기록하고, 성장하세요" - } - } - } - }, - "stats.empty" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No statistics yet" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "아직 통계가 없습니다" - } - } - } - }, - "tab.goals" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Goals" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "목표" - } - } - } - }, - "tab.main" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Main" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "메인" - } - } - } - }, - "tab.settings" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Settings" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "설정" - } - } - } - }, - "tab.stats" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stats" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "통계" - } - } - } - }, - "tab.tags" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tags" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "태그" - } - } - } - }, - "tab.tasks" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tasks" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "할일" - } - } - } - }, - "tags.empty" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No tags yet" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "태그가 없습니다" - } - } - } - }, - "tags.subtag_count" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d subtags" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "하위 태그 %d개" - } - } - } - }, - "TallyFlow" : { - "comment" : "The name of the app.", - "isCommentAutoGenerated" : true - }, - "task.type.count" : { - "extractionState" : "stale", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Count" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "횟수" - } - } - } - }, - "task.type.counter" : { - "comment" : "A label for a task with a counter.", - "isCommentAutoGenerated" : true - }, - "task.type.time" : { - "extractionState" : "stale", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Time" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "시간" - } - } - } - }, - "task.type.timer" : { - "comment" : "A label for a timer task.", - "isCommentAutoGenerated" : true - }, - "tasks.empty" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No tasks yet" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "작업이 없습니다" - } - } - } - }, - "theme.dark" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dark" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "다크" - } - } - } - }, - "theme.light" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Light" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "라이트" - } - } - } - }, - "theme.system" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "System" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "시스템" - } - } - } - }, - "weekday.monday" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Monday" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "월요일" - } - } - } - }, - "weekday.saturday" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Saturday" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "토요일" - } - } - } - }, - "weekday.sunday" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sunday" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "일요일" - } - } - } - }, - "삭제" : { - "comment" : "A button that deletes a tag.", - "isCommentAutoGenerated" : true - }, - "상위 태그" : { - "comment" : "A section for selecting a parent tag.", - "isCommentAutoGenerated" : true - }, - "색상" : { - "comment" : "A section for selecting a color for a tag.", - "isCommentAutoGenerated" : true - }, - "아이콘" : { - "comment" : "A section for selecting an icon for the task.", - "isCommentAutoGenerated" : true - }, - "없음 (슈퍼 태그)" : { - "comment" : "A placeholder for a tag that has no parent.", - "isCommentAutoGenerated" : true - }, - "유형" : { - "comment" : "A section for selecting the type of task.", - "isCommentAutoGenerated" : true - }, - "이름" : { - "comment" : "A label for the tag's name.", - "isCommentAutoGenerated" : true - }, - "작업" : { - "comment" : "A heading for a list of tasks.", - "isCommentAutoGenerated" : true - }, - "작업 이름" : { - "comment" : "A label for the task name field.", - "isCommentAutoGenerated" : true - }, - "작업 추가" : { - "comment" : "The title of the task creation screen.", - "isCommentAutoGenerated" : true - }, - "작업 편집" : { - "comment" : "The title of the screen for editing a task.", - "isCommentAutoGenerated" : true - }, - "저장" : { - "comment" : "A button that saves the current tag.", - "isCommentAutoGenerated" : true - }, - "취소" : { - "comment" : "A button that cancels the current action.", - "isCommentAutoGenerated" : true - }, - "카운터" : { - "comment" : "A label describing a task type", - "isCommentAutoGenerated" : true - }, - "타이머" : { - "comment" : "A label for a task type that is a timer.", - "isCommentAutoGenerated" : true - }, - "태그" : { - "comment" : "A section for selecting tags for a task.", - "isCommentAutoGenerated" : true - }, - "태그 이름" : { - "comment" : "A label for the name of a tag.", - "isCommentAutoGenerated" : true - }, - "태그 추가" : { - "comment" : "A title for a view that lets the user add a tag.", - "isCommentAutoGenerated" : true - }, - "태그 편집" : { - "comment" : "The title of a screen for editing a tag.", - "isCommentAutoGenerated" : true - }, - "편집" : { - "comment" : "A button that opens a sheet for editing a tag.", - "isCommentAutoGenerated" : true - }, - "한국어" : { - "comment" : "Korean for \"한국어\".", - "isCommentAutoGenerated" : true - } - }, - "version" : "1.1" -} \ No newline at end of file diff --git a/myApp/TallyFlow/IOS/Settings/AppPreferences.swift b/myApp/TallyFlow/IOS/Settings/AppPreferences.swift deleted file mode 100644 index 291ded0..0000000 --- a/myApp/TallyFlow/IOS/Settings/AppPreferences.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation -import Observation - -@Observable -final class AppPreferences { - // Calendar.weekday constants: 1 = Sunday, 2 = Monday … 7 = Saturday - var themeMode: ThemeMode = ThemeMode(rawValue: UserDefaults.standard.string(forKey: "pref.themeMode") ?? "system") ?? .system { - didSet { UserDefaults.standard.set(themeMode.rawValue, forKey: "pref.themeMode") } - } - - var languageCode: String = UserDefaults.standard.string(forKey: "pref.languageCode") ?? "ko" { - didSet { UserDefaults.standard.set(languageCode, forKey: "pref.languageCode") } - } - - var weekStartDay: Int = (UserDefaults.standard.object(forKey: "pref.weekStartDay") as? Int) ?? 2 { - didSet { UserDefaults.standard.set(weekStartDay, forKey: "pref.weekStartDay") } - } - - var dayStartHour: Int = (UserDefaults.standard.object(forKey: "pref.dayStartHour") as? Int) ?? 0 { - didSet { UserDefaults.standard.set(dayStartHour, forKey: "pref.dayStartHour") } - } - - var dayStartMinute: Int = (UserDefaults.standard.object(forKey: "pref.dayStartMinute") as? Int) ?? 0 { - didSet { UserDefaults.standard.set(dayStartMinute, forKey: "pref.dayStartMinute") } - } -} diff --git a/myApp/TallyFlow/IOS/TallyFlowApp.swift b/myApp/TallyFlow/IOS/TallyFlowApp.swift deleted file mode 100644 index 023144e..0000000 --- a/myApp/TallyFlow/IOS/TallyFlowApp.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// TallyFlowApp.swift -// TallyFlow -// -// Created by 송예찬 on 6/26/26. -// - -import SwiftUI -import SwiftData - -@main -struct TallyFlowApp: App { - private let preferences = AppPreferences() - - var sharedModelContainer: ModelContainer = { - let schema = Schema([ - TaskItem.self, - TagEntity.self, - GoalEntity.self, - TrackingRecord.self, - ]) - let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) - do { - return try ModelContainer(for: schema, configurations: [modelConfiguration]) - } catch { - fatalError("Could not create ModelContainer: \(error)") - } - }() - - var body: some Scene { - WindowGroup { - ContentView() - .environment(preferences) - } - .modelContainer(sharedModelContainer) - } -} diff --git a/myApp/TallyFlow/IOS/Theme/AppColors.swift b/myApp/TallyFlow/IOS/Theme/AppColors.swift deleted file mode 100644 index e5708d6..0000000 --- a/myApp/TallyFlow/IOS/Theme/AppColors.swift +++ /dev/null @@ -1,43 +0,0 @@ -import SwiftUI -import UIKit - -// MARK: – Semantic adaptive color tokens - -extension Color { - // Brand Greens - static let tfPrimary = Color(light: Color(hex: "#1E5C40"), dark: Color(hex: "#4CBF80")) - static let tfSecondary = Color(light: Color(hex: "#52A878"), dark: Color(hex: "#2D7050")) - - // Brand Accent – Amber Gold - static let tfAccent = Color(light: Color(hex: "#C99728"), dark: Color(hex: "#E8B84B")) - - // Surfaces - static let tfBackground = Color(light: Color(hex: "#F5FAF7"), dark: Color(hex: "#0C100D")) - static let tfSurface = Color(light: Color(hex: "#FFFFFF"), dark: Color(hex: "#141D16")) - - // Text / Content - static let tfOnBackground = Color(light: Color(hex: "#112318"), dark: Color(hex: "#E8F0EB")) - static let tfMuted = Color(light: Color(hex: "#5E7A69"), dark: Color(hex: "#5A7B66")) - static let tfSeparator = Color(light: Color(hex: "#DCE8E1"), dark: Color(hex: "#1E2B22")) -} - -// MARK: – Color helpers - -extension Color { - init(light: Color, dark: Color) { - self.init(uiColor: UIColor { traits in - traits.userInterfaceStyle == .dark ? UIColor(dark) : UIColor(light) - }) - } - - init(hex: String) { - let cleaned = hex.trimmingCharacters(in: CharacterSet(charactersIn: "#")) - var value: UInt64 = 0 - Scanner(string: cleaned).scanHexInt64(&value) - self.init( - red: Double((value >> 16) & 0xFF) / 255, - green: Double((value >> 8) & 0xFF) / 255, - blue: Double( value & 0xFF) / 255 - ) - } -} diff --git a/myApp/TallyFlow/IOS/Theme/AppTheme.swift b/myApp/TallyFlow/IOS/Theme/AppTheme.swift deleted file mode 100644 index 9555eca..0000000 --- a/myApp/TallyFlow/IOS/Theme/AppTheme.swift +++ /dev/null @@ -1,39 +0,0 @@ -import SwiftUI - -// MARK: – ThemeMode → ColorScheme - -extension ThemeMode { - var colorScheme: ColorScheme? { - switch self { - case .system: return nil - case .light: return .light - case .dark: return .dark - } - } -} - -// MARK: – Shared reusable views - -struct EmptyStateView: View { - let icon: String - let message: String - - var body: some View { - VStack(spacing: 16) { - Image(systemName: icon) - .font(.system(size: 48)) - .foregroundStyle(Color.tfMuted) - Text(message) - .font(.subheadline) - .foregroundStyle(Color.tfMuted) - .multilineTextAlignment(.center) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .padding() - } -} - -#Preview("EmptyStateView") { - EmptyStateView(icon: "tag", message: "태그가 없습니다") - .background(Color.tfBackground) -} diff --git a/myApp/TallyFlow/IOS/Theme/ThemeMode.swift b/myApp/TallyFlow/IOS/Theme/ThemeMode.swift deleted file mode 100644 index e43f709..0000000 --- a/myApp/TallyFlow/IOS/Theme/ThemeMode.swift +++ /dev/null @@ -1,17 +0,0 @@ -import Foundation - -enum ThemeMode: String, CaseIterable, Identifiable { - case system - case light - case dark - - var id: String { rawValue } - - var localizedName: String { - switch self { - case .system: return String(localized: "theme.system") - case .light: return String(localized: "theme.light") - case .dark: return String(localized: "theme.dark") - } - } -} diff --git a/myApp/TallyFlow/TallyFlow.xcodeproj/project.pbxproj b/myApp/TallyFlow/TallyFlow.xcodeproj/project.pbxproj deleted file mode 100644 index e7cc968..0000000 --- a/myApp/TallyFlow/TallyFlow.xcodeproj/project.pbxproj +++ /dev/null @@ -1,333 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 77; - objects = { - -/* Begin PBXFileReference section */ - 72C598532FED9ACF00D61864 /* TallyFlow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TallyFlow.app; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFileSystemSynchronizedRootGroup section */ - 72C598552FED9ACF00D61864 /* IOS */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = IOS; - sourceTree = ""; - }; -/* End PBXFileSystemSynchronizedRootGroup section */ - -/* Begin PBXFrameworksBuildPhase section */ - 72C598502FED9ACF00D61864 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 72C5984A2FED9ACF00D61864 = { - isa = PBXGroup; - children = ( - 72C598552FED9ACF00D61864 /* IOS */, - 72C598542FED9ACF00D61864 /* Products */, - ); - sourceTree = ""; - }; - 72C598542FED9ACF00D61864 /* Products */ = { - isa = PBXGroup; - children = ( - 72C598532FED9ACF00D61864 /* TallyFlow.app */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 72C598522FED9ACF00D61864 /* TallyFlow */ = { - isa = PBXNativeTarget; - buildConfigurationList = 72C598602FED9AD100D61864 /* Build configuration list for PBXNativeTarget "TallyFlow" */; - buildPhases = ( - 72C5984F2FED9ACF00D61864 /* Sources */, - 72C598502FED9ACF00D61864 /* Frameworks */, - 72C598512FED9ACF00D61864 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - fileSystemSynchronizedGroups = ( - 72C598552FED9ACF00D61864 /* IOS */, - ); - name = TallyFlow; - packageProductDependencies = ( - ); - productName = TallyFlow; - productReference = 72C598532FED9ACF00D61864 /* TallyFlow.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 72C5984B2FED9ACF00D61864 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 2650; - LastUpgradeCheck = 2650; - TargetAttributes = { - 72C598522FED9ACF00D61864 = { - CreatedOnToolsVersion = 26.5; - }; - }; - }; - buildConfigurationList = 72C5984E2FED9ACF00D61864 /* Build configuration list for PBXProject "TallyFlow" */; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 72C5984A2FED9ACF00D61864; - minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 77; - productRefGroup = 72C598542FED9ACF00D61864 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 72C598522FED9ACF00D61864 /* TallyFlow */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 72C598512FED9ACF00D61864 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 72C5984F2FED9ACF00D61864 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - 72C5985E2FED9AD100D61864 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = 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; - }; - 72C5985F2FED9AD100D61864 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = 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; - }; - 72C598612FED9AD100D61864 /* 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_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.TallyFlow; - 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; - }; - 72C598622FED9AD100D61864 /* 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_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.TallyFlow; - 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; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 72C5984E2FED9ACF00D61864 /* Build configuration list for PBXProject "TallyFlow" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 72C5985E2FED9AD100D61864 /* Debug */, - 72C5985F2FED9AD100D61864 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 72C598602FED9AD100D61864 /* Build configuration list for PBXNativeTarget "TallyFlow" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 72C598612FED9AD100D61864 /* Debug */, - 72C598622FED9AD100D61864 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 72C5984B2FED9ACF00D61864 /* Project object */; -} diff --git a/myApp/TallyFlow/TallyFlow.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/myApp/TallyFlow/TallyFlow.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/myApp/TallyFlow/TallyFlow.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/myApp/TallyFlow/TallyFlow.xcodeproj/project.xcworkspace/xcuserdata/ceuak.xcuserdatad/UserInterfaceState.xcuserstate b/myApp/TallyFlow/TallyFlow.xcodeproj/project.xcworkspace/xcuserdata/ceuak.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 1117e77..0000000 Binary files a/myApp/TallyFlow/TallyFlow.xcodeproj/project.xcworkspace/xcuserdata/ceuak.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/myApp/TallyFlow/TallyFlow.xcodeproj/xcuserdata/ceuak.xcuserdatad/xcschemes/xcschememanagement.plist b/myApp/TallyFlow/TallyFlow.xcodeproj/xcuserdata/ceuak.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 4d7608d..0000000 --- a/myApp/TallyFlow/TallyFlow.xcodeproj/xcuserdata/ceuak.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - SchemeUserState - - TallyFlow.xcscheme_^#shared#^_ - - orderHint - 0 - - - -