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.
 
 
 

213 lines
7.7 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. private let logger = Logging(for: String(describing: WriteFreely_MultiPlatformApp.self))
  24. #if os(macOS)
  25. @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
  26. @StateObject var updaterViewModel = MacUpdatesViewModel()
  27. @State private var selectedTab = 0
  28. #endif
  29. @State private var didCrash = UserDefaults.shared.bool(forKey: WFDefaults.didHaveFatalError)
  30. var body: some Scene {
  31. WindowGroup {
  32. ContentView()
  33. .onAppear(perform: {
  34. if model.editor.showAllPostsFlag {
  35. DispatchQueue.main.async {
  36. self.model.selectedCollection = nil
  37. self.model.showAllPosts = true
  38. showLastDraftOrCreateNewLocalPost()
  39. }
  40. } else {
  41. DispatchQueue.main.async {
  42. self.model.selectedCollection = model.editor.fetchSelectedCollectionFromAppStorage()
  43. self.model.showAllPosts = false
  44. showLastDraftOrCreateNewLocalPost()
  45. }
  46. }
  47. })
  48. .alert(isPresented: $didCrash) {
  49. var helpMsg = "Alert the humans by sharing what happened on the help forum."
  50. if let errorMsg = UserDefaults.shared.object(forKey: WFDefaults.fatalErrorDescription) as? String {
  51. helpMsg.append("\n\n\(errorMsg)")
  52. }
  53. return Alert(
  54. title: Text("Crash Detected"),
  55. message: Text(helpMsg),
  56. primaryButton: .default(
  57. Text("Let us know"), action: didPressCrashAlertButton
  58. ),
  59. secondaryButton: .cancel(
  60. Text("Dismiss"),
  61. action: resetCrashFlags
  62. )
  63. )
  64. }
  65. .onAppear {
  66. if #available(iOS 15, *) {
  67. if didCrash { generateCrashLogPost() }
  68. }
  69. }
  70. .withErrorHandling()
  71. .environmentObject(model)
  72. .environment(\.managedObjectContext, LocalStorageManager.standard.container.viewContext)
  73. // .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info.
  74. }
  75. .commands {
  76. #if os(macOS)
  77. CommandGroup(after: .appInfo) {
  78. CheckForUpdatesView(updaterViewModel: updaterViewModel)
  79. }
  80. #endif
  81. CommandGroup(replacing: .newItem, addition: {
  82. Button("New Post") {
  83. createNewLocalPost()
  84. }
  85. .keyboardShortcut("n", modifiers: [.command])
  86. })
  87. CommandGroup(after: .newItem) {
  88. Button("Refresh Posts") {
  89. DispatchQueue.main.async {
  90. model.fetchUserCollections()
  91. model.fetchUserPosts()
  92. }
  93. }
  94. .disabled(!model.account.isLoggedIn)
  95. .keyboardShortcut("r", modifiers: [.command])
  96. }
  97. SidebarCommands()
  98. #if os(macOS)
  99. PostCommands(model: model)
  100. HelpCommands(model: model)
  101. #endif
  102. ToolbarCommands()
  103. TextEditingCommands()
  104. }
  105. #if os(macOS)
  106. Settings {
  107. TabView(selection: $selectedTab) {
  108. MacAccountView()
  109. .environmentObject(model)
  110. .tabItem {
  111. Image(systemName: "person.crop.circle")
  112. Text("Account")
  113. }
  114. .tag(0)
  115. MacPreferencesView(preferences: model.preferences)
  116. .tabItem {
  117. Image(systemName: "gear")
  118. Text("Preferences")
  119. }
  120. .tag(1)
  121. MacUpdatesView(updaterViewModel: updaterViewModel)
  122. .tabItem {
  123. Image(systemName: "arrow.down.circle")
  124. Text("Updates")
  125. }
  126. .tag(2)
  127. }
  128. .environmentObject(model)
  129. .withErrorHandling()
  130. .frame(minWidth: 500, maxWidth: 500, minHeight: 200)
  131. .padding()
  132. // .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info.
  133. }
  134. #endif
  135. }
  136. private func showLastDraftOrCreateNewLocalPost() {
  137. if model.editor.lastDraftURL != nil {
  138. DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
  139. self.model.selectedPost = model.editor.fetchLastDraftFromAppStorage()
  140. }
  141. } else {
  142. createNewLocalPost()
  143. }
  144. }
  145. private func createNewLocalPost() {
  146. withAnimation {
  147. // Un-set the currently selected post
  148. self.model.selectedPost = nil
  149. }
  150. // Create the new-post managed object
  151. let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font)
  152. withAnimation {
  153. // Set it as the selectedPost
  154. DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
  155. self.model.selectedPost = managedPost
  156. }
  157. }
  158. }
  159. @available(iOS 15, *)
  160. private func generateCrashLogPost() {
  161. logger.log("Generating local log post...")
  162. DispatchQueue.main.asyncAfter(deadline: .now()) {
  163. // Unset selected post and collection and navigate to local drafts.
  164. self.model.selectedPost = nil
  165. self.model.selectedCollection = nil
  166. self.model.showAllPosts = false
  167. // Create the new log post.
  168. let newLogPost = model.editor.generateNewLocalPost(withFont: 2)
  169. newLogPost.title = "Logs For Support"
  170. var postBody: [String] = [
  171. "WriteFreely-Multiplatform v\(Bundle.main.appMarketingVersion) (\(Bundle.main.appBuildVersion))",
  172. "Generated \(Date())",
  173. ""
  174. ]
  175. postBody.append(contentsOf: logger.fetchLogs())
  176. newLogPost.body = postBody.joined(separator: "\n")
  177. self.model.selectedPost = newLogPost
  178. }
  179. logger.log("Generated local log post.")
  180. }
  181. private func resetCrashFlags() {
  182. UserDefaults.shared.set(false, forKey: WFDefaults.didHaveFatalError)
  183. UserDefaults.shared.removeObject(forKey: WFDefaults.fatalErrorDescription)
  184. }
  185. private func didPressCrashAlertButton() {
  186. resetCrashFlags()
  187. #if os(macOS)
  188. NSWorkspace().open(model.helpURL)
  189. #else
  190. UIApplication.shared.open(model.helpURL)
  191. #endif
  192. }
  193. }