ListlessiOS/ListlessiOSApp.swift (7615B)
1import SwiftUI
2
3
4
5class IOSAppDelegate: UIResponder, UIApplicationDelegate {
6 func application(
7 _ application: UIApplication,
8 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
9 ) -> Bool {
10 application.registerForRemoteNotifications()
11 return true
12 }
13
14 func application(
15 _ application: UIApplication,
16 didReceiveRemoteNotification userInfo: [AnyHashable: Any],
17 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
18 ) {
19 completionHandler(.newData)
20 }
21
22 override func buildMenu(with builder: UIMenuBuilder) {
23 guard builder.system == .main else {
24 super.buildMenu(with: builder)
25 return
26 }
27
28
29 let newItem = UIKeyCommand(
30 title: "New Item",
31 action: IOSMenuSelectors.newItem,
32 input: "n",
33 modifierFlags: .command
34 )
35 builder.insertChild(
36 UIMenu(title: "", options: .displayInline, children: [newItem]),
37 atStartOfMenu: .file
38 )
39
40
41
42 let moveUp = UIKeyCommand(
43 title: "Move Up",
44 action: IOSMenuSelectors.moveUp,
45 input: UIKeyCommand.inputUpArrow,
46 modifierFlags: .command
47 )
48 let moveDown = UIKeyCommand(
49 title: "Move Down",
50 action: IOSMenuSelectors.moveDown,
51 input: UIKeyCommand.inputDownArrow,
52 modifierFlags: .command
53 )
54 let delete = UIKeyCommand(
55 title: "Delete",
56 action: IOSMenuSelectors.deleteItem,
57 input: "\u{8}",
58 modifierFlags: .command
59 )
60 let markComplete = UIKeyCommand(
61 title: "Mark as Complete",
62 action: IOSMenuSelectors.markCompleted,
63 input: " ",
64 modifierFlags: .command
65 )
66 builder.insertChild(
67 UIMenu(title: "", options: .displayInline, children: [
68 moveUp, moveDown, delete, markComplete,
69 ]),
70 atEndOfMenu: .edit
71 )
72
73
74
75
76
77 let pageUp = UIKeyCommand(
78 title: "Page Up",
79 action: IOSMenuSelectors.navigatePageUp,
80 input: UIKeyCommand.inputUpArrow,
81 modifierFlags: .alternate
82 )
83 let pageDown = UIKeyCommand(
84 title: "Page Down",
85 action: IOSMenuSelectors.navigatePageDown,
86 input: UIKeyCommand.inputDownArrow,
87 modifierFlags: .alternate
88 )
89 let jumpToTop = UIKeyCommand(
90 title: "Jump to Top",
91 action: IOSMenuSelectors.navigateToFirst,
92 input: UIKeyCommand.inputUpArrow,
93 modifierFlags: [.command, .alternate]
94 )
95 let jumpToBottom = UIKeyCommand(
96 title: "Jump to Bottom",
97 action: IOSMenuSelectors.navigateToLast,
98 input: UIKeyCommand.inputDownArrow,
99 modifierFlags: [.command, .alternate]
100 )
101 builder.insertChild(
102 UIMenu(title: "", options: .displayInline, children: [
103 pageUp, pageDown, jumpToTop, jumpToBottom,
104 ]),
105 atEndOfMenu: .edit
106 )
107 }
108}
109
110
111
112@main
113struct ListlessiOSApp: App {
114 @UIApplicationDelegateAdaptor(IOSAppDelegate.self) var appDelegate
115 @AppStorage("appearanceMode") private var appearanceMode = 0
116 @AppStorage("didCompleteTutorial") private var didCompleteTutorial = false
117 private let persistenceController: PersistenceController
118 private let keyValueSyncBridge = KeyValueSyncBridge(keys: ["listName", "colorTheme"])
119
120 init() {
121 PerfSampler.markLaunchStart()
122 let isUITesting = ProcessInfo.processInfo.arguments.contains("UI_TESTING")
123 persistenceController = isUITesting ? PersistenceController(inMemory: true) : .shared
124 keyValueSyncBridge.start()
125
126 if isUITesting {
127 UserDefaults.standard.set(true, forKey: "didCompleteTutorial")
128 let theme = ProcessInfo.processInfo.arguments.contains("THEME_COLLAROY") ? 1 : 0
129 UserDefaults.standard.set(theme, forKey: "colorTheme")
130 }
131 }
132
133 var body: some Scene {
134 WindowGroup {
135 if didCompleteTutorial {
136 mainListView
137 } else {
138 TutorialListView { didCompleteTutorial = true }
139 }
140 }
141 }
142
143 private var mainListView: some View {
144 ItemListView(
145 store: ItemStore(persistenceController: persistenceController),
146 syncMonitor: persistenceController.syncMonitor
147 )
148 .safeAreaInset(edge: .top) {
149 Color.clear.frame(height: 8)
150 }
151 .environment(\.managedObjectContext, persistenceController.viewContext)
152 .onChange(of: appearanceMode, initial: true) { _, newValue in
153 applyAppearanceMode(newValue)
154 }
155 .task {
156 KeyboardWarmup.prime()
157 }
158 .overlay(alignment: .top) {
159 Color.outerBackground
160 .opacity(0.9)
161 .ignoresSafeArea(edges: .top)
162 .frame(height: 0)
163 }
164 }
165
166 private func applyAppearanceMode(_ mode: Int) {
167 let style: UIUserInterfaceStyle = switch mode {
168 case 1: .light
169 case 2: .dark
170 default: .unspecified
171 }
172 for scene in UIApplication.shared.connectedScenes {
173 guard let windowScene = scene as? UIWindowScene else { continue }
174 for window in windowScene.windows {
175 window.overrideUserInterfaceStyle = style
176 }
177 }
178 }
179}
180
181
182
183struct TutorialListView: View {
184 @AppStorage("appearanceMode") private var appearanceMode = 0
185 @State private var persistenceController = PersistenceController(inMemory: true)
186 var onFinishTutorial: () -> Void
187
188 var body: some View {
189 let store = ItemStore(persistenceController: persistenceController)
190 TutorialSeeder.seed(store: store)
191 return ItemListView(
192 store: store,
193 syncMonitor: persistenceController.syncMonitor,
194 onFinishTutorial: onFinishTutorial
195 )
196 .safeAreaInset(edge: .top) {
197 Color.clear.frame(height: 8)
198 }
199 .environment(\.managedObjectContext, persistenceController.viewContext)
200 .onChange(of: appearanceMode, initial: true) { _, newValue in
201 applyAppearanceMode(newValue)
202 }
203 .overlay(alignment: .top) {
204 Color.outerBackground
205 .opacity(0.9)
206 .ignoresSafeArea(edges: .top)
207 .frame(height: 0)
208 }
209 }
210
211 private func applyAppearanceMode(_ mode: Int) {
212 let style: UIUserInterfaceStyle = switch mode {
213 case 1: .light
214 case 2: .dark
215 default: .unspecified
216 }
217 for scene in UIApplication.shared.connectedScenes {
218 guard let windowScene = scene as? UIWindowScene else { continue }
219 for window in windowScene.windows {
220 window.overrideUserInterfaceStyle = style
221 }
222 }
223 }
224}