diff --git a/Shared/Extensions/NSManagedObjectContext+ExecuteAndMergeChanges.swift b/Shared/Extensions/NSManagedObjectContext+ExecuteAndMergeChanges.swift new file mode 100644 index 0000000..96bc4de --- /dev/null +++ b/Shared/Extensions/NSManagedObjectContext+ExecuteAndMergeChanges.swift @@ -0,0 +1,17 @@ +import CoreData + +extension NSManagedObjectContext { + /// Executes the given `NSBatchDeleteRequest` and directly merges the changes to bring the given + /// managed object context up to date. + /// + /// Credit: https://www.avanderlee.com/swift/nsbatchdeleterequest-core-data/ + /// + /// - Parameter batchDeleteRequest: The `NSBatchDeleteRequest` to execute. + /// - Throws: An error if anything went wrong executing the batch deletion. + public func executeAndMergeChanges(using batchDeleteRequest: NSBatchDeleteRequest) throws { + batchDeleteRequest.resultType = .resultTypeObjectIDs + let result = try execute(batchDeleteRequest) as? NSBatchDeleteResult + let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? []] + NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self]) + } +} diff --git a/Shared/LocalStorageManager.swift b/Shared/LocalStorageManager.swift new file mode 100644 index 0000000..aa3e52f --- /dev/null +++ b/Shared/LocalStorageManager.swift @@ -0,0 +1,64 @@ +import CoreData + +#if os(iOS) +import UIKit +#elseif os(macOS) +import AppKit +#endif + +class LocalStorageManager { + static let persistentContainer: NSPersistentContainer = { + let container = NSPersistentContainer(name: "LocalStorageModel") + container.loadPersistentStores { _, error in + container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + if let error = error { + fatalError("Unresolved error loading persistent store: \(error)") + } + } + return container + }() + + init() { + let center = NotificationCenter.default + + #if os(iOS) + let notification = UIApplication.willResignActiveNotification + #elseif os(macOS) + let notification = NSApplication.willResignActiveNotification + #endif + + // We don't need to worry about removing this observer because we're targeting iOS 9+ / macOS 10.11+; the + // system will clean this up the next time it would be posted to. + // See: https://developer.apple.com/documentation/foundation/notificationcenter/1413994-removeobserver + // And: https://developer.apple.com/documentation/foundation/notificationcenter/1407263-removeobserver + // swiftlint:disable:next discarded_notification_center_observer + center.addObserver(forName: notification, object: nil, queue: nil, using: self.saveContextOnResignActive) + } + + func saveContext() { + if LocalStorageManager.persistentContainer.viewContext.hasChanges { + do { + try LocalStorageManager.persistentContainer.viewContext.save() + } catch { + print("Error saving context: \(error)") + } + } + } + + func purgeUserCollections() { + let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: "WFACollection") + let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) + + do { + try LocalStorageManager.persistentContainer.viewContext.executeAndMergeChanges(using: deleteRequest) + } catch { + print("Error: Failed to purge cached collections.") + } + } +} + +private extension LocalStorageManager { + func saveContextOnResignActive(_ notification: Notification) { + saveContext() + } +} diff --git a/Shared/Models/LocalStorageModel.xcdatamodeld/LocalStorageModel.xcdatamodel/contents b/Shared/Models/LocalStorageModel.xcdatamodeld/LocalStorageModel.xcdatamodel/contents new file mode 100644 index 0000000..ff8b974 --- /dev/null +++ b/Shared/Models/LocalStorageModel.xcdatamodeld/LocalStorageModel.xcdatamodel/contents @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Shared/Models/Post.swift b/Shared/Models/Post.swift deleted file mode 100644 index 57e1d12..0000000 --- a/Shared/Models/Post.swift +++ /dev/null @@ -1,108 +0,0 @@ -import Foundation -import WriteFreely - -enum PostStatus { - case local - case edited - case published -} - -class Post: Identifiable, ObservableObject, Hashable { - @Published var wfPost: WFPost - @Published var status: PostStatus - @Published var collection: PostCollection - @Published var hasNewerRemoteCopy: Bool = false - - let id = UUID() - - init( - title: String = "Title", - body: String = "Write your post here...", - createdDate: Date = Date(), - status: PostStatus = .local, - collection: PostCollection = draftsCollection - ) { - self.wfPost = WFPost(body: body, title: title, createdDate: createdDate) - self.status = status - self.collection = collection - } - - convenience init(wfPost: WFPost, in collection: PostCollection = draftsCollection) { - self.init( - title: wfPost.title ?? "", - body: wfPost.body, - createdDate: wfPost.createdDate ?? Date(), - status: .published, - collection: collection - ) - self.wfPost = wfPost - } -} - -extension Post { - static func == (lhs: Post, rhs: Post) -> Bool { - return lhs.id == rhs.id - } - - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } -} - -#if DEBUG -let testPost = Post( - title: "Test Post Title", - body: """ - Here's some cool sample body text. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ultrices \ - posuere dignissim. Vestibulum a libero tempor, lacinia nulla vitae, congue purus. Nunc ac nulla quam. Duis \ - tincidunt eros augue, et volutpat tortor pulvinar ut. Nullam sit amet maximus urna. Phasellus non dignissim lacus.\ - Nulla ac posuere ex. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec \ - non molestie mauris. Suspendisse potenti. Vivamus at erat turpis. - - Pellentesque porttitor gravida tincidunt. Sed vitae eros non metus aliquam hendrerit. Aliquam sed risus suscipit \ - turpis dictum dictum. Duis lacus lectus, dictum vel felis in, rhoncus fringilla felis. Nunc id dolor nisl. Aliquam \ - euismod purus elit. Nullam egestas neque leo, sed aliquet ligula ultrices nec. - """, - createdDate: Date() -) - -let testPostData = [ - Post( - title: "My First Post", - body: "Look at me, creating a first post! That's cool.", - createdDate: Date(timeIntervalSince1970: 1595429452), - status: .published, - collection: userCollection1 - ), - Post( - title: "Post 2: The Quickening", - body: "See, here's the rule about Highlander jokes: _there can be only one_.", - createdDate: Date(timeIntervalSince1970: 1595514125), - status: .edited, - collection: userCollection1 - ), - Post( - title: "The Post Revolutions", - body: "I can never keep the Matrix movie order straight. Why not just call them part 2 and part 3?", - createdDate: Date(timeIntervalSince1970: 1595600006) - ), - Post( - title: "Episode IV: A New Post", - body: "How many movies does this person watch? How many movie-title jokes will they make?", - createdDate: Date(timeIntervalSince1970: 1596219877), - status: .published, - collection: userCollection2 - ), - Post( - title: "Fast (Post) Five", - body: "Look, it was either a Fast and the Furious reference, or a Resident Evil reference." - ), - Post( - title: "Post: The Final Chapter", - body: "And there you have it, a Resident Evil movie reference.", - createdDate: Date(timeIntervalSince1970: 1596043684), - status: .edited, - collection: userCollection3 - ) -] -#endif diff --git a/Shared/Models/PostCollection.swift b/Shared/Models/PostCollection.swift deleted file mode 100644 index 8801cff..0000000 --- a/Shared/Models/PostCollection.swift +++ /dev/null @@ -1,23 +0,0 @@ -import Foundation -import WriteFreely - -struct PostCollection: Identifiable { - let id = UUID() - let title: String - var wfCollection: WFCollection? -} - -extension PostCollection { - static func == (lhs: PostCollection, rhs: PostCollection) -> Bool { - return lhs.id == rhs.id - } -} - -let allPostsCollection = PostCollection(title: "All Posts") -let draftsCollection = PostCollection(title: "Drafts") - -#if DEBUG -let userCollection1 = PostCollection(title: "Collection 1") -let userCollection2 = PostCollection(title: "Collection 2") -let userCollection3 = PostCollection(title: "Collection 3") -#endif diff --git a/Shared/Models/PostStatus.swift b/Shared/Models/PostStatus.swift new file mode 100644 index 0000000..524d265 --- /dev/null +++ b/Shared/Models/PostStatus.swift @@ -0,0 +1,7 @@ +import Foundation + +enum PostStatus: Int32 { + case local = 0 + case edited = 1 + case published = 2 +} diff --git a/Shared/Models/PostStore.swift b/Shared/Models/PostStore.swift deleted file mode 100644 index 60df9b0..0000000 --- a/Shared/Models/PostStore.swift +++ /dev/null @@ -1,66 +0,0 @@ -import Foundation -import WriteFreely - -struct PostStore { - var posts: [Post] - - init(posts: [Post] = []) { - self.posts = posts - } - - mutating func add(_ post: Post) { - posts.append(post) - } - - mutating func purgeAllPosts() { - posts = [] - } - - mutating func update(_ post: Post) { - // Find the local copy in the store - let localCopy = posts.first(where: { $0.id == post.id }) - - // If there's a local copy, update the updatedDate property of its WFPost - if let localCopy = localCopy { - localCopy.wfPost.updatedDate = Date() - } else { - print("Error: Local copy not found") - } - } - - mutating func replace(post: Post, with fetchedPost: WFPost) { - // Find the local copy in the store. - let localCopy = posts.first(where: { $0.id == post.id }) - - // Replace the local copy's wfPost property with the fetched copy. - if let localCopy = localCopy { - localCopy.wfPost = fetchedPost - DispatchQueue.main.async { - localCopy.hasNewerRemoteCopy = false - localCopy.status = .published - } - } else { - print("Error: Local copy not found") - } - } - - mutating func updateStore(with fetchedPosts: [Post]) { - for fetchedPost in fetchedPosts { - // Find the local copy in the store. - let localCopy = posts.first(where: { $0.wfPost.postId == fetchedPost.wfPost.postId }) - - // If there's a local copy, check which is newer; if not, add the fetched post to the store. - if let localCopy = localCopy { - // We do not discard the local copy; we simply set the hasNewerRemoteCopy flag accordingly. - if let remoteCopyUpdatedDate = fetchedPost.wfPost.updatedDate, - let localCopyUpdatedDate = localCopy.wfPost.updatedDate { - localCopy.hasNewerRemoteCopy = remoteCopyUpdatedDate > localCopyUpdatedDate - } else { - print("Error: could not determine which copy of post is newer") - } - } else { - add(fetchedPost) - } - } - } -} diff --git a/Shared/Models/WriteFreelyModel.swift b/Shared/Models/WriteFreelyModel.swift index b70aeb9..bb85444 100644 --- a/Shared/Models/WriteFreelyModel.swift +++ b/Shared/Models/WriteFreelyModel.swift @@ -1,315 +1,377 @@ import Foundation import WriteFreely import Security // MARK: - WriteFreelyModel class WriteFreelyModel: ObservableObject { @Published var account = AccountModel() @Published var preferences = PreferencesModel() - @Published var store = PostStore() - @Published var collections = CollectionListModel(with: []) + @Published var posts = PostListModel() @Published var isLoggingIn: Bool = false - @Published var selectedPost: Post? + @Published var selectedPost: WFAPost? + + #if os(iOS) + @Published var isPresentingSettingsView: Bool = false + #endif private var client: WFClient? private let defaults = UserDefaults.standard init() { // Set the color scheme based on what's been saved in UserDefaults. DispatchQueue.main.async { self.preferences.appearance = self.defaults.integer(forKey: self.preferences.colorSchemeIntegerKey) - } - - #if DEBUG -// for post in testPostData { store.add(post) } - #endif - - DispatchQueue.main.async { self.account.restoreState() if self.account.isLoggedIn { guard let serverURL = URL(string: self.account.server) else { print("Server URL not found") return } guard let token = self.fetchTokenFromKeychain( username: self.account.username, server: self.account.server ) else { print("Could not fetch token from Keychain") return } self.account.login(WFUser(token: token, username: self.account.username)) self.client = WFClient(for: serverURL) self.client?.user = self.account.user - self.collections.clearUserCollection() self.fetchUserCollections() self.fetchUserPosts() } } } } // MARK: - WriteFreelyModel API extension WriteFreelyModel { func login(to server: URL, as username: String, password: String) { isLoggingIn = true account.server = server.absoluteString client = WFClient(for: server) client?.login(username: username, password: password, completion: loginHandler) } func logout() { guard let loggedInClient = client else { do { try purgeTokenFromKeychain(username: account.username, server: account.server) account.logout() } catch { fatalError("Failed to log out persisted state") } return } loggedInClient.logout(completion: logoutHandler) } func fetchUserCollections() { guard let loggedInClient = client else { return } loggedInClient.getUserCollections(completion: fetchUserCollectionsHandler) } func fetchUserPosts() { guard let loggedInClient = client else { return } loggedInClient.getPosts(completion: fetchUserPostsHandler) } - func publish(post: Post) { + func publish(post: WFAPost) { guard let loggedInClient = client else { return } - if let existingPostId = post.wfPost.postId { + var wfPost = WFPost( + body: post.body, + title: post.title.isEmpty ? "" : post.title, + appearance: post.appearance, + language: post.language, + rtl: post.rtl, + createdDate: post.createdDate + ) + + if let existingPostId = post.postId { // This is an existing post. + wfPost.postId = post.postId + wfPost.slug = post.slug + wfPost.updatedDate = post.updatedDate + wfPost.collectionAlias = post.collectionAlias + loggedInClient.updatePost( postId: existingPostId, - updatedPost: post.wfPost, + updatedPost: wfPost, completion: publishHandler ) } else { // This is a new local draft. loggedInClient.createPost( - post: post.wfPost, in: post.collection.wfCollection?.alias, completion: publishHandler + post: wfPost, in: post.collectionAlias, completion: publishHandler ) } } - func updateFromServer(post: Post) { + func updateFromServer(post: WFAPost) { guard let loggedInClient = client else { return } - guard let postId = post.wfPost.postId else { return } + guard let postId = post.postId else { return } DispatchQueue.main.async { self.selectedPost = post } - loggedInClient.getPost(byId: postId, completion: updateFromServerHandler) + if let postCollectionAlias = post.collectionAlias, + let postSlug = post.slug { + loggedInClient.getPost(bySlug: postSlug, from: postCollectionAlias, completion: updateFromServerHandler) + } else { + loggedInClient.getPost(byId: postId, completion: updateFromServerHandler) + } } } private extension WriteFreelyModel { func loginHandler(result: Result) { DispatchQueue.main.async { self.isLoggingIn = false } do { let user = try result.get() fetchUserCollections() fetchUserPosts() saveTokenToKeychain(user.token, username: user.username, server: account.server) DispatchQueue.main.async { self.account.login(user) } } catch WFError.notFound { DispatchQueue.main.async { self.account.currentError = AccountError.usernameNotFound } } catch WFError.unauthorized { DispatchQueue.main.async { self.account.currentError = AccountError.invalidPassword } } catch { - if let error = error as? NSError, error.domain == NSURLErrorDomain, error.code == -1003 { + if (error as NSError).domain == NSURLErrorDomain, + (error as NSError).code == -1003 { DispatchQueue.main.async { self.account.currentError = AccountError.serverNotFound } } } } func logoutHandler(result: Result) { do { _ = try result.get() do { try purgeTokenFromKeychain(username: account.user?.username, server: account.server) client = nil DispatchQueue.main.async { self.account.logout() - self.collections.clearUserCollection() - self.store.purgeAllPosts() + LocalStorageManager().purgeUserCollections() + self.posts.purgeAllPosts() } } catch { print("Something went wrong purging the token from the Keychain.") } } catch WFError.notFound { // The user token is invalid or doesn't exist, so it's been invalidated by the server. Proceed with // purging the token from the Keychain, destroying the client object, and setting the AccountModel to its // logged-out state. do { try purgeTokenFromKeychain(username: account.user?.username, server: account.server) client = nil DispatchQueue.main.async { self.account.logout() - self.collections.clearUserCollection() - self.store.purgeAllPosts() + LocalStorageManager().purgeUserCollections() + self.posts.purgeAllPosts() } } catch { print("Something went wrong purging the token from the Keychain.") } } catch { // We get a 'cannot parse response' (similar to what we were seeing in the Swift package) NSURLError here, // so we're using a hacky workaround — if we get the NSURLError, but the AccountModel still thinks we're // logged in, try calling the logout function again and see what we get. // Conditional cast from 'Error' to 'NSError' always succeeds but is the only way to check error properties. - if let error = error as? NSError, - error.domain == NSURLErrorDomain, - error.code == NSURLErrorCannotParseResponse { + if (error as NSError).domain == NSURLErrorDomain, + (error as NSError).code == NSURLErrorCannotParseResponse { if account.isLoggedIn { self.logout() } } } } func fetchUserCollectionsHandler(result: Result<[WFCollection], Error>) { do { let fetchedCollections = try result.get() - var fetchedCollectionsArray: [PostCollection] = [] for fetchedCollection in fetchedCollections { - var postCollection = PostCollection(title: fetchedCollection.title) - postCollection.wfCollection = fetchedCollection - fetchedCollectionsArray.append(postCollection) + DispatchQueue.main.async { + let localCollection = WFACollection(context: LocalStorageManager.persistentContainer.viewContext) + localCollection.alias = fetchedCollection.alias + localCollection.blogDescription = fetchedCollection.description + localCollection.email = fetchedCollection.email + localCollection.isPublic = fetchedCollection.isPublic ?? false + localCollection.styleSheet = fetchedCollection.styleSheet + localCollection.title = fetchedCollection.title + localCollection.url = fetchedCollection.url + } } DispatchQueue.main.async { - self.collections = CollectionListModel(with: fetchedCollectionsArray) + LocalStorageManager().saveContext() } } catch { print(error) } } func fetchUserPostsHandler(result: Result<[WFPost], Error>) { do { let fetchedPosts = try result.get() - var fetchedPostsArray: [Post] = [] for fetchedPost in fetchedPosts { - var post: Post - if let matchingAlias = fetchedPost.collectionAlias { - let postCollection = ( - collections.userCollections.filter { $0.wfCollection?.alias == matchingAlias } - ).first - post = Post(wfPost: fetchedPost, in: postCollection ?? draftsCollection) + // For each fetched post, we + // 1. check to see if a matching post exists + if let managedPost = posts.userPosts.first(where: { $0.postId == fetchedPost.postId }) { + // If it exists, we set the hasNewerRemoteCopy flag as appropriate. + if let fetchedPostUpdatedDate = fetchedPost.updatedDate, + let localPostUpdatedDate = managedPost.updatedDate { + managedPost.hasNewerRemoteCopy = fetchedPostUpdatedDate > localPostUpdatedDate + } else { + print("Error: could not determine which copy of post is newer") + } } else { - post = Post(wfPost: fetchedPost) + // If it doesn't exist, we create the managed object. + let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext) + managedPost.postId = fetchedPost.postId + managedPost.slug = fetchedPost.slug + managedPost.appearance = fetchedPost.appearance + managedPost.language = fetchedPost.language + managedPost.rtl = fetchedPost.rtl ?? false + managedPost.createdDate = fetchedPost.createdDate + managedPost.updatedDate = fetchedPost.updatedDate + managedPost.title = fetchedPost.title ?? "" + managedPost.body = fetchedPost.body + managedPost.collectionAlias = fetchedPost.collectionAlias + managedPost.status = PostStatus.published.rawValue } - fetchedPostsArray.append(post) } DispatchQueue.main.async { - self.store.updateStore(with: fetchedPostsArray) + LocalStorageManager().saveContext() + self.posts.loadCachedPosts() } } catch { print(error) } } func publishHandler(result: Result) { do { - let wfPost = try result.get() - let foundPostIndex = store.posts.firstIndex(where: { - $0.wfPost.title == wfPost.title && $0.wfPost.body == wfPost.body + let fetchedPost = try result.get() + let foundPostIndex = posts.userPosts.firstIndex(where: { + $0.title == fetchedPost.title && $0.body == fetchedPost.body }) guard let index = foundPostIndex else { return } + let cachedPost = self.posts.userPosts[index] + cachedPost.appearance = fetchedPost.appearance + cachedPost.body = fetchedPost.body + cachedPost.collectionAlias = fetchedPost.collectionAlias + cachedPost.createdDate = fetchedPost.createdDate + cachedPost.language = fetchedPost.language + cachedPost.postId = fetchedPost.postId + cachedPost.rtl = fetchedPost.rtl ?? false + cachedPost.slug = fetchedPost.slug + cachedPost.status = PostStatus.published.rawValue + cachedPost.title = fetchedPost.title ?? "" + cachedPost.updatedDate = fetchedPost.updatedDate DispatchQueue.main.async { - self.store.posts[index].wfPost = wfPost + LocalStorageManager().saveContext() } } catch { print(error) } } func updateFromServerHandler(result: Result) { + // ⚠️ NOTE: + // The API does not return a collection alias, so we take care not to overwrite the + // cached post's collection alias with the 'nil' value from the fetched post. + // See: https://github.com/writeas/writefreely-swift/issues/20 do { let fetchedPost = try result.get() + guard let cachedPost = self.selectedPost else { return } + cachedPost.appearance = fetchedPost.appearance + cachedPost.body = fetchedPost.body + cachedPost.createdDate = fetchedPost.createdDate + cachedPost.language = fetchedPost.language + cachedPost.postId = fetchedPost.postId + cachedPost.rtl = fetchedPost.rtl ?? false + cachedPost.slug = fetchedPost.slug + cachedPost.status = PostStatus.published.rawValue + cachedPost.title = fetchedPost.title ?? "" + cachedPost.updatedDate = fetchedPost.updatedDate + cachedPost.hasNewerRemoteCopy = false DispatchQueue.main.async { - guard let selectedPost = self.selectedPost else { return } - self.store.replace(post: selectedPost, with: fetchedPost) + LocalStorageManager().saveContext() } } catch { print(error) } } } private extension WriteFreelyModel { // MARK: - Keychain Helpers func saveTokenToKeychain(_ token: String, username: String?, server: String) { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecValueData as String: token.data(using: .utf8)!, kSecAttrAccount as String: username ?? "anonymous", kSecAttrService as String: server ] let status = SecItemAdd(query as CFDictionary, nil) guard status == errSecDuplicateItem || status == errSecSuccess else { fatalError("Error storing in Keychain with OSStatus: \(status)") } } func purgeTokenFromKeychain(username: String?, server: String) throws { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: username ?? "anonymous", kSecAttrService as String: server ] let status = SecItemDelete(query as CFDictionary) guard status == errSecSuccess || status == errSecItemNotFound else { fatalError("Error deleting from Keychain with OSStatus: \(status)") } } func fetchTokenFromKeychain(username: String?, server: String) -> String? { let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: username ?? "anonymous", kSecAttrService as String: server, kSecMatchLimit as String: kSecMatchLimitOne, kSecReturnAttributes as String: true, kSecReturnData as String: true ] var secItem: CFTypeRef? let status = SecItemCopyMatching(query as CFDictionary, &secItem) guard status != errSecItemNotFound else { return nil } guard status == errSecSuccess else { fatalError("Error fetching from Keychain with OSStatus: \(status)") } guard let existingSecItem = secItem as? [String: Any], let tokenData = existingSecItem[kSecValueData as String] as? Data, let token = String(data: tokenData, encoding: .utf8) else { return nil } return token } } diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index efdeef3..8cbefca 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -1,29 +1,40 @@ import SwiftUI struct ContentView: View { @EnvironmentObject var model: WriteFreelyModel var body: some View { NavigationView { SidebarView() - PostListView(selectedCollection: allPostsCollection) + PostListView(selectedCollection: nil, showAllPosts: true) Text("Select a post, or create a new local draft.") .foregroundColor(.secondary) } .environmentObject(model) + + #if os(iOS) + EmptyView() + .sheet( + isPresented: $model.isPresentingSettingsView, + onDismiss: { model.isPresentingSettingsView = false }, + content: { + SettingsView() + .environmentObject(model) + } + ) + #endif } } struct ContentView_Previews: PreviewProvider { static var previews: some View { + let context = LocalStorageManager.persistentContainer.viewContext let model = WriteFreelyModel() - model.collections = CollectionListModel(with: [userCollection1, userCollection2, userCollection3]) - for post in testPostData { - model.store.add(post) - } + return ContentView() + .environment(\.managedObjectContext, context) .environmentObject(model) } } diff --git a/Shared/Navigation/SidebarView.swift b/Shared/Navigation/SidebarView.swift index a95ee08..b4c9719 100644 --- a/Shared/Navigation/SidebarView.swift +++ b/Shared/Navigation/SidebarView.swift @@ -1,16 +1,18 @@ import SwiftUI struct SidebarView: View { var body: some View { CollectionListView() } } struct SidebarView_Previews: PreviewProvider { static var previews: some View { + let context = LocalStorageManager.persistentContainer.viewContext let model = WriteFreelyModel() - model.collections = CollectionListModel(with: [userCollection1, userCollection2, userCollection3]) + return SidebarView() + .environment(\.managedObjectContext, context) .environmentObject(model) } } diff --git a/Shared/PostCollection/CollectionListModel.swift b/Shared/PostCollection/CollectionListModel.swift deleted file mode 100644 index 64c7c54..0000000 --- a/Shared/PostCollection/CollectionListModel.swift +++ /dev/null @@ -1,18 +0,0 @@ -import SwiftUI - -class CollectionListModel: ObservableObject { - private(set) var userCollections: [PostCollection] = [] - @Published private(set) var collectionsList: [PostCollection] = [ allPostsCollection, draftsCollection ] - - init(with userCollections: [PostCollection]) { - for userCollection in userCollections { - self.userCollections.append(userCollection) - } - collectionsList.append(contentsOf: self.userCollections) - } - - func clearUserCollection() { - userCollections = [] - collectionsList = [ allPostsCollection, draftsCollection ] - } -} diff --git a/Shared/PostCollection/CollectionListView.swift b/Shared/PostCollection/CollectionListView.swift index 1e06865..1f65af6 100644 --- a/Shared/PostCollection/CollectionListView.swift +++ b/Shared/PostCollection/CollectionListView.swift @@ -1,28 +1,44 @@ import SwiftUI struct CollectionListView: View { @EnvironmentObject var model: WriteFreelyModel + @Environment(\.managedObjectContext) var moc + + @FetchRequest( + entity: WFACollection.entity(), + sortDescriptors: [NSSortDescriptor(keyPath: \WFACollection.title, ascending: true)] + ) var collections: FetchedResults var body: some View { List { - ForEach(model.collections.collectionsList) { collection in - NavigationLink( - destination: PostListView(selectedCollection: collection) - ) { - Text(collection.title) + NavigationLink(destination: PostListView(selectedCollection: nil, showAllPosts: true)) { + Text("All Posts") + } + NavigationLink(destination: PostListView(selectedCollection: nil, showAllPosts: false)) { + Text(model.account.server == "https://write.as" ? "Anonymous" : "Drafts") + } + Section(header: Text("Your Blogs")) { + ForEach(collections, id: \.alias) { collection in + NavigationLink( + destination: PostListView(selectedCollection: collection, showAllPosts: false) + ) { + Text(collection.title) + } } } } .navigationTitle("Collections") .listStyle(SidebarListStyle()) } } -struct CollectionSidebar_Previews: PreviewProvider { +struct CollectionListView_Previews: PreviewProvider { static var previews: some View { + let context = LocalStorageManager.persistentContainer.viewContext let model = WriteFreelyModel() - model.collections = CollectionListModel(with: [userCollection1, userCollection2, userCollection3]) + return CollectionListView() + .environment(\.managedObjectContext, context) .environmentObject(model) } } diff --git a/Shared/PostEditor/PostEditorStatusToolbarView.swift b/Shared/PostEditor/PostEditorStatusToolbarView.swift index ca8ea77..5174b04 100644 --- a/Shared/PostEditor/PostEditorStatusToolbarView.swift +++ b/Shared/PostEditor/PostEditorStatusToolbarView.swift @@ -1,118 +1,87 @@ import SwiftUI struct PostEditorStatusToolbarView: View { #if os(iOS) @Environment(\.horizontalSizeClass) var horizontalSizeClass #endif @EnvironmentObject var model: WriteFreelyModel - @ObservedObject var post: Post + @ObservedObject var post: WFAPost var body: some View { if post.hasNewerRemoteCopy { #if os(iOS) if horizontalSizeClass == .compact { VStack { PostStatusBadgeView(post: post) HStack { Text("⚠️ Newer copy on server. Replace local copy?") .font(.caption) .foregroundColor(.secondary) Button(action: { model.updateFromServer(post: post) }, label: { Image(systemName: "square.and.arrow.down") }) } .padding(.bottom) } .padding(.top) } else { HStack { PostStatusBadgeView(post: post) .padding(.trailing) Text("⚠️ Newer copy on server. Replace local copy?") .font(.callout) .foregroundColor(.secondary) Button(action: { model.updateFromServer(post: post) }, label: { Image(systemName: "square.and.arrow.down") }) } } #else HStack { PostStatusBadgeView(post: post) .padding(.trailing) Text("⚠️ Newer copy on server. Replace local copy?") .font(.callout) .foregroundColor(.secondary) Button(action: { model.updateFromServer(post: post) }, label: { Image(systemName: "square.and.arrow.down") }) } #endif } else { PostStatusBadgeView(post: post) } } } -struct ToolbarView_LocalPreviews: PreviewProvider { +struct PESTView_StandardPreviews: PreviewProvider { static var previews: some View { + let context = LocalStorageManager.persistentContainer.viewContext let model = WriteFreelyModel() - let post = testPost - return PostEditorStatusToolbarView(post: post) - .environmentObject(model) - } -} + let testPost = WFAPost(context: context) + testPost.status = PostStatus.published.rawValue -struct ToolbarView_RemotePreviews: PreviewProvider { - static var previews: some View { - let model = WriteFreelyModel() - let newerRemotePost = Post( - title: testPost.wfPost.title ?? "", - body: testPost.wfPost.body, - createdDate: testPost.wfPost.createdDate ?? Date(), - status: testPost.status, - collection: testPost.collection - ) - newerRemotePost.hasNewerRemoteCopy = true - return PostEditorStatusToolbarView(post: newerRemotePost) + return PostEditorStatusToolbarView(post: testPost) .environmentObject(model) } } -#if os(iOS) -struct ToolbarView_CompactLocalPreviews: PreviewProvider { +struct PESTView_OutdatedLocalCopyPreviews: PreviewProvider { static var previews: some View { + let context = LocalStorageManager.persistentContainer.viewContext let model = WriteFreelyModel() - let post = testPost - return PostEditorStatusToolbarView(post: post) - .environmentObject(model) - .environment(\.horizontalSizeClass, .compact) - } -} -#endif + let testPost = WFAPost(context: context) + testPost.status = PostStatus.published.rawValue + testPost.hasNewerRemoteCopy = true -#if os(iOS) -struct ToolbarView_CompactRemotePreviews: PreviewProvider { - static var previews: some View { - let model = WriteFreelyModel() - let newerRemotePost = Post( - title: testPost.wfPost.title ?? "", - body: testPost.wfPost.body, - createdDate: testPost.wfPost.createdDate ?? Date(), - status: testPost.status, - collection: testPost.collection - ) - newerRemotePost.hasNewerRemoteCopy = true - return PostEditorStatusToolbarView(post: newerRemotePost) + return PostEditorStatusToolbarView(post: testPost) .environmentObject(model) - .environment(\.horizontalSizeClass, .compact) } } -#endif diff --git a/Shared/PostEditor/PostEditorView.swift b/Shared/PostEditor/PostEditorView.swift index 6b9911b..88003be 100644 --- a/Shared/PostEditor/PostEditorView.swift +++ b/Shared/PostEditor/PostEditorView.swift @@ -1,98 +1,76 @@ import SwiftUI struct PostEditorView: View { @EnvironmentObject var model: WriteFreelyModel - @ObservedObject var post: Post + @ObservedObject var post: WFAPost - @State private var isNewPost = false - @State private var title = "" var body: some View { VStack { - TextEditor(text: $title) + TextEditor(text: $post.title) .font(.title) .frame(height: 100) - .onChange(of: title) { _ in - if post.status == .published && post.wfPost.title != title { - post.status = .edited + .onChange(of: post.title) { _ in + if post.status == PostStatus.published.rawValue { + post.status = PostStatus.edited.rawValue } - post.wfPost.title = title } - TextEditor(text: $post.wfPost.body) + TextEditor(text: $post.body) .font(.body) - .onChange(of: post.wfPost.body) { _ in - if post.status == .published { - post.status = .edited + .onChange(of: post.body) { _ in + if post.status == PostStatus.published.rawValue { + post.status = PostStatus.edited.rawValue } } } .padding() .toolbar { ToolbarItem(placement: .status) { PostEditorStatusToolbarView(post: post) } ToolbarItem(placement: .primaryAction) { Button(action: { - model.publish(post: post) - post.status = .published + publishPost() }, label: { Image(systemName: "paperplane") }) } } - .onAppear(perform: { - title = post.wfPost.title ?? "" - checkIfNewPost() - if self.isNewPost { - addNewPostToStore() + .onChange(of: post.hasNewerRemoteCopy, perform: { _ in + if post.status == PostStatus.edited.rawValue && !post.hasNewerRemoteCopy { + post.status = PostStatus.published.rawValue } }) .onDisappear(perform: { - if post.status == .edited { + if post.status < PostStatus.published.rawValue { DispatchQueue.main.async { - model.store.update(post) + LocalStorageManager().saveContext() } } }) } - private func checkIfNewPost() { - self.isNewPost = !model.store.posts.contains(where: { $0.id == post.id }) - } - - private func addNewPostToStore() { - withAnimation { - model.store.add(post) - self.isNewPost = false + private func publishPost() { + DispatchQueue.main.async { + LocalStorageManager().saveContext() + model.posts.loadCachedPosts() + model.publish(post: post) } } } -struct PostEditorView_NewLocalDraftPreviews: PreviewProvider { +struct PostEditorView_Previews: PreviewProvider { static var previews: some View { - PostEditorView(post: Post()) - .environmentObject(WriteFreelyModel()) - } -} + 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() -struct PostEditorView_NewerLocalPostPreviews: PreviewProvider { - static var previews: some View { - return PostEditorView(post: testPost) - .environmentObject(WriteFreelyModel()) - } -} + let model = WriteFreelyModel() -struct PostEditorView_NewerRemotePostPreviews: PreviewProvider { - static var previews: some View { - let newerRemotePost = Post( - title: testPost.wfPost.title ?? "", - body: testPost.wfPost.body, - createdDate: testPost.wfPost.createdDate ?? Date(), - status: testPost.status, - collection: testPost.collection - ) - newerRemotePost.hasNewerRemoteCopy = true - return PostEditorView(post: newerRemotePost) - .environmentObject(WriteFreelyModel()) + return PostEditorView(post: testPost) + .environment(\.managedObjectContext, context) + .environmentObject(model) } } diff --git a/Shared/PostList/PostCellView.swift b/Shared/PostList/PostCellView.swift index 8bb68cf..7ef7e91 100644 --- a/Shared/PostList/PostCellView.swift +++ b/Shared/PostList/PostCellView.swift @@ -1,35 +1,42 @@ import SwiftUI struct PostCellView: View { - @ObservedObject var post: Post + @ObservedObject var post: WFAPost var body: some View { HStack { VStack(alignment: .leading) { - Text(post.wfPost.title ?? "") + Text(post.title) .font(.headline) .lineLimit(1) - Text(buildDateString(from: post.wfPost.createdDate ?? Date())) + Text(buildDateString(from: post.createdDate ?? Date())) .font(.caption) .lineLimit(1) } Spacer() PostStatusBadgeView(post: post) } .padding(5) } func buildDateString(from date: Date) -> String { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .long dateFormatter.timeStyle = .short return dateFormatter.string(from: date) } } struct PostCell_Previews: PreviewProvider { static var previews: some View { - PostCellView(post: testPost) + 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() + + return PostCellView(post: testPost) + .environment(\.managedObjectContext, context) } } diff --git a/Shared/PostList/PostListFilteredView.swift b/Shared/PostList/PostListFilteredView.swift new file mode 100644 index 0000000..e441018 --- /dev/null +++ b/Shared/PostList/PostListFilteredView.swift @@ -0,0 +1,45 @@ +import SwiftUI + +struct PostListFilteredView: View { + var fetchRequest: FetchRequest + + init(filter: String?, showAllPosts: Bool) { + if showAllPosts { + fetchRequest = FetchRequest( + entity: WFAPost.entity(), + sortDescriptors: [NSSortDescriptor(key: "createdDate", ascending: false)] + ) + } else { + if let filter = filter { + fetchRequest = FetchRequest( + entity: WFAPost.entity(), + sortDescriptors: [NSSortDescriptor(key: "createdDate", ascending: false)], + predicate: NSPredicate(format: "collectionAlias == %@", filter) + ) + } else { + fetchRequest = FetchRequest( + entity: WFAPost.entity(), + sortDescriptors: [NSSortDescriptor(key: "createdDate", ascending: false)], + predicate: NSPredicate(format: "collectionAlias == nil") + ) + } + } + } + + var body: some View { + List(fetchRequest.wrappedValue, id: \.self) { post in + NavigationLink(destination: PostEditorView(post: post)) { + PostCellView(post: post) + } + } + } +} + +struct PostListFilteredView_Previews: PreviewProvider { + static var previews: some View { + let context = LocalStorageManager.persistentContainer.viewContext + + return PostListFilteredView(filter: nil, showAllPosts: false) + .environment(\.managedObjectContext, context) + } +} diff --git a/Shared/PostList/PostListModel.swift b/Shared/PostList/PostListModel.swift new file mode 100644 index 0000000..aab1ebd --- /dev/null +++ b/Shared/PostList/PostListModel.swift @@ -0,0 +1,36 @@ +import SwiftUI +import CoreData + +class PostListModel: ObservableObject { + @Published var userPosts = [WFAPost]() + + init() { + loadCachedPosts() + } + + func loadCachedPosts() { + let request = WFAPost.createFetchRequest() + let sort = NSSortDescriptor(key: "createdDate", ascending: false) + request.sortDescriptors = [sort] + + userPosts = [] + do { + let cachedPosts = try LocalStorageManager.persistentContainer.viewContext.fetch(request) + userPosts.append(contentsOf: cachedPosts) + } catch { + print("Error: Failed to fetch cached posts.") + } + } + + func purgeAllPosts() { + userPosts = [] + let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: "WFAPost") + let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) + + do { + try LocalStorageManager.persistentContainer.viewContext.executeAndMergeChanges(using: deleteRequest) + } catch { + print("Error: Failed to purge cached posts.") + } + } +} diff --git a/Shared/PostList/PostListView.swift b/Shared/PostList/PostListView.swift index 4d79bc7..2eafb4e 100644 --- a/Shared/PostList/PostListView.swift +++ b/Shared/PostList/PostListView.swift @@ -1,137 +1,126 @@ import SwiftUI struct PostListView: View { @EnvironmentObject var model: WriteFreelyModel - @State var selectedCollection: PostCollection + @Environment(\.managedObjectContext) var moc - #if os(iOS) - @State private var isPresentingSettings = false - #endif + @State var selectedCollection: WFACollection? + @State var showAllPosts: Bool = false var body: some View { #if os(iOS) GeometryReader { geometry in - List { - ForEach(showPosts(for: selectedCollection)) { post in - NavigationLink( - destination: PostEditorView(post: post) - ) { - PostCellView( - post: post - ) - } - } - } - .environmentObject(model) - .navigationTitle(selectedCollection.title) + PostListFilteredView(filter: selectedCollection?.alias, showAllPosts: showAllPosts) + .navigationTitle( + showAllPosts ? "All Posts" : selectedCollection?.title ?? ( + model.account.server == "https://write.as" ? "Anonymous" : "Drafts" + ) + ) .toolbar { ToolbarItem(placement: .primaryAction) { Button(action: { - let post = Post() - model.store.add(post) + createNewLocalDraft() }, label: { Image(systemName: "square.and.pencil") }) } ToolbarItem(placement: .bottomBar) { HStack { Button(action: { - isPresentingSettings = true + model.isPresentingSettingsView = true }, label: { Image(systemName: "gear") - }).sheet( - isPresented: $isPresentingSettings, - onDismiss: { - isPresentingSettings = false - }, - content: { - SettingsView(isPresented: $isPresentingSettings) - } - ) + }) .padding(.leading) Spacer() Text(pluralizedPostCount(for: showPosts(for: selectedCollection))) .foregroundColor(.secondary) Spacer() Button(action: { reloadFromServer() }, label: { Image(systemName: "arrow.clockwise") }) .disabled(!model.account.isLoggedIn) } .padding() .frame(width: geometry.size.width) } } } #else //if os(macOS) - List { - ForEach(showPosts(for: selectedCollection)) { post in - NavigationLink( - destination: PostEditorView(post: post) - ) { - PostCellView( - post: post - ) - } - } - } - .navigationTitle(selectedCollection.title) + PostListFilteredView(filter: selectedCollection?.alias, showAllPosts: showAllPosts) + .navigationTitle( + showAllPosts ? "All Posts" : selectedCollection?.title ?? ( + model.account.server == "https://write.as" ? "Anonymous" : "Drafts" + ) + ) .navigationSubtitle(pluralizedPostCount(for: showPosts(for: selectedCollection))) .toolbar { Button(action: { - let post = Post() - model.store.add(post) + createNewLocalDraft() }, label: { Image(systemName: "square.and.pencil") }) Button(action: { reloadFromServer() }, label: { Image(systemName: "arrow.clockwise") }) .disabled(!model.account.isLoggedIn) } #endif } - private func pluralizedPostCount(for posts: [Post]) -> String { + private func pluralizedPostCount(for posts: [WFAPost]) -> String { if posts.count == 1 { return "1 post" } else { return "\(posts.count) posts" } } - private func showPosts(for collection: PostCollection) -> [Post] { - if collection == allPostsCollection { - return model.store.posts + private func showPosts(for collection: WFACollection?) -> [WFAPost] { + if showAllPosts { + return model.posts.userPosts } else { - return model.store.posts.filter { - $0.collection.title == collection.title + if let selectedCollection = collection { + return model.posts.userPosts.filter { $0.collectionAlias == selectedCollection.alias } + } else { + return model.posts.userPosts.filter { $0.collectionAlias == nil } } } } private func reloadFromServer() { DispatchQueue.main.async { - model.collections.clearUserCollection() model.fetchUserCollections() model.fetchUserPosts() } } + + private func createNewLocalDraft() { + let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext) + managedPost.createdDate = Date() + managedPost.title = "" + managedPost.body = "" + managedPost.status = PostStatus.local.rawValue + if let selectedCollectionAlias = selectedCollection?.alias { + managedPost.collectionAlias = selectedCollectionAlias + } + DispatchQueue.main.async { + LocalStorageManager().saveContext() + } + } } -struct PostList_Previews: PreviewProvider { +struct PostListView_Previews: PreviewProvider { static var previews: some View { + let context = LocalStorageManager.persistentContainer.viewContext let model = WriteFreelyModel() - for post in testPostData { - model.store.add(post) - } - return Group { - PostListView(selectedCollection: allPostsCollection) - .environmentObject(model) - } + + return PostListView() + .environment(\.managedObjectContext, context) + .environmentObject(model) } } diff --git a/Shared/PostList/PostStatusBadgeView.swift b/Shared/PostList/PostStatusBadgeView.swift index 3698fea..fa691b4 100644 --- a/Shared/PostList/PostStatusBadgeView.swift +++ b/Shared/PostList/PostStatusBadgeView.swift @@ -1,57 +1,70 @@ import SwiftUI struct PostStatusBadgeView: View { - @ObservedObject var post: Post + @ObservedObject var post: WFAPost var body: some View { - let (badgeLabel, badgeColor) = setupBadgeProperties(for: post.status) + let (badgeLabel, badgeColor) = setupBadgeProperties(for: PostStatus(rawValue: post.status)!) Text(badgeLabel) .font(.caption) .fontWeight(.semibold) .foregroundColor(.white) .textCase(.uppercase) .lineLimit(1) .padding(EdgeInsets(top: 2.5, leading: 7.5, bottom: 2.5, trailing: 7.5)) .background(badgeColor) .clipShape(RoundedRectangle(cornerRadius: 5.0, style: .circular)) } func setupBadgeProperties(for status: PostStatus) -> (String, Color) { var badgeLabel: String var badgeColor: Color switch status { case .local: badgeLabel = "local" badgeColor = Color(red: 0.75, green: 0.5, blue: 0.85, opacity: 1.0) case .edited: badgeLabel = "edited" badgeColor = Color(red: 0.75, green: 0.7, blue: 0.1, opacity: 1.0) case .published: badgeLabel = "published" badgeColor = .gray } return (badgeLabel, badgeColor) } } struct PostStatusBadge_LocalDraftPreviews: PreviewProvider { static var previews: some View { - PostStatusBadgeView(post: testPostData[2]) + let context = LocalStorageManager.persistentContainer.viewContext + let testPost = WFAPost(context: context) + testPost.status = PostStatus.local.rawValue + + return PostStatusBadgeView(post: testPost) + .environment(\.managedObjectContext, context) } } struct PostStatusBadge_EditedPreviews: PreviewProvider { static var previews: some View { - Group { - PostStatusBadgeView(post: testPostData[1]) - } + let context = LocalStorageManager.persistentContainer.viewContext + let testPost = WFAPost(context: context) + testPost.status = PostStatus.edited.rawValue + + return PostStatusBadgeView(post: testPost) + .environment(\.managedObjectContext, context) } } struct PostStatusBadge_PublishedPreviews: PreviewProvider { static var previews: some View { - PostStatusBadgeView(post: testPostData[0]) + let context = LocalStorageManager.persistentContainer.viewContext + let testPost = WFAPost(context: context) + testPost.status = PostStatus.published.rawValue + + return PostStatusBadgeView(post: testPost) + .environment(\.managedObjectContext, context) } } diff --git a/Shared/WriteFreely_MultiPlatformApp.swift b/Shared/WriteFreely_MultiPlatformApp.swift index 63c1182..287ccb7 100644 --- a/Shared/WriteFreely_MultiPlatformApp.swift +++ b/Shared/WriteFreely_MultiPlatformApp.swift @@ -1,41 +1,42 @@ 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() .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 } } diff --git a/WFACollection+CoreDataClass.swift b/WFACollection+CoreDataClass.swift new file mode 100644 index 0000000..3f94575 --- /dev/null +++ b/WFACollection+CoreDataClass.swift @@ -0,0 +1,7 @@ +import Foundation +import CoreData + +@objc(WFACollection) +public class WFACollection: NSManagedObject { + +} diff --git a/WFACollection+CoreDataProperties.swift b/WFACollection+CoreDataProperties.swift new file mode 100644 index 0000000..8c8ca02 --- /dev/null +++ b/WFACollection+CoreDataProperties.swift @@ -0,0 +1,22 @@ +import Foundation +import CoreData + +extension WFACollection { + + @nonobjc public class func createFetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "WFACollection") + } + + @NSManaged public var alias: String? + @NSManaged public var blogDescription: String? + @NSManaged public var email: String? + @NSManaged public var isPublic: Bool + @NSManaged public var styleSheet: String? + @NSManaged public var title: String + @NSManaged public var url: String? + +} + +extension WFACollection: Identifiable { + +} diff --git a/WFAPost+CoreDataClass.swift b/WFAPost+CoreDataClass.swift new file mode 100644 index 0000000..3977691 --- /dev/null +++ b/WFAPost+CoreDataClass.swift @@ -0,0 +1,7 @@ +import Foundation +import CoreData + +@objc(WFAPost) +public class WFAPost: NSManagedObject { + +} diff --git a/WFAPost+CoreDataProperties.swift b/WFAPost+CoreDataProperties.swift new file mode 100644 index 0000000..48d4d2b --- /dev/null +++ b/WFAPost+CoreDataProperties.swift @@ -0,0 +1,35 @@ +// +// WFAPost+CoreDataProperties.swift +// WriteFreely-MultiPlatform +// +// Created by Angelo Stavrow on 2020-09-08. +// +// + +import Foundation +import CoreData + +extension WFAPost { + + @nonobjc public class func createFetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "WFAPost") + } + + @NSManaged public var appearance: String? + @NSManaged public var body: String + @NSManaged public var collectionAlias: String? + @NSManaged public var createdDate: Date? + @NSManaged public var language: String? + @NSManaged public var postId: String? + @NSManaged public var rtl: Bool + @NSManaged public var slug: String? + @NSManaged public var status: Int32 + @NSManaged public var title: String + @NSManaged public var updatedDate: Date? + @NSManaged public var hasNewerRemoteCopy: Bool + +} + +extension WFAPost: Identifiable { + +} diff --git a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj index 8480589..445a26f 100644 --- a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj +++ b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj @@ -1,1007 +1,1056 @@ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 52; objects = { /* Begin PBXBuildFile section */ 17120DA124E19839002B9F6C /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388D24DDEC7400DEFF9A /* AccountView.swift */; }; 17120DA224E1985C002B9F6C /* AccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388B24DDC83F00DEFF9A /* AccountModel.swift */; }; 17120DA324E19A42002B9F6C /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5389124DDED0000DEFF9A /* PreferencesView.swift */; }; 17120DA724E19D11002B9F6C /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DA424E19CBF002B9F6C /* SettingsView.swift */; }; 17120DA924E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DA824E1B2F5002B9F6C /* AccountLogoutView.swift */; }; 17120DAA24E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DA824E1B2F5002B9F6C /* AccountLogoutView.swift */; }; 17120DAC24E1B99F002B9F6C /* AccountLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DAB24E1B99F002B9F6C /* AccountLoginView.swift */; }; 17120DAD24E1B99F002B9F6C /* AccountLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DAB24E1B99F002B9F6C /* AccountLoginView.swift */; }; 17120DB224E1E19C002B9F6C /* SettingsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DB124E1E19C002B9F6C /* SettingsHeaderView.swift */; }; - 171BFDF724D49FD400888236 /* PostCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF624D49FD400888236 /* PostCollection.swift */; }; - 171BFDF824D49FD400888236 /* PostCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF624D49FD400888236 /* PostCollection.swift */; }; 171BFDFA24D4AF8300888236 /* CollectionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF924D4AF8300888236 /* CollectionListView.swift */; }; 171BFDFB24D4AF8300888236 /* CollectionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF924D4AF8300888236 /* CollectionListView.swift */; }; 174D313224EC2831006CA9EE /* WriteFreelyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174D313124EC2831006CA9EE /* WriteFreelyModel.swift */; }; 174D313324EC2831006CA9EE /* WriteFreelyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174D313124EC2831006CA9EE /* WriteFreelyModel.swift */; }; 1753F6AC24E431CC00309365 /* MacPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1753F6AB24E431CC00309365 /* MacPreferencesView.swift */; }; - 1756AE6B24CB1E4B00FD7257 /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6A24CB1E4B00FD7257 /* Post.swift */; }; - 1756AE6C24CB1E4B00FD7257 /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6A24CB1E4B00FD7257 /* Post.swift */; }; - 1756AE6E24CB255B00FD7257 /* PostStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6D24CB255B00FD7257 /* PostStore.swift */; }; - 1756AE6F24CB255B00FD7257 /* PostStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6D24CB255B00FD7257 /* PostStore.swift */; }; + 1756AE6E24CB255B00FD7257 /* PostListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6D24CB255B00FD7257 /* PostListModel.swift */; }; + 1756AE6F24CB255B00FD7257 /* PostListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6D24CB255B00FD7257 /* PostListModel.swift */; }; 1756AE7424CB26FA00FD7257 /* PostCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7324CB26FA00FD7257 /* PostCellView.swift */; }; 1756AE7524CB26FA00FD7257 /* PostCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7324CB26FA00FD7257 /* PostCellView.swift */; }; 1756AE7724CB2EDD00FD7257 /* PostEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7624CB2EDD00FD7257 /* PostEditorView.swift */; }; 1756AE7824CB2EDD00FD7257 /* PostEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7624CB2EDD00FD7257 /* PostEditorView.swift */; }; 1756AE7A24CB65DF00FD7257 /* PostListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7924CB65DF00FD7257 /* PostListView.swift */; }; 1756AE7B24CB65DF00FD7257 /* PostListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7924CB65DF00FD7257 /* PostListView.swift */; }; 1756AE8124CB844500FD7257 /* View+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE8024CB844500FD7257 /* View+Keyboard.swift */; }; 1756DBB324FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB224FECDBB00207AB8 /* PostEditorStatusToolbarView.swift */; }; 1756DBB424FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB224FECDBB00207AB8 /* PostEditorStatusToolbarView.swift */; }; - 1762DCB324EB086C0019C4EB /* CollectionListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1762DCB224EB086C0019C4EB /* CollectionListModel.swift */; }; - 1762DCB424EB086C0019C4EB /* CollectionListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1762DCB224EB086C0019C4EB /* CollectionListModel.swift */; }; + 1756DBB724FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */; }; + 1756DBB824FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */; }; + 1756DBBA24FED45500207AB8 /* LocalStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */; }; + 1756DBBB24FED45500207AB8 /* LocalStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */; }; + 1756DC0124FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */; }; + 1756DC0224FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */; }; + 1756DC0324FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */; }; + 1756DC0424FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */; }; 1765F62A24E18EA200C9EBF0 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1765F62924E18EA200C9EBF0 /* SidebarView.swift */; }; 1765F62B24E18EA200C9EBF0 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1765F62924E18EA200C9EBF0 /* SidebarView.swift */; }; 17A5388824DDA31F00DEFF9A /* MacAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388724DDA31F00DEFF9A /* MacAccountView.swift */; }; 17A5388C24DDC83F00DEFF9A /* AccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388B24DDC83F00DEFF9A /* AccountModel.swift */; }; 17A5388F24DDEC7400DEFF9A /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388D24DDEC7400DEFF9A /* AccountView.swift */; }; 17A5389324DDED0000DEFF9A /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5389124DDED0000DEFF9A /* PreferencesView.swift */; }; + 17B996D82502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */; }; + 17B996D92502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */; }; + 17B996DA2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */; }; + 17B996DB2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */; }; + 17C42E622507D8E600072984 /* PostStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E612507D8E600072984 /* PostStatus.swift */; }; + 17C42E632507D8E600072984 /* PostStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E612507D8E600072984 /* PostStatus.swift */; }; + 17C42E652509237800072984 /* PostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E642509237800072984 /* PostListFilteredView.swift */; }; + 17C42E662509237800072984 /* PostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E642509237800072984 /* PostListFilteredView.swift */; }; + 17C42E70250AA12300072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */; }; + 17C42E71250AAFD500072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */; }; 17D435E824E3128F0036B539 /* PreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D435E724E3128F0036B539 /* PreferencesModel.swift */; }; 17D435E924E3128F0036B539 /* PreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D435E724E3128F0036B539 /* PreferencesModel.swift */; }; 17DF329D24C87D3500BCE2E3 /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF329C24C87D3500BCE2E3 /* Tests_iOS.swift */; }; 17DF32A824C87D3500BCE2E3 /* Tests_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF32A724C87D3500BCE2E3 /* Tests_macOS.swift */; }; 17DF32AA24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */; }; 17DF32AB24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */; }; 17DF32AC24C87D3500BCE2E3 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF328224C87D3300BCE2E3 /* ContentView.swift */; }; 17DF32AD24C87D3500BCE2E3 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF328224C87D3300BCE2E3 /* ContentView.swift */; }; 17DF32AE24C87D3500BCE2E3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 17DF328324C87D3500BCE2E3 /* Assets.xcassets */; }; 17DF32AF24C87D3500BCE2E3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 17DF328324C87D3500BCE2E3 /* Assets.xcassets */; }; 17DF32C024C87D7B00BCE2E3 /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 17DF32BF24C87D7B00BCE2E3 /* WriteFreely */; }; 17DF32C324C87D8D00BCE2E3 /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 17DF32C224C87D8D00BCE2E3 /* WriteFreely */; }; 17DF32D524C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF32D424C8CA3400BCE2E3 /* PostStatusBadgeView.swift */; }; 17DF32D624C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF32D424C8CA3400BCE2E3 /* PostStatusBadgeView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 17DF329924C87D3500BCE2E3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 17DF327C24C87D3300BCE2E3 /* Project object */; proxyType = 1; remoteGlobalIDString = 17DF328724C87D3500BCE2E3; remoteInfo = "WriteFreely-MultiPlatform (iOS)"; }; 17DF32A424C87D3500BCE2E3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 17DF327C24C87D3300BCE2E3 /* Project object */; proxyType = 1; remoteGlobalIDString = 17DF328F24C87D3500BCE2E3; remoteInfo = "WriteFreely-MultiPlatform (macOS)"; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 17120DA424E19CBF002B9F6C /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 17120DA824E1B2F5002B9F6C /* AccountLogoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountLogoutView.swift; sourceTree = ""; }; 17120DAB24E1B99F002B9F6C /* AccountLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountLoginView.swift; sourceTree = ""; }; 17120DB124E1E19C002B9F6C /* SettingsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHeaderView.swift; sourceTree = ""; }; - 171BFDF624D49FD400888236 /* PostCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCollection.swift; sourceTree = ""; }; 171BFDF924D4AF8300888236 /* CollectionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionListView.swift; sourceTree = ""; }; 174D313124EC2831006CA9EE /* WriteFreelyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteFreelyModel.swift; sourceTree = ""; }; 1753F6AB24E431CC00309365 /* MacPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPreferencesView.swift; sourceTree = ""; }; - 1756AE6A24CB1E4B00FD7257 /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = ""; }; - 1756AE6D24CB255B00FD7257 /* PostStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostStore.swift; sourceTree = ""; }; + 1756AE6D24CB255B00FD7257 /* PostListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListModel.swift; sourceTree = ""; }; 1756AE7324CB26FA00FD7257 /* PostCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCellView.swift; sourceTree = ""; }; 1756AE7624CB2EDD00FD7257 /* PostEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorView.swift; sourceTree = ""; }; 1756AE7924CB65DF00FD7257 /* PostListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListView.swift; sourceTree = ""; }; 1756AE8024CB844500FD7257 /* View+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Keyboard.swift"; sourceTree = ""; }; 1756DBB224FECDBB00207AB8 /* PostEditorStatusToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorStatusToolbarView.swift; sourceTree = ""; }; - 1762DCB224EB086C0019C4EB /* CollectionListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionListModel.swift; sourceTree = ""; }; + 1756DBB624FED3A400207AB8 /* LocalStorageModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LocalStorageModel.xcdatamodel; sourceTree = ""; }; + 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStorageManager.swift; sourceTree = ""; }; + 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFACollection+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; }; + 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFACollection+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; 1765F62924E18EA200C9EBF0 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = ""; }; 17A5388724DDA31F00DEFF9A /* MacAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacAccountView.swift; sourceTree = ""; }; 17A5388B24DDC83F00DEFF9A /* AccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountModel.swift; sourceTree = ""; }; 17A5388D24DDEC7400DEFF9A /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = ""; }; 17A5389124DDED0000DEFF9A /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = ""; }; + 17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFAPost+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; }; + 17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFAPost+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; + 17C42E612507D8E600072984 /* PostStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostStatus.swift; sourceTree = ""; }; + 17C42E642509237800072984 /* PostListFilteredView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListFilteredView.swift; sourceTree = ""; }; + 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+ExecuteAndMergeChanges.swift"; sourceTree = ""; }; 17D435E724E3128F0036B539 /* PreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesModel.swift; sourceTree = ""; }; 17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteFreely_MultiPlatformApp.swift; sourceTree = ""; }; 17DF328224C87D3300BCE2E3 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 17DF328324C87D3500BCE2E3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 17DF328824C87D3500BCE2E3 /* WriteFreely-MultiPlatform.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "WriteFreely-MultiPlatform.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 17DF328B24C87D3500BCE2E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 17DF329024C87D3500BCE2E3 /* WriteFreely-MultiPlatform.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "WriteFreely-MultiPlatform.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 17DF329224C87D3500BCE2E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 17DF329324C87D3500BCE2E3 /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; }; 17DF329824C87D3500BCE2E3 /* Tests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 17DF329C24C87D3500BCE2E3 /* Tests_iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOS.swift; sourceTree = ""; }; 17DF329E24C87D3500BCE2E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 17DF32A324C87D3500BCE2E3 /* Tests macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 17DF32A724C87D3500BCE2E3 /* Tests_macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOS.swift; sourceTree = ""; }; 17DF32A924C87D3500BCE2E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 17DF32C624C884FF00BCE2E3 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 17DF32C724C8853700BCE2E3 /* CODE_OF_CONDUCT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CODE_OF_CONDUCT.md; sourceTree = ""; }; 17DF32C824C8854B00BCE2E3 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; 17DF32C924C8855E00BCE2E3 /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; }; 17DF32CA24C8856C00BCE2E3 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; 17DF32D424C8CA3400BCE2E3 /* PostStatusBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostStatusBadgeView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 17DF328524C87D3500BCE2E3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 17DF32C024C87D7B00BCE2E3 /* WriteFreely in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF328D24C87D3500BCE2E3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 17DF32C324C87D8D00BCE2E3 /* WriteFreely in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF329524C87D3500BCE2E3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 17DF32A024C87D3500BCE2E3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 17120DA624E19CE2002B9F6C /* Settings */ = { isa = PBXGroup; children = ( 17120DB124E1E19C002B9F6C /* SettingsHeaderView.swift */, 17120DA424E19CBF002B9F6C /* SettingsView.swift */, ); path = Settings; sourceTree = ""; }; 1739B8D324EAFAB700DA7421 /* PostEditor */ = { isa = PBXGroup; children = ( 1756AE7624CB2EDD00FD7257 /* PostEditorView.swift */, 1756DBB224FECDBB00207AB8 /* PostEditorStatusToolbarView.swift */, ); path = PostEditor; sourceTree = ""; }; 1756AE7F24CB841200FD7257 /* Extensions */ = { isa = PBXGroup; children = ( 1756AE8024CB844500FD7257 /* View+Keyboard.swift */, + 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */, ); path = Extensions; sourceTree = ""; }; 1762DCB124EB07680019C4EB /* Models */ = { isa = PBXGroup; children = ( - 1756AE6A24CB1E4B00FD7257 /* Post.swift */, - 171BFDF624D49FD400888236 /* PostCollection.swift */, - 1756AE6D24CB255B00FD7257 /* PostStore.swift */, + 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */, + 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */, + 17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */, + 17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */, + 17C42E612507D8E600072984 /* PostStatus.swift */, 174D313124EC2831006CA9EE /* WriteFreelyModel.swift */, + 1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */, ); path = Models; sourceTree = ""; }; 1765F62C24E1924800C9EBF0 /* Preferences */ = { isa = PBXGroup; children = ( 17D435E724E3128F0036B539 /* PreferencesModel.swift */, 17A5389124DDED0000DEFF9A /* PreferencesView.swift */, ); path = Preferences; sourceTree = ""; }; 17A5388924DDA50500DEFF9A /* Settings */ = { isa = PBXGroup; children = ( 17A5388724DDA31F00DEFF9A /* MacAccountView.swift */, 1753F6AB24E431CC00309365 /* MacPreferencesView.swift */, ); path = Settings; sourceTree = ""; }; 17DF327B24C87D3300BCE2E3 = { isa = PBXGroup; children = ( 17DF32C624C884FF00BCE2E3 /* README.md */, 17DF32C924C8855E00BCE2E3 /* LICENSE.md */, 17DF32CA24C8856C00BCE2E3 /* CHANGELOG.md */, 17DF32C724C8853700BCE2E3 /* CODE_OF_CONDUCT.md */, 17DF32C824C8854B00BCE2E3 /* CONTRIBUTING.md */, 17DF328024C87D3300BCE2E3 /* Shared */, 17DF328A24C87D3500BCE2E3 /* iOS */, 17DF329124C87D3500BCE2E3 /* macOS */, 17DF329B24C87D3500BCE2E3 /* Tests iOS */, 17DF32A624C87D3500BCE2E3 /* Tests macOS */, 17DF328924C87D3500BCE2E3 /* Products */, 17DF32C124C87D8D00BCE2E3 /* Frameworks */, ); sourceTree = ""; }; 17DF328024C87D3300BCE2E3 /* Shared */ = { isa = PBXGroup; children = ( 17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */, + 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */, 17DF328324C87D3500BCE2E3 /* Assets.xcassets */, 17DF32D024C8B75C00BCE2E3 /* Account */, 1756AE7F24CB841200FD7257 /* Extensions */, 1762DCB124EB07680019C4EB /* Models */, 17DF32CC24C8B72300BCE2E3 /* Navigation */, 1739B8D324EAFAB700DA7421 /* PostEditor */, 17DF32D124C8B78500BCE2E3 /* PostList */, 17DF32D224C8B78D00BCE2E3 /* PostCollection */, 1765F62C24E1924800C9EBF0 /* Preferences */, ); path = Shared; sourceTree = ""; }; 17DF328924C87D3500BCE2E3 /* Products */ = { isa = PBXGroup; children = ( 17DF328824C87D3500BCE2E3 /* WriteFreely-MultiPlatform.app */, 17DF329024C87D3500BCE2E3 /* WriteFreely-MultiPlatform.app */, 17DF329824C87D3500BCE2E3 /* Tests iOS.xctest */, 17DF32A324C87D3500BCE2E3 /* Tests macOS.xctest */, ); name = Products; sourceTree = ""; }; 17DF328A24C87D3500BCE2E3 /* iOS */ = { isa = PBXGroup; children = ( 17DF328B24C87D3500BCE2E3 /* Info.plist */, 17120DA624E19CE2002B9F6C /* Settings */, ); path = iOS; sourceTree = ""; }; 17DF329124C87D3500BCE2E3 /* macOS */ = { isa = PBXGroup; children = ( 17DF329224C87D3500BCE2E3 /* Info.plist */, 17DF329324C87D3500BCE2E3 /* macOS.entitlements */, 17A5388924DDA50500DEFF9A /* Settings */, ); path = macOS; sourceTree = ""; }; 17DF329B24C87D3500BCE2E3 /* Tests iOS */ = { isa = PBXGroup; children = ( 17DF329C24C87D3500BCE2E3 /* Tests_iOS.swift */, 17DF329E24C87D3500BCE2E3 /* Info.plist */, ); path = "Tests iOS"; sourceTree = ""; }; 17DF32A624C87D3500BCE2E3 /* Tests macOS */ = { isa = PBXGroup; children = ( 17DF32A724C87D3500BCE2E3 /* Tests_macOS.swift */, 17DF32A924C87D3500BCE2E3 /* Info.plist */, ); path = "Tests macOS"; sourceTree = ""; }; 17DF32C124C87D8D00BCE2E3 /* Frameworks */ = { isa = PBXGroup; children = ( ); name = Frameworks; sourceTree = ""; }; 17DF32CC24C8B72300BCE2E3 /* Navigation */ = { isa = PBXGroup; children = ( 17DF328224C87D3300BCE2E3 /* ContentView.swift */, 1765F62924E18EA200C9EBF0 /* SidebarView.swift */, ); path = Navigation; sourceTree = ""; }; 17DF32D024C8B75C00BCE2E3 /* Account */ = { isa = PBXGroup; children = ( 17A5388B24DDC83F00DEFF9A /* AccountModel.swift */, 17120DAB24E1B99F002B9F6C /* AccountLoginView.swift */, 17120DA824E1B2F5002B9F6C /* AccountLogoutView.swift */, 17A5388D24DDEC7400DEFF9A /* AccountView.swift */, ); path = Account; sourceTree = ""; }; 17DF32D124C8B78500BCE2E3 /* PostList */ = { isa = PBXGroup; children = ( 1756AE7324CB26FA00FD7257 /* PostCellView.swift */, + 1756AE6D24CB255B00FD7257 /* PostListModel.swift */, 1756AE7924CB65DF00FD7257 /* PostListView.swift */, 17DF32D424C8CA3400BCE2E3 /* PostStatusBadgeView.swift */, + 17C42E642509237800072984 /* PostListFilteredView.swift */, ); path = PostList; sourceTree = ""; }; 17DF32D224C8B78D00BCE2E3 /* PostCollection */ = { isa = PBXGroup; children = ( - 1762DCB224EB086C0019C4EB /* CollectionListModel.swift */, 171BFDF924D4AF8300888236 /* CollectionListView.swift */, ); path = PostCollection; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 17DF328724C87D3500BCE2E3 /* WriteFreely-MultiPlatform (iOS) */ = { isa = PBXNativeTarget; buildConfigurationList = 17DF32B224C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "WriteFreely-MultiPlatform (iOS)" */; buildPhases = ( 17DF328424C87D3500BCE2E3 /* Sources */, 17DF328524C87D3500BCE2E3 /* Frameworks */, 17DF328624C87D3500BCE2E3 /* Resources */, 17DF32C424C87E6700BCE2E3 /* ShellScript */, ); buildRules = ( ); dependencies = ( ); name = "WriteFreely-MultiPlatform (iOS)"; packageProductDependencies = ( 17DF32BF24C87D7B00BCE2E3 /* WriteFreely */, ); productName = "WriteFreely-MultiPlatform (iOS)"; productReference = 17DF328824C87D3500BCE2E3 /* WriteFreely-MultiPlatform.app */; productType = "com.apple.product-type.application"; }; 17DF328F24C87D3500BCE2E3 /* WriteFreely-MultiPlatform (macOS) */ = { isa = PBXNativeTarget; buildConfigurationList = 17DF32B524C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "WriteFreely-MultiPlatform (macOS)" */; buildPhases = ( 17DF328C24C87D3500BCE2E3 /* Sources */, 17DF328D24C87D3500BCE2E3 /* Frameworks */, 17DF328E24C87D3500BCE2E3 /* Resources */, 17DF32C524C87FDB00BCE2E3 /* ShellScript */, ); buildRules = ( ); dependencies = ( ); name = "WriteFreely-MultiPlatform (macOS)"; packageProductDependencies = ( 17DF32C224C87D8D00BCE2E3 /* WriteFreely */, ); productName = "WriteFreely-MultiPlatform (macOS)"; productReference = 17DF329024C87D3500BCE2E3 /* WriteFreely-MultiPlatform.app */; productType = "com.apple.product-type.application"; }; 17DF329724C87D3500BCE2E3 /* Tests iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 17DF32B824C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "Tests iOS" */; buildPhases = ( 17DF329424C87D3500BCE2E3 /* Sources */, 17DF329524C87D3500BCE2E3 /* Frameworks */, 17DF329624C87D3500BCE2E3 /* Resources */, ); buildRules = ( ); dependencies = ( 17DF329A24C87D3500BCE2E3 /* PBXTargetDependency */, ); name = "Tests iOS"; productName = "Tests iOS"; productReference = 17DF329824C87D3500BCE2E3 /* Tests iOS.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; 17DF32A224C87D3500BCE2E3 /* Tests macOS */ = { isa = PBXNativeTarget; buildConfigurationList = 17DF32BB24C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "Tests macOS" */; buildPhases = ( 17DF329F24C87D3500BCE2E3 /* Sources */, 17DF32A024C87D3500BCE2E3 /* Frameworks */, 17DF32A124C87D3500BCE2E3 /* Resources */, ); buildRules = ( ); dependencies = ( 17DF32A524C87D3500BCE2E3 /* PBXTargetDependency */, ); name = "Tests macOS"; productName = "Tests macOS"; productReference = 17DF32A324C87D3500BCE2E3 /* Tests macOS.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 17DF327C24C87D3300BCE2E3 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1200; LastUpgradeCheck = 1200; TargetAttributes = { 17DF328724C87D3500BCE2E3 = { CreatedOnToolsVersion = 12.0; }; 17DF328F24C87D3500BCE2E3 = { CreatedOnToolsVersion = 12.0; }; 17DF329724C87D3500BCE2E3 = { CreatedOnToolsVersion = 12.0; TestTargetID = 17DF328724C87D3500BCE2E3; }; 17DF32A224C87D3500BCE2E3 = { CreatedOnToolsVersion = 12.0; TestTargetID = 17DF328F24C87D3500BCE2E3; }; }; }; buildConfigurationList = 17DF327F24C87D3300BCE2E3 /* Build configuration list for PBXProject "WriteFreely-MultiPlatform" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 17DF327B24C87D3300BCE2E3; packageReferences = ( 17DF32BE24C87D7B00BCE2E3 /* XCRemoteSwiftPackageReference "writefreely-swift" */, ); productRefGroup = 17DF328924C87D3500BCE2E3 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 17DF328724C87D3500BCE2E3 /* WriteFreely-MultiPlatform (iOS) */, 17DF328F24C87D3500BCE2E3 /* WriteFreely-MultiPlatform (macOS) */, 17DF329724C87D3500BCE2E3 /* Tests iOS */, 17DF32A224C87D3500BCE2E3 /* Tests macOS */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 17DF328624C87D3500BCE2E3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 17DF32AE24C87D3500BCE2E3 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF328E24C87D3500BCE2E3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 17DF32AF24C87D3500BCE2E3 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF329624C87D3500BCE2E3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 17DF32A124C87D3500BCE2E3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 17DF32C424C87E6700BCE2E3 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# Run SwiftLint on builds\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; 17DF32C524C87FDB00BCE2E3 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# Run SwiftLint on builds\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 17DF328424C87D3500BCE2E3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 17DF32AC24C87D3500BCE2E3 /* ContentView.swift in Sources */, + 17C42E622507D8E600072984 /* PostStatus.swift in Sources */, + 1756DBBA24FED45500207AB8 /* LocalStorageManager.swift in Sources */, 1756AE8124CB844500FD7257 /* View+Keyboard.swift in Sources */, + 17C42E652509237800072984 /* PostListFilteredView.swift in Sources */, 17120DAC24E1B99F002B9F6C /* AccountLoginView.swift in Sources */, 17120DA924E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */, 171BFDFA24D4AF8300888236 /* CollectionListView.swift in Sources */, 1756DBB324FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */, 17120DB224E1E19C002B9F6C /* SettingsHeaderView.swift in Sources */, + 1756DBB724FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */, + 17B996DA2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */, 1756AE7724CB2EDD00FD7257 /* PostEditorView.swift in Sources */, 17DF32D524C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */, 17D435E824E3128F0036B539 /* PreferencesModel.swift in Sources */, 1765F62A24E18EA200C9EBF0 /* SidebarView.swift in Sources */, - 1762DCB324EB086C0019C4EB /* CollectionListModel.swift in Sources */, 1756AE7A24CB65DF00FD7257 /* PostListView.swift in Sources */, - 171BFDF724D49FD400888236 /* PostCollection.swift in Sources */, + 17B996D82502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */, + 1756DC0124FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */, 17DF32AA24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */, 17120DA724E19D11002B9F6C /* SettingsView.swift in Sources */, + 1756DC0324FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */, 17120DA224E1985C002B9F6C /* AccountModel.swift in Sources */, 17120DA324E19A42002B9F6C /* PreferencesView.swift in Sources */, - 1756AE6E24CB255B00FD7257 /* PostStore.swift in Sources */, + 1756AE6E24CB255B00FD7257 /* PostListModel.swift in Sources */, 174D313224EC2831006CA9EE /* WriteFreelyModel.swift in Sources */, - 1756AE6B24CB1E4B00FD7257 /* Post.swift in Sources */, + 17C42E70250AA12300072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */, 17120DA124E19839002B9F6C /* AccountView.swift in Sources */, 1756AE7424CB26FA00FD7257 /* PostCellView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF328C24C87D3500BCE2E3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 171BFDF824D49FD400888236 /* PostCollection.swift in Sources */, 17DF32AD24C87D3500BCE2E3 /* ContentView.swift in Sources */, 1765F62B24E18EA200C9EBF0 /* SidebarView.swift in Sources */, + 1756DBBB24FED45500207AB8 /* LocalStorageManager.swift in Sources */, 174D313324EC2831006CA9EE /* WriteFreelyModel.swift in Sources */, 1756AE7824CB2EDD00FD7257 /* PostEditorView.swift in Sources */, 17D435E924E3128F0036B539 /* PreferencesModel.swift in Sources */, 17120DAA24E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */, 17DF32D624C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */, + 17C42E662509237800072984 /* PostListFilteredView.swift in Sources */, 17120DAD24E1B99F002B9F6C /* AccountLoginView.swift in Sources */, + 17C42E71250AAFD500072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */, 1756AE7B24CB65DF00FD7257 /* PostListView.swift in Sources */, 1753F6AC24E431CC00309365 /* MacPreferencesView.swift in Sources */, + 1756DC0424FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */, + 17B996DB2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */, 171BFDFB24D4AF8300888236 /* CollectionListView.swift in Sources */, 17DF32AB24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */, 17A5388C24DDC83F00DEFF9A /* AccountModel.swift in Sources */, - 1762DCB424EB086C0019C4EB /* CollectionListModel.swift in Sources */, + 17B996D92502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */, + 1756DBB824FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */, 17A5389324DDED0000DEFF9A /* PreferencesView.swift in Sources */, - 1756AE6F24CB255B00FD7257 /* PostStore.swift in Sources */, + 1756AE6F24CB255B00FD7257 /* PostListModel.swift in Sources */, + 1756DC0224FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */, 1756DBB424FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */, - 1756AE6C24CB1E4B00FD7257 /* Post.swift in Sources */, 17A5388F24DDEC7400DEFF9A /* AccountView.swift in Sources */, 1756AE7524CB26FA00FD7257 /* PostCellView.swift in Sources */, 17A5388824DDA31F00DEFF9A /* MacAccountView.swift in Sources */, + 17C42E632507D8E600072984 /* PostStatus.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF329424C87D3500BCE2E3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 17DF329D24C87D3500BCE2E3 /* Tests_iOS.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF329F24C87D3500BCE2E3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 17DF32A824C87D3500BCE2E3 /* Tests_macOS.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 17DF329A24C87D3500BCE2E3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 17DF328724C87D3500BCE2E3 /* WriteFreely-MultiPlatform (iOS) */; targetProxy = 17DF329924C87D3500BCE2E3 /* PBXContainerItemProxy */; }; 17DF32A524C87D3500BCE2E3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 17DF328F24C87D3500BCE2E3 /* WriteFreely-MultiPlatform (macOS) */; targetProxy = 17DF32A424C87D3500BCE2E3 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 17DF32B024C87D3500BCE2E3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 17DF32B124C87D3500BCE2E3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 17DF32B324C87D3500BCE2E3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = TPPAB4YBA6; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = iOS/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 0.0.3; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; PRODUCT_NAME = "WriteFreely-MultiPlatform"; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 17DF32B424C87D3500BCE2E3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = TPPAB4YBA6; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = iOS/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); MARKETING_VERSION = 0.0.3; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; PRODUCT_NAME = "WriteFreely-MultiPlatform"; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 17DF32B624C87D3500BCE2E3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = TPPAB4YBA6; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = macOS/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 0.0.3; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; PRODUCT_NAME = "WriteFreely-MultiPlatform"; SDKROOT = macosx; SWIFT_VERSION = 5.0; }; name = Debug; }; 17DF32B724C87D3500BCE2E3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = TPPAB4YBA6; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = macOS/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 0.0.3; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; PRODUCT_NAME = "WriteFreely-MultiPlatform"; SDKROOT = macosx; SWIFT_VERSION = 5.0; }; name = Release; }; 17DF32B924C87D3500BCE2E3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = TPPAB4YBA6; INFOPLIST_FILE = "Tests iOS/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = "WriteFreely-MultiPlatform (iOS)"; }; name = Debug; }; 17DF32BA24C87D3500BCE2E3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = TPPAB4YBA6; INFOPLIST_FILE = "Tests iOS/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = "WriteFreely-MultiPlatform (iOS)"; VALIDATE_PRODUCT = YES; }; name = Release; }; 17DF32BC24C87D3500BCE2E3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = TPPAB4YBA6; INFOPLIST_FILE = "Tests macOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.Tests-macOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_VERSION = 5.0; TEST_TARGET_NAME = "WriteFreely-MultiPlatform (macOS)"; }; name = Debug; }; 17DF32BD24C87D3500BCE2E3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = TPPAB4YBA6; INFOPLIST_FILE = "Tests macOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.Tests-macOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_VERSION = 5.0; TEST_TARGET_NAME = "WriteFreely-MultiPlatform (macOS)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 17DF327F24C87D3300BCE2E3 /* Build configuration list for PBXProject "WriteFreely-MultiPlatform" */ = { isa = XCConfigurationList; buildConfigurations = ( 17DF32B024C87D3500BCE2E3 /* Debug */, 17DF32B124C87D3500BCE2E3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 17DF32B224C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "WriteFreely-MultiPlatform (iOS)" */ = { isa = XCConfigurationList; buildConfigurations = ( 17DF32B324C87D3500BCE2E3 /* Debug */, 17DF32B424C87D3500BCE2E3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 17DF32B524C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "WriteFreely-MultiPlatform (macOS)" */ = { isa = XCConfigurationList; buildConfigurations = ( 17DF32B624C87D3500BCE2E3 /* Debug */, 17DF32B724C87D3500BCE2E3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 17DF32B824C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "Tests iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 17DF32B924C87D3500BCE2E3 /* Debug */, 17DF32BA24C87D3500BCE2E3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 17DF32BB24C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "Tests macOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 17DF32BC24C87D3500BCE2E3 /* Debug */, 17DF32BD24C87D3500BCE2E3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 17DF32BE24C87D7B00BCE2E3 /* XCRemoteSwiftPackageReference "writefreely-swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "git@github.com:writeas/writefreely-swift.git"; requirement = { kind = upToNextMajorVersion; minimumVersion = 0.1.1; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 17DF32BF24C87D7B00BCE2E3 /* WriteFreely */ = { isa = XCSwiftPackageProductDependency; package = 17DF32BE24C87D7B00BCE2E3 /* XCRemoteSwiftPackageReference "writefreely-swift" */; productName = WriteFreely; }; 17DF32C224C87D8D00BCE2E3 /* WriteFreely */ = { isa = XCSwiftPackageProductDependency; package = 17DF32BE24C87D7B00BCE2E3 /* XCRemoteSwiftPackageReference "writefreely-swift" */; productName = WriteFreely; }; /* End XCSwiftPackageProductDependency section */ + +/* Begin XCVersionGroup section */ + 1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 1756DBB624FED3A400207AB8 /* LocalStorageModel.xcdatamodel */, + ); + currentVersion = 1756DBB624FED3A400207AB8 /* LocalStorageModel.xcdatamodel */; + path = LocalStorageModel.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ }; rootObject = 17DF327C24C87D3300BCE2E3 /* Project object */; } diff --git a/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist b/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist index 6cd8075..2723ebe 100644 --- a/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist @@ -1,19 +1,19 @@ SchemeUserState WriteFreely-MultiPlatform (iOS).xcscheme_^#shared#^_ orderHint - 1 + 0 WriteFreely-MultiPlatform (macOS).xcscheme_^#shared#^_ orderHint - 0 + 1 diff --git a/iOS/Settings/SettingsHeaderView.swift b/iOS/Settings/SettingsHeaderView.swift index 75121d7..9177ec4 100644 --- a/iOS/Settings/SettingsHeaderView.swift +++ b/iOS/Settings/SettingsHeaderView.swift @@ -1,26 +1,26 @@ import SwiftUI struct SettingsHeaderView: View { - @Binding var isPresented: Bool + @Environment(\.presentationMode) var presentationMode var body: some View { HStack { Text("Settings") .font(.largeTitle) .fontWeight(.bold) Spacer() Button(action: { - isPresented = false + presentationMode.wrappedValue.dismiss() }, label: { Image(systemName: "xmark.circle") }) } .padding() } } struct SettingsHeaderView_Previews: PreviewProvider { static var previews: some View { - SettingsHeaderView(isPresented: .constant(true)) + SettingsHeaderView() } } diff --git a/iOS/Settings/SettingsView.swift b/iOS/Settings/SettingsView.swift index c4718c5..f6b60fc 100644 --- a/iOS/Settings/SettingsView.swift +++ b/iOS/Settings/SettingsView.swift @@ -1,29 +1,27 @@ import SwiftUI struct SettingsView: View { @EnvironmentObject var model: WriteFreelyModel - @Binding var isPresented: Bool - var body: some View { VStack { - SettingsHeaderView(isPresented: $isPresented) + SettingsHeaderView() Form { Section(header: Text("Login Details")) { AccountView() } Section(header: Text("Appearance")) { PreferencesView(preferences: model.preferences) } } } // .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info. } } struct SettingsView_Previews: PreviewProvider { static var previews: some View { - SettingsView(isPresented: .constant(true)) + SettingsView() .environmentObject(WriteFreelyModel()) } }