ios widget showing what is available at chucks
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: move siri intents to app intents

+198 -199
+198
wasup-chucks/AppIntents.swift
··· 1 + // 2 + // AppIntents.swift 3 + // wasup-chucks 4 + // 5 + // Created by Kieran Klukas on 2/2/26. 6 + // 7 + 8 + import AppIntents 9 + 10 + // MARK: - Check Status 11 + 12 + struct CheckChucksStatus: AppIntent { 13 + static var title: LocalizedStringResource = "Check Chuck's Status" 14 + static var description = IntentDescription("Check if Chuck's is open or closed.") 15 + 16 + @MainActor 17 + func perform() async throws -> some IntentResult & ReturnsValue<String> { 18 + let status = ChucksStatus.calculate() 19 + 20 + if status.isOpen { 21 + if let remaining = status.timeRemaining { 22 + return .result(value: "Open for \(status.currentPhase.rawValue). Closes in \(remaining.compactCountdown).") 23 + } 24 + return .result(value: "Open for \(status.currentPhase.rawValue).") 25 + } else { 26 + if let next = status.nextPhase, next != .closed, let remaining = status.timeRemaining { 27 + return .result(value: "Closed. \(next.rawValue) starts in \(remaining.compactCountdown).") 28 + } 29 + return .result(value: "Closed for the day.") 30 + } 31 + } 32 + } 33 + 34 + // MARK: - Is Open 35 + 36 + struct IsChucksOpen: AppIntent { 37 + static var title: LocalizedStringResource = "Is Chuck's Open" 38 + static var description = IntentDescription("Returns true if Chuck's is currently open.") 39 + 40 + @MainActor 41 + func perform() async throws -> some IntentResult & ReturnsValue<Bool> { 42 + let status = ChucksStatus.calculate() 43 + return .result(value: status.isOpen) 44 + } 45 + } 46 + 47 + // MARK: - Get Menu 48 + 49 + struct GetChucksMenu: AppIntent { 50 + static var title: LocalizedStringResource = "Get Chuck's Menu" 51 + static var description = IntentDescription("Get what's being served at Chuck's.") 52 + 53 + @Parameter(title: "Meal", default: .current) 54 + var meal: MealOption 55 + 56 + @MainActor 57 + func perform() async throws -> some IntentResult & ReturnsValue<[String]> { 58 + let status = ChucksStatus.calculate() 59 + let phase: MealPhase 60 + 61 + switch meal { 62 + case .current: 63 + phase = status.isOpen ? status.currentPhase : (status.nextPhase ?? .lunch) 64 + case .breakfast: 65 + phase = .breakfast 66 + case .lunch: 67 + phase = .lunch 68 + case .dinner: 69 + phase = .dinner 70 + } 71 + 72 + if phase == .closed { 73 + return .result(value: []) 74 + } 75 + 76 + do { 77 + let items = try await ChucksService.shared.getSpecials(for: Date(), phase: phase) 78 + return .result(value: items.map { $0.name }) 79 + } catch { 80 + return .result(value: []) 81 + } 82 + } 83 + } 84 + 85 + // MARK: - Get Meal Time 86 + 87 + struct GetMealTime: AppIntent { 88 + static var title: LocalizedStringResource = "Get Meal Time" 89 + static var description = IntentDescription("Get when a meal is served at Chuck's.") 90 + 91 + @Parameter(title: "Meal") 92 + var meal: MealTimeOption 93 + 94 + @MainActor 95 + func perform() async throws -> some IntentResult & ReturnsValue<String> { 96 + let calendar = CedarvilleTime.calendar 97 + let weekday = calendar.component(.weekday, from: Date()) 98 + let schedule = MealSchedule.schedule(for: weekday) 99 + 100 + let phase: MealPhase = switch meal { 101 + case .breakfast: .breakfast 102 + case .lunch: .lunch 103 + case .dinner: .dinner 104 + } 105 + 106 + guard let mealSchedule = schedule.first(where: { $0.phase == phase }) else { 107 + return .result(value: "Not served today") 108 + } 109 + 110 + let start = formatTime(mealSchedule.startHour, mealSchedule.startMinute) 111 + let end = formatTime(mealSchedule.endHour, mealSchedule.endMinute) 112 + return .result(value: "\(start) - \(end)") 113 + } 114 + 115 + private func formatTime(_ hour: Int, _ minute: Int) -> String { 116 + let period = hour >= 12 ? "PM" : "AM" 117 + let h = hour > 12 ? hour - 12 : (hour == 0 ? 12 : hour) 118 + return minute == 0 ? "\(h) \(period)" : "\(h):\(String(format: "%02d", minute)) \(period)" 119 + } 120 + } 121 + 122 + // MARK: - Get Current Meal Phase 123 + 124 + struct GetCurrentMealPhase: AppIntent { 125 + static var title: LocalizedStringResource = "Get Current Meal Phase" 126 + static var description = IntentDescription("Get which meal is currently being served (or next up).") 127 + 128 + @MainActor 129 + func perform() async throws -> some IntentResult & ReturnsValue<String> { 130 + let status = ChucksStatus.calculate() 131 + if status.isOpen { 132 + return .result(value: status.currentPhase.rawValue) 133 + } else if let next = status.nextPhase, next != .closed { 134 + return .result(value: next.rawValue) 135 + } 136 + return .result(value: "Closed") 137 + } 138 + } 139 + 140 + // MARK: - Get Minutes Until Close 141 + 142 + struct GetMinutesUntilClose: AppIntent { 143 + static var title: LocalizedStringResource = "Get Minutes Until Close" 144 + static var description = IntentDescription("Get how many minutes until Chuck's closes. Returns 0 if already closed.") 145 + 146 + @MainActor 147 + func perform() async throws -> some IntentResult & ReturnsValue<Int> { 148 + let status = ChucksStatus.calculate() 149 + guard status.isOpen, let remaining = status.timeRemaining else { 150 + return .result(value: 0) 151 + } 152 + return .result(value: Int(remaining / 60)) 153 + } 154 + } 155 + 156 + // MARK: - Get Minutes Until Open 157 + 158 + struct GetMinutesUntilOpen: AppIntent { 159 + static var title: LocalizedStringResource = "Get Minutes Until Open" 160 + static var description = IntentDescription("Get how many minutes until Chuck's opens. Returns 0 if already open.") 161 + 162 + @MainActor 163 + func perform() async throws -> some IntentResult & ReturnsValue<Int> { 164 + let status = ChucksStatus.calculate() 165 + if status.isOpen { 166 + return .result(value: 0) 167 + } 168 + guard let remaining = status.timeRemaining else { 169 + return .result(value: 0) 170 + } 171 + return .result(value: Int(remaining / 60)) 172 + } 173 + } 174 + 175 + // MARK: - Parameters 176 + 177 + enum MealOption: String, AppEnum { 178 + case current, breakfast, lunch, dinner 179 + 180 + static var typeDisplayRepresentation: TypeDisplayRepresentation = "Meal" 181 + static var caseDisplayRepresentations: [MealOption: DisplayRepresentation] = [ 182 + .current: "Current", 183 + .breakfast: "Breakfast", 184 + .lunch: "Lunch", 185 + .dinner: "Dinner" 186 + ] 187 + } 188 + 189 + enum MealTimeOption: String, AppEnum { 190 + case breakfast, lunch, dinner 191 + 192 + static var typeDisplayRepresentation: TypeDisplayRepresentation = "Meal" 193 + static var caseDisplayRepresentations: [MealTimeOption: DisplayRepresentation] = [ 194 + .breakfast: "Breakfast", 195 + .lunch: "Lunch", 196 + .dinner: "Dinner" 197 + ] 198 + }
-199
wasup-chucks/SiriIntents.swift
··· 1 - // 2 - // SiriIntents.swift 3 - // wasup-chucks 4 - // 5 - // Created by Kieran Klukas on 2/2/26. 6 - // 7 - 8 - import AppIntents 9 - 10 - // MARK: - Chuck's Status Intent 11 - 12 - struct ChucksStatusIntent: AppIntent { 13 - static var title: LocalizedStringResource = "Check Chuck's Status" 14 - static var description = IntentDescription("Check if Chuck's dining hall is currently open or closed.") 15 - static var openAppWhenRun: Bool = false 16 - 17 - @MainActor 18 - func perform() async throws -> some IntentResult & ProvidesDialog { 19 - let status = ChucksStatus.calculate() 20 - 21 - if status.isOpen { 22 - if let remaining = status.timeRemaining { 23 - let timeStr = remaining.compactCountdown 24 - return .result(dialog: "Chuck's is open for \(status.currentPhase.rawValue). It closes in \(timeStr).") 25 - } else { 26 - return .result(dialog: "Chuck's is open for \(status.currentPhase.rawValue).") 27 - } 28 - } else { 29 - if let next = status.nextPhase, next != .closed, let remaining = status.timeRemaining { 30 - let timeStr = remaining.compactCountdown 31 - return .result(dialog: "Chuck's is closed. \(next.rawValue) starts in \(timeStr).") 32 - } else { 33 - return .result(dialog: "Chuck's is closed for the day.") 34 - } 35 - } 36 - } 37 - } 38 - 39 - // MARK: - Chuck's Menu Intent 40 - 41 - struct ChucksMenuIntent: AppIntent { 42 - static var title: LocalizedStringResource = "Get Chuck's Menu" 43 - static var description = IntentDescription("Find out what's being served at Chuck's.") 44 - static var openAppWhenRun: Bool = false 45 - 46 - @Parameter(title: "Meal", default: .current) 47 - var meal: MealParameter 48 - 49 - @MainActor 50 - func perform() async throws -> some IntentResult & ProvidesDialog { 51 - let status = ChucksStatus.calculate() 52 - let phase: MealPhase 53 - 54 - switch meal { 55 - case .current: 56 - phase = status.isOpen ? status.currentPhase : (status.nextPhase ?? .lunch) 57 - case .breakfast: 58 - phase = .breakfast 59 - case .lunch: 60 - phase = .lunch 61 - case .dinner: 62 - phase = .dinner 63 - } 64 - 65 - if phase == .closed { 66 - return .result(dialog: "Chuck's is closed for the day. Check back tomorrow!") 67 - } 68 - 69 - do { 70 - let items = try await ChucksService.shared.getSpecials(for: Date(), phase: phase) 71 - if items.isEmpty { 72 - return .result(dialog: "I couldn't find the menu for \(phase.rawValue) right now.") 73 - } 74 - 75 - let itemNames = items.prefix(5).map { $0.name }.joined(separator: ", ") 76 - return .result(dialog: "For \(phase.rawValue) at Home Cooking: \(itemNames).") 77 - } catch { 78 - return .result(dialog: "I couldn't load the menu right now. Try again later.") 79 - } 80 - } 81 - } 82 - 83 - // MARK: - Meal Time Intent 84 - 85 - struct ChucksMealTimeIntent: AppIntent { 86 - static var title: LocalizedStringResource = "Get Meal Time" 87 - static var description = IntentDescription("Find out what time a meal is served at Chuck's.") 88 - static var openAppWhenRun: Bool = false 89 - 90 - @Parameter(title: "Meal") 91 - var meal: MealTimeParameter 92 - 93 - @MainActor 94 - func perform() async throws -> some IntentResult & ProvidesDialog { 95 - let calendar = CedarvilleTime.calendar 96 - let weekday = calendar.component(.weekday, from: Date()) 97 - let schedule = MealSchedule.schedule(for: weekday) 98 - 99 - let phase: MealPhase 100 - switch meal { 101 - case .breakfast: 102 - phase = .breakfast 103 - case .lunch: 104 - phase = .lunch 105 - case .dinner: 106 - phase = .dinner 107 - } 108 - 109 - guard let mealSchedule = schedule.first(where: { $0.phase == phase }) else { 110 - return .result(dialog: "\(phase.rawValue) isn't served today.") 111 - } 112 - 113 - let startTime = formatTime(mealSchedule.startHour, mealSchedule.startMinute) 114 - let endTime = formatTime(mealSchedule.endHour, mealSchedule.endMinute) 115 - 116 - return .result(dialog: "\(phase.rawValue) is served from \(startTime) to \(endTime) today.") 117 - } 118 - 119 - private func formatTime(_ hour: Int, _ minute: Int) -> String { 120 - let period = hour >= 12 ? "PM" : "AM" 121 - let displayHour = hour > 12 ? hour - 12 : (hour == 0 ? 12 : hour) 122 - if minute == 0 { 123 - return "\(displayHour) \(period)" 124 - } 125 - return "\(displayHour):\(String(format: "%02d", minute)) \(period)" 126 - } 127 - } 128 - 129 - // MARK: - Meal Parameters 130 - 131 - enum MealTimeParameter: String, AppEnum { 132 - case breakfast 133 - case lunch 134 - case dinner 135 - 136 - static var typeDisplayRepresentation: TypeDisplayRepresentation = "Meal" 137 - 138 - static var caseDisplayRepresentations: [MealTimeParameter: DisplayRepresentation] = [ 139 - .breakfast: "Breakfast", 140 - .lunch: "Lunch", 141 - .dinner: "Dinner" 142 - ] 143 - } 144 - 145 - enum MealParameter: String, AppEnum { 146 - case current 147 - case breakfast 148 - case lunch 149 - case dinner 150 - 151 - static var typeDisplayRepresentation: TypeDisplayRepresentation = "Meal" 152 - 153 - static var caseDisplayRepresentations: [MealParameter: DisplayRepresentation] = [ 154 - .current: "Current Meal", 155 - .breakfast: "Breakfast", 156 - .lunch: "Lunch", 157 - .dinner: "Dinner" 158 - ] 159 - } 160 - 161 - // MARK: - App Shortcuts 162 - 163 - struct ChucksShortcuts: AppShortcutsProvider { 164 - static var appShortcuts: [AppShortcut] { 165 - AppShortcut( 166 - intent: ChucksStatusIntent(), 167 - phrases: [ 168 - "Is \(.applicationName) open", 169 - "Check \(.applicationName) status", 170 - "When does \(.applicationName) open", 171 - "When does \(.applicationName) close" 172 - ], 173 - shortTitle: "Chuck's Status", 174 - systemImageName: "fork.knife.circle" 175 - ) 176 - 177 - AppShortcut( 178 - intent: ChucksMenuIntent(), 179 - phrases: [ 180 - "What's for \(\.$meal) at \(.applicationName)", 181 - "\(.applicationName) menu", 182 - "What's being served at \(.applicationName)" 183 - ], 184 - shortTitle: "Chuck's Menu", 185 - systemImageName: "menucard" 186 - ) 187 - 188 - AppShortcut( 189 - intent: ChucksMealTimeIntent(), 190 - phrases: [ 191 - "What time is \(\.$meal) at \(.applicationName)", 192 - "When is \(\.$meal) at \(.applicationName)", 193 - "\(.applicationName) \(\.$meal) hours" 194 - ], 195 - shortTitle: "Meal Times", 196 - systemImageName: "clock" 197 - ) 198 - } 199 - }