Source code for the WriteFreely SwiftUI app for iOS, iPadOS, and macOS
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

184 lines
6.5 KiB

  1. import SwiftUI
  2. #if os(macOS)
  3. import Sparkle
  4. #endif
  5. @main
  6. struct CheckForDebugModifier {
  7. static func main() {
  8. #if os(macOS)
  9. if NSEvent.modifierFlags.contains(.shift) {
  10. // Clear the launch-to-last-draft values to load a new draft.
  11. UserDefaults.shared.setValue(false, forKey: WFDefaults.showAllPostsFlag)
  12. UserDefaults.shared.setValue(nil, forKey: WFDefaults.selectedCollectionURL)
  13. UserDefaults.shared.setValue(nil, forKey: WFDefaults.lastDraftURL)
  14. } else {
  15. // No-op
  16. }
  17. #endif
  18. WriteFreely_MultiPlatformApp.main()
  19. }
  20. }
  21. struct WriteFreely_MultiPlatformApp: App {
  22. @StateObject private var model = WriteFreelyModel.shared
  23. #if os(macOS)
  24. @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
  25. @StateObject var updaterViewModel = MacUpdatesViewModel()
  26. @State private var selectedTab = 0
  27. #endif
  28. @State private var didCrash = UserDefaults.shared.bool(forKey: WFDefaults.didHaveFatalError)
  29. var body: some Scene {
  30. WindowGroup {
  31. ContentView()
  32. .onAppear(perform: {
  33. if model.editor.showAllPostsFlag {
  34. DispatchQueue.main.async {
  35. self.model.selectedCollection = nil
  36. self.model.showAllPosts = true
  37. showLastDraftOrCreateNewLocalPost()
  38. }
  39. } else {
  40. DispatchQueue.main.async {
  41. self.model.selectedCollection = model.editor.fetchSelectedCollectionFromAppStorage()
  42. self.model.showAllPosts = false
  43. showLastDraftOrCreateNewLocalPost()
  44. }
  45. }
  46. })
  47. .alert(isPresented: $didCrash) {
  48. var helpMsg = "Alert the humans by sharing what happened on the help forum."
  49. if let errorMsg = UserDefaults.shared.object(forKey: WFDefaults.fatalErrorDescription) as? String {
  50. helpMsg.append("\n\n\(errorMsg)")
  51. }
  52. return Alert(
  53. title: Text("Crash Detected"),
  54. message: Text(helpMsg),
  55. primaryButton: .default(
  56. Text("Let us know"), action: didPressCrashAlertButton
  57. ),
  58. secondaryButton: .cancel(
  59. Text("Dismiss"),
  60. action: resetCrashFlags
  61. )
  62. )
  63. }
  64. .withErrorHandling()
  65. .environmentObject(model)
  66. .environment(\.managedObjectContext, LocalStorageManager.standard.container.viewContext)
  67. // .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info.
  68. }
  69. .commands {
  70. #if os(macOS)
  71. CommandGroup(after: .appInfo) {
  72. CheckForUpdatesView(updaterViewModel: updaterViewModel)
  73. }
  74. #endif
  75. CommandGroup(replacing: .newItem, addition: {
  76. Button("New Post") {
  77. createNewLocalPost()
  78. }
  79. .keyboardShortcut("n", modifiers: [.command])
  80. })
  81. CommandGroup(after: .newItem) {
  82. Button("Refresh Posts") {
  83. DispatchQueue.main.async {
  84. model.fetchUserCollections()
  85. model.fetchUserPosts()
  86. }
  87. }
  88. .disabled(!model.account.isLoggedIn)
  89. .keyboardShortcut("r", modifiers: [.command])
  90. }
  91. SidebarCommands()
  92. #if os(macOS)
  93. PostCommands(model: model)
  94. #endif
  95. CommandGroup(after: .help) {
  96. Button("Visit Support Forum") {
  97. #if os(macOS)
  98. NSWorkspace().open(model.helpURL)
  99. #else
  100. UIApplication.shared.open(model.helpURL)
  101. #endif
  102. }
  103. }
  104. ToolbarCommands()
  105. TextEditingCommands()
  106. }
  107. #if os(macOS)
  108. Settings {
  109. TabView(selection: $selectedTab) {
  110. MacAccountView()
  111. .environmentObject(model)
  112. .tabItem {
  113. Image(systemName: "person.crop.circle")
  114. Text("Account")
  115. }
  116. .tag(0)
  117. MacPreferencesView(preferences: model.preferences)
  118. .tabItem {
  119. Image(systemName: "gear")
  120. Text("Preferences")
  121. }
  122. .tag(1)
  123. MacUpdatesView(updaterViewModel: updaterViewModel)
  124. .tabItem {
  125. Image(systemName: "arrow.down.circle")
  126. Text("Updates")
  127. }
  128. .tag(2)
  129. }
  130. .withErrorHandling()
  131. .frame(minWidth: 500, maxWidth: 500, minHeight: 200)
  132. .padding()
  133. // .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info.
  134. }
  135. #endif
  136. }
  137. private func showLastDraftOrCreateNewLocalPost() {
  138. if model.editor.lastDraftURL != nil {
  139. self.model.selectedPost = model.editor.fetchLastDraftFromAppStorage()
  140. } else {
  141. createNewLocalPost()
  142. }
  143. }
  144. private func createNewLocalPost() {
  145. withAnimation {
  146. // Un-set the currently selected post
  147. self.model.selectedPost = nil
  148. }
  149. // Create the new-post managed object
  150. let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font)
  151. withAnimation {
  152. // Set it as the selectedPost
  153. DispatchQueue.main.asyncAfter(deadline: .now()) {
  154. self.model.selectedPost = managedPost
  155. }
  156. }
  157. }
  158. private func resetCrashFlags() {
  159. UserDefaults.shared.set(false, forKey: WFDefaults.didHaveFatalError)
  160. UserDefaults.shared.removeObject(forKey: WFDefaults.fatalErrorDescription)
  161. }
  162. private func didPressCrashAlertButton() {
  163. resetCrashFlags()
  164. #if os(macOS)
  165. NSWorkspace().open(model.helpURL)
  166. #else
  167. UIApplication.shared.open(model.helpURL)
  168. #endif
  169. }
  170. }