From fe99f67f81308ab995c918d004c2960a21c587fc Mon Sep 17 00:00:00 2001 From: Trey t Date: Wed, 5 Nov 2025 19:21:46 -0600 Subject: [PATCH] wip --- iosApp/MyCrib/AppIntent.swift | 18 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 35 ++ iosApp/MyCrib/Assets.xcassets/Contents.json | 6 + .../WidgetBackground.colorset/Contents.json | 11 + iosApp/MyCrib/Info.plist | 11 + iosApp/MyCrib/MyCrib.swift | 455 ++++++++++++++++++ iosApp/MyCrib/MyCribBundle.swift | 18 + iosApp/MyCrib/MyCribControl.swift | 77 +++ iosApp/MyCrib/MyCribLiveActivity.swift | 80 +++ iosApp/MyCribExtension.entitlements | 10 + iosApp/TaskWidgetExample.swift | 251 ++++++++++ iosApp/iosApp.xcodeproj/project.pbxproj | 208 +++++++- iosApp/iosApp/DELETE_THESE_FILES.txt | 28 -- iosApp/iosApp/LookupsManager.swift | 2 +- iosApp/iosApp/SETUP_INSTRUCTIONS.md | 48 -- iosApp/iosApp/StateFlowExtensions.swift | 4 + iosApp/iosApp/iosApp.entitlements | 10 + 18 files changed, 1203 insertions(+), 80 deletions(-) create mode 100644 iosApp/MyCrib/AppIntent.swift create mode 100644 iosApp/MyCrib/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 iosApp/MyCrib/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 iosApp/MyCrib/Assets.xcassets/Contents.json create mode 100644 iosApp/MyCrib/Assets.xcassets/WidgetBackground.colorset/Contents.json create mode 100644 iosApp/MyCrib/Info.plist create mode 100644 iosApp/MyCrib/MyCrib.swift create mode 100644 iosApp/MyCrib/MyCribBundle.swift create mode 100644 iosApp/MyCrib/MyCribControl.swift create mode 100644 iosApp/MyCrib/MyCribLiveActivity.swift create mode 100644 iosApp/MyCribExtension.entitlements create mode 100644 iosApp/TaskWidgetExample.swift delete mode 100644 iosApp/iosApp/DELETE_THESE_FILES.txt delete mode 100644 iosApp/iosApp/SETUP_INSTRUCTIONS.md create mode 100644 iosApp/iosApp/iosApp.entitlements diff --git a/iosApp/MyCrib/AppIntent.swift b/iosApp/MyCrib/AppIntent.swift new file mode 100644 index 0000000..ea59360 --- /dev/null +++ b/iosApp/MyCrib/AppIntent.swift @@ -0,0 +1,18 @@ +// +// AppIntent.swift +// MyCrib +// +// Created by Trey Tartt on 11/5/25. +// + +import WidgetKit +import AppIntents + +struct ConfigurationAppIntent: WidgetConfigurationIntent { + static var title: LocalizedStringResource { "Configuration" } + static var description: IntentDescription { "This is an example widget." } + + // An example configurable parameter. + @Parameter(title: "Favorite Emoji", default: "πŸ˜ƒ") + var favoriteEmoji: String +} diff --git a/iosApp/MyCrib/Assets.xcassets/AccentColor.colorset/Contents.json b/iosApp/MyCrib/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/iosApp/MyCrib/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/MyCrib/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosApp/MyCrib/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..2305880 --- /dev/null +++ b/iosApp/MyCrib/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "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/iosApp/MyCrib/Assets.xcassets/Contents.json b/iosApp/MyCrib/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/iosApp/MyCrib/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/MyCrib/Assets.xcassets/WidgetBackground.colorset/Contents.json b/iosApp/MyCrib/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/iosApp/MyCrib/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/MyCrib/Info.plist b/iosApp/MyCrib/Info.plist new file mode 100644 index 0000000..0f118fb --- /dev/null +++ b/iosApp/MyCrib/Info.plist @@ -0,0 +1,11 @@ + + + + + NSExtension + + NSExtensionPointIdentifier + com.apple.widgetkit-extension + + + diff --git a/iosApp/MyCrib/MyCrib.swift b/iosApp/MyCrib/MyCrib.swift new file mode 100644 index 0000000..2abf840 --- /dev/null +++ b/iosApp/MyCrib/MyCrib.swift @@ -0,0 +1,455 @@ +// +// MyCrib.swift +// MyCrib +// +// Created by Trey Tartt on 11/5/25. +// + +import WidgetKit +import SwiftUI + +class CacheManager { + struct CustomTask: Codable { + let id: Int + let title: String + let description: String? + let priority: String? + let status: String? + let dueDate: String? + let category: String? + + enum CodingKeys: String, CodingKey { + case id, title, description, priority, status, category + case dueDate = "due_date" + } + } + + private let userDefaults = UserDefaults.standard + private static let cacheKey = "cached_tasks" + + static func getData() -> [CustomTask] { + do { + let string = UserDefaults.standard.string(forKey: cacheKey) ?? "[]" + let jsonData = string.data(using: .utf8)! + let customTask = try JSONDecoder().decode([CustomTask].self, from: jsonData) + return customTask + } catch { + print("Error decoding tasks: \(error)") + return [] + } + } + + static func getUpcomingTasks() -> [CustomTask] { + let allTasks = getData() + // Filter for pending/in-progress tasks, sorted by due date + let upcoming = allTasks.filter { task in + let status = task.status?.lowercased() ?? "" + return status == "pending" || status == "in_progress" || status == "in progress" + } + + // Sort by due date (earliest first) + return upcoming.sorted { task1, task2 in + guard let date1 = task1.dueDate, let date2 = task2.dueDate else { + return task1.dueDate != nil + } + return date1 < date2 + } + } +} + +struct Provider: AppIntentTimelineProvider { + func placeholder(in context: Context) -> SimpleEntry { + SimpleEntry( + date: Date(), + configuration: ConfigurationAppIntent(), + upcomingTasks: [] + ) + } + + func snapshot(for configuration: ConfigurationAppIntent, in context: Context) async -> SimpleEntry { + let tasks = CacheManager.getUpcomingTasks() + return SimpleEntry( + date: Date(), + configuration: configuration, + upcomingTasks: tasks + ) + } + + func timeline(for configuration: ConfigurationAppIntent, in context: Context) async -> Timeline { + let tasks = CacheManager.getUpcomingTasks() + + // Update every hour + let currentDate = Date() + let nextUpdate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate)! + let entry = SimpleEntry( + date: currentDate, + configuration: configuration, + upcomingTasks: tasks + ) + + return Timeline(entries: [entry], policy: .after(nextUpdate)) + } +} + +struct SimpleEntry: TimelineEntry { + let date: Date + let configuration: ConfigurationAppIntent + let upcomingTasks: [CacheManager.CustomTask] + + var taskCount: Int { + upcomingTasks.count + } + + var nextTask: CacheManager.CustomTask? { + upcomingTasks.first + } +} + +struct MyCribEntryView : View { + var entry: Provider.Entry + @Environment(\.widgetFamily) var family + + var body: some View { + switch family { + case .systemSmall: + SmallWidgetView(entry: entry) + case .systemMedium: + MediumWidgetView(entry: entry) + default: + SmallWidgetView(entry: entry) + } + } +} + +// MARK: - Small Widget View +struct SmallWidgetView: View { + let entry: SimpleEntry + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + // Header + HStack(spacing: 6) { + Image(systemName: "house.fill") + .font(.system(size: 14, weight: .semibold)) + .foregroundStyle(.blue) + + Text("MyCrib") + .font(.system(size: 14, weight: .bold)) + .foregroundStyle(.primary) + + Spacer() + } + + // Task Count + VStack(alignment: .leading, spacing: 4) { + Text("\(entry.taskCount)") + .font(.system(size: 36, weight: .bold)) + .foregroundStyle(.blue) + + if entry.taskCount > 0 { + Text(entry.taskCount == 1 ? "upcoming task" : "upcoming tasks") + .font(.system(size: 12, weight: .medium)) + .foregroundStyle(.secondary) + } + } + + // Next Task + if let nextTask = entry.nextTask { + VStack(alignment: .leading, spacing: 4) { + Text("NEXT UP") + .font(.system(size: 9, weight: .semibold)) + .foregroundStyle(.secondary) + .tracking(0.5) + + Text(nextTask.title) + .font(.system(size: 12, weight: .semibold)) + .lineLimit(2) + .foregroundStyle(.primary) + + if let dueDate = nextTask.dueDate { + HStack(spacing: 4) { + Image(systemName: "calendar") + .font(.system(size: 9)) + Text(formatDate(dueDate)) + .font(.system(size: 10, weight: .medium)) + } + .foregroundStyle(.orange) + } + } + .padding(.top, 8) + .padding(.horizontal, 10) + .padding(.vertical, 8) + .frame(maxWidth: .infinity, alignment: .leading) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(Color.blue.opacity(0.1)) + ) + } else { + HStack { + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 16)) + .foregroundStyle(.green) + // Text("All caught up!") + // .font(.system(size: 11, weight: .medium)) + // .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 8) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(Color.green.opacity(0.1)) + ) + } + } + .padding(16) + } + + private func formatDate(_ dateString: String) -> String { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + + if let date = formatter.date(from: dateString) { + let now = Date() + let calendar = Calendar.current + + if calendar.isDateInToday(date) { + return "Today" + } else if calendar.isDateInTomorrow(date) { + return "Tomorrow" + } else { + formatter.dateFormat = "MMM d" + return formatter.string(from: date) + } + } + + return dateString + } +} + +// MARK: - Medium Widget View +struct MediumWidgetView: View { + let entry: SimpleEntry + + var body: some View { + HStack(spacing: 16) { + // Left side - Task count + VStack(alignment: .leading, spacing: 4) { + Spacer() + + Text("\(entry.taskCount)") + .font(.system(size: 42, weight: .bold)) + .foregroundStyle(.blue) + + Text(entry.taskCount == 1 ? "upcoming\n task" : "upcoming\ntasks") + .font(.system(size: 11, weight: .medium)) + .foregroundStyle(.secondary) + .lineLimit(2) + + Spacer() + } + .frame(maxWidth: 75) + + Divider() + + // Right side - Next tasks + VStack(alignment: .leading, spacing: 8) { + if let nextTask = entry.nextTask { + Text("NEXT UP") + .font(.system(size: 9, weight: .semibold)) + .foregroundStyle(.secondary) + .tracking(0.5) + } + + if entry.nextTask != nil { + ForEach(Array(entry.upcomingTasks.prefix(3).enumerated()), id: \.element.id) { index, task in + TaskRowView(task: task, index: index) + } + + Spacer() + } else { + Spacer() + + VStack(spacing: 8) { + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 24)) + .foregroundStyle(.green) + Text("All caught up!") + .font(.system(size: 12, weight: .medium)) + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity) + + Spacer() + } + } + .frame(maxWidth: .infinity) + } + .padding(16) + } +} + +// MARK: - Task Row +struct TaskRowView: View { + let task: CacheManager.CustomTask + let index: Int + + var body: some View { + HStack(spacing: 8) { + Circle() + .fill(priorityColor) + .frame(width: 6, height: 6) + + VStack(alignment: .leading, spacing: 2) { + Text(task.title) + .font(.system(size: 11, weight: .semibold)) + .lineLimit(1) + .foregroundStyle(.primary) + + if let dueDate = task.dueDate { + HStack(spacing: 3) { + Image(systemName: "calendar") + .font(.system(size: 8)) + Text(formatDate(dueDate)) + .font(.system(size: 9, weight: .medium)) + } + .foregroundStyle(.secondary) + } + } + + Spacer() + + if let priority = task.priority { + Text(priority.prefix(1).uppercased()) + .font(.system(size: 9, weight: .bold)) + .foregroundStyle(.white) + .frame(width: 16, height: 16) + .background( + Circle() + .fill(priorityColor) + ) + } + } + .padding(.vertical, 4) + } + + private var priorityColor: Color { + switch task.priority?.lowercased() { + case "urgent": return .red + case "high": return .orange + case "medium": return .blue + default: return .gray + } + } + + private func formatDate(_ dateString: String) -> String { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd" + + if let date = formatter.date(from: dateString) { + let now = Date() + let calendar = Calendar.current + + if calendar.isDateInToday(date) { + return "Today" + } else if calendar.isDateInTomorrow(date) { + return "Tomorrow" + } else { + formatter.dateFormat = "MMM d" + return formatter.string(from: date) + } + } + + return dateString + } +} + +struct MyCrib: Widget { + let kind: String = "MyCrib" + + var body: some WidgetConfiguration { + AppIntentConfiguration(kind: kind, intent: ConfigurationAppIntent.self, provider: Provider()) { entry in + MyCribEntryView(entry: entry) + .containerBackground(.fill.tertiary, for: .widget) + } + } +} + +// MARK: - Previews +#Preview(as: .systemSmall) { + MyCrib() +} timeline: { + SimpleEntry( + date: .now, + configuration: ConfigurationAppIntent(), + upcomingTasks: [ + CacheManager.CustomTask( + id: 1, + title: "Fix leaky faucet", + description: "Kitchen sink needs repair", + priority: "high", + status: "pending", + dueDate: "2024-12-15", + category: "plumbing" + ), + CacheManager.CustomTask( + id: 2, + title: "Paint living room", + description: nil, + priority: "medium", + status: "pending", + dueDate: "2024-12-20", + category: "painting" + ) + ] + ) + + SimpleEntry( + date: .now, + configuration: ConfigurationAppIntent(), + upcomingTasks: [] + ) +} + +#Preview(as: .systemMedium) { + MyCrib() +} timeline: { + SimpleEntry( + date: .now, + configuration: ConfigurationAppIntent(), + upcomingTasks: [ + CacheManager.CustomTask( + id: 1, + title: "Fix leaky faucet", + description: "Kitchen sink needs repair", + priority: "high", + status: "pending", + dueDate: "2024-12-15", + category: "plumbing" + ), + CacheManager.CustomTask( + id: 2, + title: "Paint living room", + description: nil, + priority: "medium", + status: "pending", + dueDate: "2024-12-20", + category: "painting" + ), + CacheManager.CustomTask( + id: 3, + title: "Clean gutters", + description: "Remove debris", + priority: "low", + status: "pending", + dueDate: "2024-12-25", + category: "maintenance" + ) + ] + ) + + SimpleEntry( + date: .now, + configuration: ConfigurationAppIntent(), + upcomingTasks: [] + ) +} diff --git a/iosApp/MyCrib/MyCribBundle.swift b/iosApp/MyCrib/MyCribBundle.swift new file mode 100644 index 0000000..a937a4b --- /dev/null +++ b/iosApp/MyCrib/MyCribBundle.swift @@ -0,0 +1,18 @@ +// +// MyCribBundle.swift +// MyCrib +// +// Created by Trey Tartt on 11/5/25. +// + +import WidgetKit +import SwiftUI + +@main +struct MyCribBundle: WidgetBundle { + var body: some Widget { + MyCrib() + MyCribControl() + MyCribLiveActivity() + } +} diff --git a/iosApp/MyCrib/MyCribControl.swift b/iosApp/MyCrib/MyCribControl.swift new file mode 100644 index 0000000..bd8decd --- /dev/null +++ b/iosApp/MyCrib/MyCribControl.swift @@ -0,0 +1,77 @@ +// +// MyCribControl.swift +// MyCrib +// +// Created by Trey Tartt on 11/5/25. +// + +import AppIntents +import SwiftUI +import WidgetKit + +struct MyCribControl: ControlWidget { + static let kind: String = "com.example.mycrib.MyCrib.MyCrib" + + var body: some ControlWidgetConfiguration { + AppIntentControlConfiguration( + kind: Self.kind, + provider: Provider() + ) { value in + ControlWidgetToggle( + "Start Timer", + isOn: value.isRunning, + action: StartTimerIntent(value.name) + ) { isRunning in + Label(isRunning ? "On" : "Off", systemImage: "timer") + } + } + .displayName("Timer") + .description("A an example control that runs a timer.") + } +} + +extension MyCribControl { + struct Value { + var isRunning: Bool + var name: String + } + + struct Provider: AppIntentControlValueProvider { + func previewValue(configuration: TimerConfiguration) -> Value { + MyCribControl.Value(isRunning: false, name: configuration.timerName) + } + + func currentValue(configuration: TimerConfiguration) async throws -> Value { + let isRunning = true // Check if the timer is running + return MyCribControl.Value(isRunning: isRunning, name: configuration.timerName) + } + } +} + +struct TimerConfiguration: ControlConfigurationIntent { + static let title: LocalizedStringResource = "Timer Name Configuration" + + @Parameter(title: "Timer Name", default: "Timer") + var timerName: String +} + +struct StartTimerIntent: SetValueIntent { + static let title: LocalizedStringResource = "Start a timer" + + @Parameter(title: "Timer Name") + var name: String + + @Parameter(title: "Timer is running") + var value: Bool + + init() {} + + init(_ name: String) { + self.name = name + } + + func perform() async throws -> some IntentResult { + // Start the timer… + return .result() + } +} diff --git a/iosApp/MyCrib/MyCribLiveActivity.swift b/iosApp/MyCrib/MyCribLiveActivity.swift new file mode 100644 index 0000000..9f8ffd7 --- /dev/null +++ b/iosApp/MyCrib/MyCribLiveActivity.swift @@ -0,0 +1,80 @@ +// +// MyCribLiveActivity.swift +// MyCrib +// +// Created by Trey Tartt on 11/5/25. +// + +import ActivityKit +import WidgetKit +import SwiftUI + +struct MyCribAttributes: ActivityAttributes { + public struct ContentState: Codable, Hashable { + // Dynamic stateful properties about your activity go here! + var emoji: String + } + + // Fixed non-changing properties about your activity go here! + var name: String +} + +struct MyCribLiveActivity: Widget { + var body: some WidgetConfiguration { + ActivityConfiguration(for: MyCribAttributes.self) { context in + // Lock screen/banner UI goes here + VStack { + Text("Hello \(context.state.emoji)") + } + .activityBackgroundTint(Color.cyan) + .activitySystemActionForegroundColor(Color.black) + + } dynamicIsland: { context in + DynamicIsland { + // Expanded UI goes here. Compose the expanded UI through + // various regions, like leading/trailing/center/bottom + DynamicIslandExpandedRegion(.leading) { + Text("Leading") + } + DynamicIslandExpandedRegion(.trailing) { + Text("Trailing") + } + DynamicIslandExpandedRegion(.bottom) { + Text("Bottom \(context.state.emoji)") + // more content + } + } compactLeading: { + Text("L") + } compactTrailing: { + Text("T \(context.state.emoji)") + } minimal: { + Text(context.state.emoji) + } + .widgetURL(URL(string: "http://www.apple.com")) + .keylineTint(Color.red) + } + } +} + +extension MyCribAttributes { + fileprivate static var preview: MyCribAttributes { + MyCribAttributes(name: "World") + } +} + +extension MyCribAttributes.ContentState { + fileprivate static var smiley: MyCribAttributes.ContentState { + MyCribAttributes.ContentState(emoji: "πŸ˜€") + } + + fileprivate static var starEyes: MyCribAttributes.ContentState { + MyCribAttributes.ContentState(emoji: "🀩") + } +} + +#Preview("Notification", as: .content, using: MyCribAttributes.preview) { + MyCribLiveActivity() +} contentStates: { + MyCribAttributes.ContentState.smiley + MyCribAttributes.ContentState.starEyes +} diff --git a/iosApp/MyCribExtension.entitlements b/iosApp/MyCribExtension.entitlements new file mode 100644 index 0000000..52e1157 --- /dev/null +++ b/iosApp/MyCribExtension.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.tt.mycrib.MyCrib + + + diff --git a/iosApp/TaskWidgetExample.swift b/iosApp/TaskWidgetExample.swift new file mode 100644 index 0000000..ec139bf --- /dev/null +++ b/iosApp/TaskWidgetExample.swift @@ -0,0 +1,251 @@ +import WidgetKit +import SwiftUI +import ComposeApp + +// MARK: - Timeline Entry +struct TaskWidgetEntry: TimelineEntry { + let date: Date + let tasks: [CustomTask] +} + +// MARK: - Provider +struct TaskWidgetProvider: TimelineProvider { + func placeholder(in context: Context) -> TaskWidgetEntry { + TaskWidgetEntry(date: Date(), tasks: []) + } + + func getSnapshot(in context: Context, completion: @escaping (TaskWidgetEntry) -> ()) { + let tasks = LookupsManager.shared.allTasks + let entry = TaskWidgetEntry( + date: Date(), + tasks: Array(tasks.prefix(5)) + ) + completion(entry) + } + + func getTimeline(in context: Context, completion: @escaping (Timeline) -> ()) { + let tasks = LookupsManager.shared.allTasks + let entry = TaskWidgetEntry( + date: Date(), + tasks: Array(tasks.prefix(5)) + ) + + // Refresh every hour + let nextUpdate = Calendar.current.date(byAdding: .hour, value: 1, to: Date())! + let timeline = Timeline(entries: [entry], policy: .after(nextUpdate)) + completion(timeline) + } +} + +// MARK: - Widget View +struct TaskWidgetEntryView: View { + var entry: TaskWidgetEntry + @Environment(\.widgetFamily) var family + + var body: some View { + switch family { + case .systemSmall: + SmallWidgetView(tasks: entry.tasks) + case .systemMedium: + MediumWidgetView(tasks: entry.tasks) + default: + SmallWidgetView(tasks: entry.tasks) + } + } +} + +// MARK: - Small Widget +struct SmallWidgetView: View { + let tasks: [CustomTask] + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + HStack { + Image(systemName: "house.fill") + .foregroundColor(.blue) + Text("Tasks") + .font(.headline) + .fontWeight(.bold) + } + + if tasks.isEmpty { + Spacer() + Text("No tasks") + .font(.caption) + .foregroundColor(.secondary) + Spacer() + } else { + if let firstTask = tasks.first { + VStack(alignment: .leading, spacing: 4) { + Text(firstTask.title) + .font(.subheadline) + .fontWeight(.medium) + .lineLimit(2) + + HStack { + Text(firstTask.priority?.uppercased() ?? "") + .font(.caption2) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(priorityColor(firstTask.priority)) + .foregroundColor(.white) + .cornerRadius(4) + + Text(firstTask.dueDate ?? "") + .font(.caption2) + .foregroundColor(.secondary) + } + } + } + + Spacer() + + Text("\(tasks.count) total tasks") + .font(.caption2) + .foregroundColor(.secondary) + } + } + .padding() + } + + func priorityColor(_ priority: String?) -> Color { + switch priority?.lowercased() { + case "urgent": return .red + case "high": return .orange + case "medium": return .blue + default: return .gray + } + } +} + +// MARK: - Medium Widget +struct MediumWidgetView: View { + let tasks: [CustomTask] + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + HStack { + Image(systemName: "house.fill") + .foregroundColor(.blue) + Text("My Tasks") + .font(.headline) + .fontWeight(.bold) + Spacer() + Text("\(tasks.count)") + .font(.caption) + .foregroundColor(.secondary) + } + + Divider() + + if tasks.isEmpty { + Spacer() + HStack { + Spacer() + VStack { + Image(systemName: "checkmark.circle") + .font(.largeTitle) + .foregroundColor(.green) + Text("All done!") + .font(.caption) + .foregroundColor(.secondary) + } + Spacer() + } + Spacer() + } else { + ForEach(tasks.prefix(3), id: \.id) { task in + HStack(spacing: 8) { + Circle() + .fill(priorityColor(task.priority)) + .frame(width: 8, height: 8) + + VStack(alignment: .leading, spacing: 2) { + Text(task.title) + .font(.caption) + .fontWeight(.medium) + .lineLimit(1) + + Text(task.dueDate ?? "No due date") + .font(.caption2) + .foregroundColor(.secondary) + } + + Spacer() + + Text(task.priority?.uppercased() ?? "") + .font(.caption2) + .foregroundColor(.secondary) + } + } + } + } + .padding() + } + + func priorityColor(_ priority: String?) -> Color { + switch priority?.lowercased() { + case "urgent": return .red + case "high": return .orange + case "medium": return .blue + default: return .gray + } + } +} + +// MARK: - Widget Configuration +@main +struct TaskWidget: Widget { + let kind: String = "TaskWidget" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: TaskWidgetProvider()) { entry in + if #available(iOS 17.0, *) { + TaskWidgetEntryView(entry: entry) + .containerBackground(.fill.tertiary, for: .widget) + } else { + TaskWidgetEntryView(entry: entry) + .padding() + .background() + } + } + .configurationDisplayName("My Tasks") + .description("View your upcoming tasks at a glance") + .supportedFamilies([.systemSmall, .systemMedium]) + } +} + +// MARK: - Preview +struct TaskWidget_Previews: PreviewProvider { + static var previews: some View { + let sampleTasks = [ + CustomTask( + id: 1, + residence: 1, + createdBy: 1, + createdByUsername: "user", + title: "Fix leaky faucet", + description: "Kitchen sink", + category: "plumbing", + priority: "high", + status: "pending", + dueDate: "2024-12-15", + estimatedCost: nil, + actualCost: nil, + notes: nil, + createdAt: "2024-01-01T00:00:00Z", + updatedAt: "2024-01-01T00:00:00Z", + showCompletedButton: false, + daysUntilDue: nil, + isOverdue: nil, + lastCompletion: nil + ) + ] + + TaskWidgetEntryView(entry: TaskWidgetEntry(date: Date(), tasks: sampleTasks)) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + + TaskWidgetEntryView(entry: TaskWidgetEntry(date: Date(), tasks: sampleTasks)) + .previewContext(WidgetPreviewContext(family: .systemMedium)) + } +} diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 2c59016..3728c39 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -6,11 +6,52 @@ objectVersion = 77; objects = { +/* Begin PBXBuildFile section */ + 1C0789402EBC218B00392B46 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C07893F2EBC218B00392B46 /* WidgetKit.framework */; }; + 1C0789422EBC218B00392B46 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C0789412EBC218B00392B46 /* SwiftUI.framework */; }; + 1C0789532EBC218D00392B46 /* MyCribExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 1C07893D2EBC218B00392B46 /* MyCribExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 1C0789512EBC218D00392B46 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6A3E1D84F9F1A2FD92A75A6C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 1C07893C2EBC218B00392B46; + remoteInfo = MyCribExtension; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 1C0789582EBC218D00392B46 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + 1C0789532EBC218D00392B46 /* MyCribExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ + 1C07893D2EBC218B00392B46 /* MyCribExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = MyCribExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 1C07893F2EBC218B00392B46 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 1C0789412EBC218B00392B46 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + 1C0789612EBC2F5400392B46 /* MyCribExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MyCribExtension.entitlements; sourceTree = ""; }; 96A3DDC05E14B3F83E56282F /* MyCrib.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MyCrib.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ + 1C0789572EBC218D00392B46 /* Exceptions for "MyCrib" folder in "MyCribExtension" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 1C07893C2EBC218B00392B46 /* MyCribExtension */; + }; 84D9B4B86A80D013B8CBB951 /* Exceptions for "iosApp" folder in "iosApp" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( @@ -21,6 +62,14 @@ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ + 1C0789432EBC218B00392B46 /* MyCrib */ = { + isa = PBXFileSystemSynchronizedRootGroup; + exceptions = ( + 1C0789572EBC218D00392B46 /* Exceptions for "MyCrib" folder in "MyCribExtension" target */, + ); + path = MyCrib; + sourceTree = ""; + }; 7A237E53D5D71D9D6A361E29 /* Configuration */ = { isa = PBXFileSystemSynchronizedRootGroup; path = Configuration; @@ -37,6 +86,15 @@ /* End PBXFileSystemSynchronizedRootGroup section */ /* Begin PBXFrameworksBuildPhase section */ + 1C07893A2EBC218B00392B46 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1C0789422EBC218B00392B46 /* SwiftUI.framework in Frameworks */, + 1C0789402EBC218B00392B46 /* WidgetKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4C05B929016E54EA711D74CA /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -47,11 +105,23 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1C07893E2EBC218B00392B46 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1C07893F2EBC218B00392B46 /* WidgetKit.framework */, + 1C0789412EBC218B00392B46 /* SwiftUI.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 86BC7E88090398B44B7DB0E4 = { isa = PBXGroup; children = ( + 1C0789612EBC2F5400392B46 /* MyCribExtension.entitlements */, 7A237E53D5D71D9D6A361E29 /* Configuration */, E822E6B231E7783DE992578C /* iosApp */, + 1C0789432EBC218B00392B46 /* MyCrib */, + 1C07893E2EBC218B00392B46 /* Frameworks */, FA6022B7B844191C54E57EB4 /* Products */, ); sourceTree = ""; @@ -60,6 +130,7 @@ isa = PBXGroup; children = ( 96A3DDC05E14B3F83E56282F /* MyCrib.app */, + 1C07893D2EBC218B00392B46 /* MyCribExtension.appex */, ); name = Products; sourceTree = ""; @@ -67,6 +138,28 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 1C07893C2EBC218B00392B46 /* MyCribExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1C0789542EBC218D00392B46 /* Build configuration list for PBXNativeTarget "MyCribExtension" */; + buildPhases = ( + 1C0789392EBC218B00392B46 /* Sources */, + 1C07893A2EBC218B00392B46 /* Frameworks */, + 1C07893B2EBC218B00392B46 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 1C0789432EBC218B00392B46 /* MyCrib */, + ); + name = MyCribExtension; + packageProductDependencies = ( + ); + productName = MyCribExtension; + productReference = 1C07893D2EBC218B00392B46 /* MyCribExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; D4ADB376A7A4CFB73469E173 /* iosApp */ = { isa = PBXNativeTarget; buildConfigurationList = 293B4412461C9407D900D07D /* Build configuration list for PBXNativeTarget "iosApp" */; @@ -75,10 +168,12 @@ 3B506EC7E4A1032BA1E06A37 /* Sources */, 4C05B929016E54EA711D74CA /* Frameworks */, 50827B76877E1E3968917892 /* Resources */, + 1C0789582EBC218D00392B46 /* Embed Foundation Extensions */, ); buildRules = ( ); dependencies = ( + 1C0789522EBC218D00392B46 /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( E822E6B231E7783DE992578C /* iosApp */, @@ -97,9 +192,12 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1620; + LastSwiftUpdateCheck = 2600; LastUpgradeCheck = 1620; TargetAttributes = { + 1C07893C2EBC218B00392B46 = { + CreatedOnToolsVersion = 26.0.1; + }; D4ADB376A7A4CFB73469E173 = { CreatedOnToolsVersion = 16.2; }; @@ -120,11 +218,19 @@ projectRoot = ""; targets = ( D4ADB376A7A4CFB73469E173 /* iosApp */, + 1C07893C2EBC218B00392B46 /* MyCribExtension */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 1C07893B2EBC218B00392B46 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 50827B76877E1E3968917892 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -157,6 +263,13 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 1C0789392EBC218B00392B46 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 3B506EC7E4A1032BA1E06A37 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -166,6 +279,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 1C0789522EBC218D00392B46 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 1C07893C2EBC218B00392B46 /* MyCribExtension */; + targetProxy = 1C0789512EBC218D00392B46 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 0248CABA5A5197845F2E5C26 /* Release */ = { isa = XCBuildConfiguration; @@ -173,10 +294,11 @@ ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = iosApp/iosApp.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; - DEVELOPMENT_TEAM = "${TEAM_ID}"; + DEVELOPMENT_TEAM = V3PF3M6B6U; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = iosApp/Info.plist; @@ -189,12 +311,81 @@ "$(inherited)", "@executable_path/Frameworks", ); + PRODUCT_BUNDLE_IDENTIFIER = com.tt.mycrib.MyCrib; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; + 1C0789552EBC218D00392B46 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = MyCribExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = V3PF3M6B6U; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MyCrib/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = MyCrib; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.tt.mycrib.MyCrib.MyCrib; + 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; + }; + 1C0789562EBC218D00392B46 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = MyCribExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = V3PF3M6B6U; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = MyCrib/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = MyCrib; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.tt.mycrib.MyCrib.MyCrib; + 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; + }; 468E4A6C96BEEFB382150D37 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReferenceAnchor = 7A237E53D5D71D9D6A361E29 /* Configuration */; @@ -324,10 +515,11 @@ ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = iosApp/iosApp.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; - DEVELOPMENT_TEAM = "${TEAM_ID}"; + DEVELOPMENT_TEAM = V3PF3M6B6U; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = iosApp/Info.plist; @@ -340,6 +532,7 @@ "$(inherited)", "@executable_path/Frameworks", ); + PRODUCT_BUNDLE_IDENTIFIER = com.tt.mycrib.MyCrib; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -349,6 +542,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 1C0789542EBC218D00392B46 /* Build configuration list for PBXNativeTarget "MyCribExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1C0789552EBC218D00392B46 /* Debug */, + 1C0789562EBC218D00392B46 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 293B4412461C9407D900D07D /* Build configuration list for PBXNativeTarget "iosApp" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/iosApp/iosApp/DELETE_THESE_FILES.txt b/iosApp/iosApp/DELETE_THESE_FILES.txt deleted file mode 100644 index d67c893..0000000 --- a/iosApp/iosApp/DELETE_THESE_FILES.txt +++ /dev/null @@ -1,28 +0,0 @@ -PLEASE DELETE THE FOLLOWING FILES: - -❌ AuthSubviews.swift -❌ CommonSubviews.swift -❌ ResidenceSubviews.swift -❌ TaskSubviews.swift - -These are old consolidated files. The new individual subview files are in the Subviews folder: - -βœ… Subviews/Common/ErrorView.swift -βœ… Subviews/Common/ErrorMessageView.swift -βœ… Subviews/Auth/LoginHeader.swift -βœ… Subviews/Auth/RegisterHeader.swift -βœ… Subviews/Residence/SummaryCard.swift -βœ… Subviews/Residence/SummaryStatView.swift -βœ… Subviews/Residence/ResidenceCard.swift -βœ… Subviews/Residence/TaskStatChip.swift -βœ… Subviews/Residence/EmptyResidencesView.swift -βœ… Subviews/Residence/PropertyHeaderCard.swift -βœ… Subviews/Residence/PropertyDetailItem.swift -βœ… Subviews/Task/TaskPill.swift -βœ… Subviews/Task/StatusBadge.swift -βœ… Subviews/Task/PriorityBadge.swift -βœ… Subviews/Task/EmptyTasksView.swift -βœ… Subviews/Task/TaskCard.swift -βœ… Subviews/Task/TasksSection.swift - -Each file contains only ONE view component with its own preview. diff --git a/iosApp/iosApp/LookupsManager.swift b/iosApp/iosApp/LookupsManager.swift index f528365..ac7c9d1 100644 --- a/iosApp/iosApp/LookupsManager.swift +++ b/iosApp/iosApp/LookupsManager.swift @@ -61,7 +61,7 @@ class LookupsManager: ObservableObject { // Observe all tasks Task { - for await tasks in repository.allTasks.taskTaskAsyncSequence { + for await tasks in repository.allTasks.allTasksAsyncSequence { self.allTasks = tasks } } diff --git a/iosApp/iosApp/SETUP_INSTRUCTIONS.md b/iosApp/iosApp/SETUP_INSTRUCTIONS.md deleted file mode 100644 index 946c9ae..0000000 --- a/iosApp/iosApp/SETUP_INSTRUCTIONS.md +++ /dev/null @@ -1,48 +0,0 @@ -# iOS Project Setup Instructions - -## Adding New Subview Files to Xcode - -Four new Swift files containing reusable subviews have been created but need to be added to the Xcode project: - -1. `CommonSubviews.swift` - Error views -2. `AuthSubviews.swift` - Login/Register headers -3. `ResidenceSubviews.swift` - Property-related views -4. `TaskSubviews.swift` - Task-related views - -### Steps to Add Files (30 seconds): - -1. Open `iosApp.xcodeproj` in Xcode -2. Right-click on the `iosApp` folder in the Project Navigator -3. Select **"Add Files to 'iosApp'..."** -4. Navigate to the `iosApp/iosApp/` directory -5. Select all 4 `*Subviews.swift` files: - - `AuthSubviews.swift` - - `CommonSubviews.swift` - - `ResidenceSubviews.swift` - - `TaskSubviews.swift` -6. Make sure **"Copy items if needed"** is UNchecked (files are already in the project) -7. Make sure **"Add to targets: iosApp"** is checked -8. Click **"Add"** - -### Verify - -Build the project (Cmd+B). All errors should be resolved. - -### File Locations - -All files are located in: `/Users/treyt/Desktop/code/myCrib/MyCribKMM/iosApp/iosApp/` - -``` -iosApp/ -└── iosApp/ - β”œβ”€β”€ AuthSubviews.swift ← Add this - β”œβ”€β”€ CommonSubviews.swift ← Add this - β”œβ”€β”€ ResidenceSubviews.swift ← Add this - β”œβ”€β”€ TaskSubviews.swift ← Add this - β”œβ”€β”€ Login/ - β”œβ”€β”€ Register/ - β”œβ”€β”€ Residence/ - └── Task/ -``` - -That's it! The project should now compile without errors. diff --git a/iosApp/iosApp/StateFlowExtensions.swift b/iosApp/iosApp/StateFlowExtensions.swift index 41a406c..d4a00e7 100644 --- a/iosApp/iosApp/StateFlowExtensions.swift +++ b/iosApp/iosApp/StateFlowExtensions.swift @@ -69,6 +69,10 @@ extension Kotlinx_coroutines_coreStateFlow { return asAsyncSequence() } + var allTasksAsyncSequence: AsyncStream<[CustomTask]> { + return asAsyncSequence() + } + var boolAsyncSequence: AsyncStream { return asAsyncSequence() } diff --git a/iosApp/iosApp/iosApp.entitlements b/iosApp/iosApp/iosApp.entitlements new file mode 100644 index 0000000..52e1157 --- /dev/null +++ b/iosApp/iosApp/iosApp.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.tt.mycrib.MyCrib + + +