diff --git a/Shared/PostEditor/PostEditorModel.swift b/Shared/PostEditor/PostEditorModel.swift index 8d83713..7580271 100644 --- a/Shared/PostEditor/PostEditorModel.swift +++ b/Shared/PostEditor/PostEditorModel.swift @@ -1,32 +1,32 @@ import SwiftUI import CoreData enum PostAppearance: String { case sans = "OpenSans-Regular" case mono = "Hack-Regular" case serif = "Lora-Regular" } struct PostEditorModel { @AppStorage("lastDraftURL") private var lastDraftURL: URL? func saveLastDraft(_ post: WFAPost) { self.lastDraftURL = post.status != PostStatus.published.rawValue ? post.objectID.uriRepresentation() : nil } func clearLastDraft() { self.lastDraftURL = nil } func fetchLastDraftFromUserDefaults() -> WFAPost? { guard let postURL = lastDraftURL else { return nil } let coordinator = LocalStorageManager.persistentContainer.persistentStoreCoordinator guard let postManagedObjectID = coordinator.managedObjectID(forURIRepresentation: postURL) else { return nil } guard let post = LocalStorageManager.persistentContainer.viewContext.object( - with: postManagedObjectID + with: postManagedObjectID ) as? WFAPost else { return nil } return post } } diff --git a/Shared/WriteFreely_MultiPlatformApp.swift b/Shared/WriteFreely_MultiPlatformApp.swift index 287ccb7..ef3a31f 100644 --- a/Shared/WriteFreely_MultiPlatformApp.swift +++ b/Shared/WriteFreely_MultiPlatformApp.swift @@ -1,42 +1,71 @@ import SwiftUI @main struct WriteFreely_MultiPlatformApp: App { @StateObject private var model = WriteFreelyModel() #if os(macOS) @State private var selectedTab = 0 #endif var body: some Scene { WindowGroup { ContentView() + .onAppear(perform: { + if let lastDraft = model.editor.fetchLastDraftFromUserDefaults() { + self.model.selectedPost = lastDraft + } else { + createNewLocalPost() + } + }) .environmentObject(model) .environment(\.managedObjectContext, LocalStorageManager.persistentContainer.viewContext) // .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info. } #if os(macOS) Settings { TabView(selection: $selectedTab) { MacAccountView() .environmentObject(model) .tabItem { Image(systemName: "person.crop.circle") Text("Account") } .tag(0) MacPreferencesView(preferences: model.preferences) .tabItem { Image(systemName: "gear") Text("Preferences") } .tag(1) } .frame(minWidth: 300, maxWidth: 300, minHeight: 200, maxHeight: 200) .padding() // .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info. } #endif } + + private func createNewLocalPost() { + let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext) + managedPost.createdDate = Date() + managedPost.title = "" + managedPost.body = "" + managedPost.status = PostStatus.local.rawValue + managedPost.collectionAlias = nil + switch model.preferences.font { + case 1: + managedPost.appearance = "sans" + case 2: + managedPost.appearance = "wrap" + default: + managedPost.appearance = "serif" + } + if let languageCode = Locale.current.languageCode { + managedPost.language = languageCode + managedPost.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft + } + self.model.selectedPost = managedPost + } } diff --git a/iOS/PostEditor/PostEditorView.swift b/iOS/PostEditor/PostEditorView.swift index a5699f4..e5dfbd7 100644 --- a/iOS/PostEditor/PostEditorView.swift +++ b/iOS/PostEditor/PostEditorView.swift @@ -1,238 +1,251 @@ import SwiftUI struct PostEditorView: View { @EnvironmentObject var model: WriteFreelyModel @Environment(\.horizontalSizeClass) var horizontalSizeClass @Environment(\.managedObjectContext) var moc @Environment(\.presentationMode) var presentationMode @ObservedObject var post: WFAPost @State private var updatingTitleFromServer: Bool = false @State private var updatingBodyFromServer: Bool = false @State private var selectedCollection: WFACollection? @FetchRequest( entity: WFACollection.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \WFACollection.title, ascending: true)] ) var collections: FetchedResults var body: some View { VStack { if post.hasNewerRemoteCopy { RemoteChangePromptView( remoteChangeType: .remoteCopyUpdated, buttonHandler: { model.updateFromServer(post: post) } ) } else if post.wasDeletedFromServer { RemoteChangePromptView( remoteChangeType: .remoteCopyDeleted, buttonHandler: { self.presentationMode.wrappedValue.dismiss() DispatchQueue.main.async { model.posts.remove(post) } } ) } PostTextEditingView( post: _post, updatingTitleFromServer: $updatingTitleFromServer, updatingBodyFromServer: $updatingBodyFromServer ) } .navigationBarTitleDisplayMode(.inline) .padding() .toolbar { ToolbarItem(placement: .principal) { PostEditorStatusToolbarView(post: post) } ToolbarItem(placement: .primaryAction) { if model.isProcessingRequest { ProgressView() } else { Menu(content: { if post.status == PostStatus.local.rawValue { Menu(content: { Label("Publish to…", systemImage: "paperplane") Button(action: { if model.account.isLoggedIn { post.collectionAlias = nil publishPost() } else { self.model.isPresentingSettingsView = true } }, label: { Text(" \(model.account.server == "https://write.as" ? "Anonymous" : "Drafts")") }) ForEach(collections) { collection in Button(action: { if model.account.isLoggedIn { post.collectionAlias = collection.alias publishPost() } else { self.model.isPresentingSettingsView = true } }, label: { Text(" \(collection.title)") }) } }, label: { Label("Publish…", systemImage: "paperplane") }) } else { Button(action: { if model.account.isLoggedIn { publishPost() } else { self.model.isPresentingSettingsView = true } }, label: { Label("Publish", systemImage: "paperplane") }) .disabled(post.status == PostStatus.published.rawValue || post.body.count == 0) } Button(action: { sharePost() }, label: { Label("Share", systemImage: "square.and.arrow.up") }) .disabled(post.postId == nil) // Button(action: { // print("Tapped 'Delete...' button") // }, label: { // Label("Delete…", systemImage: "trash") // }) if model.account.isLoggedIn && post.status != PostStatus.local.rawValue { Section(header: Text("Move To Collection")) { Label("Move to:", systemImage: "arrowshape.zigzag.right") Picker(selection: $selectedCollection, label: Text("Move to…")) { Text( " \(model.account.server == "https://write.as" ? "Anonymous" : "Drafts")" ).tag(nil as WFACollection?) ForEach(collections) { collection in Text(" \(collection.title)").tag(collection as WFACollection?) } } } } }, label: { Image(systemName: "ellipsis.circle") }) } } } .onChange(of: post.hasNewerRemoteCopy, perform: { _ in if !post.hasNewerRemoteCopy { updatingTitleFromServer = true updatingBodyFromServer = true } }) .onChange(of: selectedCollection, perform: { [selectedCollection] newCollection in if post.collectionAlias == newCollection?.alias { return } else { post.collectionAlias = newCollection?.alias model.move(post: post, from: selectedCollection, to: newCollection) } }) + .onChange(of: post.status, perform: { value in + if value != PostStatus.published.rawValue { + self.model.editor.saveLastDraft(post) + } else { + self.model.editor.clearLastDraft() + } + DispatchQueue.main.async { + LocalStorageManager().saveContext() + } + }) .onAppear(perform: { self.selectedCollection = collections.first { $0.alias == post.collectionAlias } if post.status != PostStatus.published.rawValue { - self.model.editor.saveLastDraft(post) + DispatchQueue.main.async { + self.model.editor.saveLastDraft(post) + } } else { self.model.editor.clearLastDraft() } }) .onDisappear(perform: { + self.model.editor.clearLastDraft() if post.title.count == 0 && post.body.count == 0 && post.status == PostStatus.local.rawValue && post.updatedDate == nil && post.postId == nil { DispatchQueue.main.async { model.posts.remove(post) } } else if post.status != PostStatus.published.rawValue { DispatchQueue.main.async { LocalStorageManager().saveContext() } } }) } private func publishPost() { DispatchQueue.main.async { LocalStorageManager().saveContext() model.publish(post: post) } #if os(iOS) self.hideKeyboard() #endif } private func sharePost() { // If the post doesn't have a post ID, it isn't published, and therefore can't be shared, so return early. guard let postId = post.postId else { return } var urlString: String if let postSlug = post.slug, let postCollectionAlias = post.collectionAlias { // This post is in a collection, so share the URL as server/collectionAlias/postSlug. urlString = "\(model.account.server)/\((postCollectionAlias))/\((postSlug))" } else { // This is a draft post, so share the URL as server/postID urlString = "\(model.account.server)/\((postId))" } guard let data = URL(string: urlString) else { return } let activityView = UIActivityViewController(activityItems: [data], applicationActivities: nil) UIApplication.shared.windows.first?.rootViewController?.present(activityView, animated: true, completion: nil) if UIDevice.current.userInterfaceIdiom == .pad { activityView.popoverPresentationController?.permittedArrowDirections = .up activityView.popoverPresentationController?.sourceView = UIApplication.shared.windows.first activityView.popoverPresentationController?.sourceRect = CGRect( x: UIScreen.main.bounds.width, y: -125, width: 200, height: 200 ) } } } struct PostEditorView_EmptyPostPreviews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.persistentContainer.viewContext let testPost = WFAPost(context: context) testPost.createdDate = Date() testPost.appearance = "norm" let model = WriteFreelyModel() return PostEditorView(post: testPost) .environment(\.managedObjectContext, context) .environmentObject(model) } } struct PostEditorView_ExistingPostPreviews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.persistentContainer.viewContext let testPost = WFAPost(context: context) testPost.title = "Test Post Title" testPost.body = "Here's some cool sample body text." testPost.createdDate = Date() testPost.appearance = "code" testPost.hasNewerRemoteCopy = true let model = WriteFreelyModel() return PostEditorView(post: testPost) .environment(\.managedObjectContext, context) .environmentObject(model) } }