diff --git a/Shared/Account/AccountLoginView.swift b/Shared/Account/AccountLoginView.swift index 8284b95..c7ea5ea 100644 --- a/Shared/Account/AccountLoginView.swift +++ b/Shared/Account/AccountLoginView.swift @@ -1,106 +1,109 @@ import SwiftUI struct AccountLoginView: View { @EnvironmentObject var model: WriteFreelyModel + @EnvironmentObject var errorHandling: ErrorHandling @State private var alertMessage: String = "" @State private var username: String = "" @State private var password: String = "" @State private var server: String = "" var body: some View { VStack { Text("Log in to publish and share your posts.") .font(.caption) .foregroundColor(.secondary) HStack { Image(systemName: "person.circle") .foregroundColor(.gray) #if os(iOS) TextField("Username", text: $username) .autocapitalization(.none) .disableAutocorrection(true) .textFieldStyle(RoundedBorderTextFieldStyle()) #else TextField("Username", text: $username) #endif } HStack { Image(systemName: "lock.circle") .foregroundColor(.gray) #if os(iOS) SecureField("Password", text: $password) .autocapitalization(.none) .disableAutocorrection(true) .textFieldStyle(RoundedBorderTextFieldStyle()) #else SecureField("Password", text: $password) #endif } HStack { Image(systemName: "link.circle") .foregroundColor(.gray) #if os(iOS) TextField("Server URL", text: $server) .keyboardType(.URL) .autocapitalization(.none) .disableAutocorrection(true) .textFieldStyle(RoundedBorderTextFieldStyle()) #else TextField("Server URL", text: $server) #endif } Spacer() if model.isLoggingIn { ProgressView("Logging in...") .padding() } else { Button(action: { #if os(iOS) hideKeyboard() #endif // If the server string is not prefixed with a scheme, prepend "https://" to it. if !(server.hasPrefix("https://") || server.hasPrefix("http://")) { server = "https://\(server)" } // We only need the protocol and host from the URL, so drop anything else. let url = URLComponents(string: server) if let validURL = url { let scheme = validURL.scheme let host = validURL.host var hostURL = URLComponents() hostURL.scheme = scheme hostURL.host = host server = hostURL.string ?? server model.login( to: URL(string: server)!, as: username, password: password ) } else { - model.loginErrorMessage = AccountError.invalidServerURL.localizedDescription - model.isPresentingLoginErrorAlert = true + self.errorHandling.handle(error: AccountError.invalidServerURL) } }, label: { Text("Log In") }) .disabled( model.account.isLoggedIn || (username.isEmpty || password.isEmpty || server.isEmpty) ) .padding() } } - .alert(isPresented: $model.isPresentingLoginErrorAlert) { - Alert( - title: Text("Error Logging In"), - message: Text(model.loginErrorMessage ?? "An unknown error occurred while trying to login."), - dismissButton: .default(Text("OK")) - ) + .onChange(of: model.hasError) { value in + if value { + if let error = model.currentError { + self.errorHandling.handle(error: error) + } else { + self.errorHandling.handle(error: AppError.genericError) + } + model.hasError = false + } } } } struct AccountLoginView_Previews: PreviewProvider { static var previews: some View { AccountLoginView() .environmentObject(WriteFreelyModel()) } } diff --git a/Shared/Account/AccountModel.swift b/Shared/Account/AccountModel.swift index 94171ec..25103ba 100644 --- a/Shared/Account/AccountModel.swift +++ b/Shared/Account/AccountModel.swift @@ -1,84 +1,32 @@ import SwiftUI import WriteFreely -enum AccountError: Error { - case invalidPassword - case usernameNotFound - case serverNotFound - case invalidServerURL - case couldNotSaveTokenToKeychain - case couldNotFetchTokenFromKeychain - case couldNotDeleteTokenFromKeychain -} - -extension AccountError: LocalizedError { - public var errorDescription: String? { - switch self { - case .serverNotFound: - return NSLocalizedString( - "The server could not be found. Please check the information you've entered and try again.", - comment: "" - ) - case .invalidPassword: - return NSLocalizedString( - "Invalid password. Please check that you've entered your password correctly and try logging in again.", - comment: "" - ) - case .usernameNotFound: - return NSLocalizedString( - "Username not found. Did you use your email address by mistake?", - comment: "" - ) - case .invalidServerURL: - return NSLocalizedString( - "Please enter a valid instance domain name. It should look like \"https://example.com\" or \"write.as\".", // swiftlint:disable:this line_length - comment: "" - ) - case .couldNotSaveTokenToKeychain: - return NSLocalizedString( - "There was a problem trying to save your access token to the device, please try logging in again.", - comment: "" - ) - case .couldNotFetchTokenFromKeychain: - return NSLocalizedString( - "There was a problem trying to fetch your access token from the device, please try logging in again.", - comment: "" - ) - case .couldNotDeleteTokenFromKeychain: - return NSLocalizedString( - "There was a problem trying to delete your access token from the device, please try logging out again.", - comment: "" - ) - } - } -} - struct AccountModel { @AppStorage(WFDefaults.isLoggedIn, store: UserDefaults.shared) var isLoggedIn: Bool = false private let defaults = UserDefaults.shared var server: String = "" var username: String = "" private(set) var user: WFUser? mutating func login(_ user: WFUser) { self.user = user self.username = user.username ?? "" self.isLoggedIn = true defaults.set(user.username, forKey: WFDefaults.usernameStringKey) defaults.set(server, forKey: WFDefaults.serverStringKey) } mutating func logout() { self.user = nil self.isLoggedIn = false defaults.removeObject(forKey: WFDefaults.usernameStringKey) defaults.removeObject(forKey: WFDefaults.serverStringKey) } mutating func restoreState() { server = defaults.string(forKey: WFDefaults.serverStringKey) ?? "" username = defaults.string(forKey: WFDefaults.usernameStringKey) ?? "" } } diff --git a/Shared/Account/AccountView.swift b/Shared/Account/AccountView.swift index 4ff4527..9241026 100644 --- a/Shared/Account/AccountView.swift +++ b/Shared/Account/AccountView.swift @@ -1,26 +1,27 @@ import SwiftUI struct AccountView: View { @EnvironmentObject var model: WriteFreelyModel var body: some View { if model.account.isLoggedIn { HStack { Spacer() AccountLogoutView() Spacer() } .padding() } else { AccountLoginView() + .withErrorHandling() .padding(.top) } } } struct AccountLogin_Previews: PreviewProvider { static var previews: some View { AccountView() .environmentObject(WriteFreelyModel()) } } diff --git a/Shared/ErrorHandling/ErrorConstants.swift b/Shared/ErrorHandling/ErrorConstants.swift new file mode 100644 index 0000000..eb1f037 --- /dev/null +++ b/Shared/ErrorHandling/ErrorConstants.swift @@ -0,0 +1,150 @@ +import Foundation + +// MARK: - Network Errors + +enum NetworkError: Error { + case noConnectionError +} + +extension NetworkError: LocalizedError { + public var errorDescription: String? { + switch self { + case .noConnectionError: + return NSLocalizedString( + "There is no internet connection at the moment. Please reconnect or try again later.", + comment: "" + ) + } + } +} + +// MARK: - Keychain Errors + +enum KeychainError: Error { + case couldNotStoreAccessToken + case couldNotPurgeAccessToken + case couldNotFetchAccessToken +} + +extension KeychainError: LocalizedError { + public var errorDescription: String? { + switch self { + case .couldNotStoreAccessToken: + return NSLocalizedString("There was a problem storing your access token in the Keychain.", comment: "") + case .couldNotPurgeAccessToken: + return NSLocalizedString("Something went wrong purging the token from the Keychain.", comment: "") + case .couldNotFetchAccessToken: + return NSLocalizedString("Something went wrong fetching the token from the Keychain.", comment: "") + } + } +} + +// MARK: - Account Errors + +enum AccountError: Error { + case invalidPassword + case usernameNotFound + case serverNotFound + case invalidServerURL + case unknownLoginError + case genericAuthError +} + +extension AccountError: LocalizedError { + public var errorDescription: String? { + switch self { + case .serverNotFound: + return NSLocalizedString( + "The server could not be found. Please check the information you've entered and try again.", + comment: "" + ) + case .invalidPassword: + return NSLocalizedString( + "Invalid password. Please check that you've entered your password correctly and try logging in again.", + comment: "" + ) + case .usernameNotFound: + return NSLocalizedString( + "Username not found. Did you use your email address by mistake?", + comment: "" + ) + case .invalidServerURL: + return NSLocalizedString( + "Please enter a valid instance domain name. It should look like \"https://example.com\" or \"write.as\".", // swiftlint:disable:this line_length + comment: "" + ) + case .genericAuthError: + return NSLocalizedString("Something went wrong, please try logging in again.", comment: "") + case .unknownLoginError: + return NSLocalizedString("An unknown error occurred while trying to login.", comment: "") + } + } +} + +// MARK: - Local Store Errors + +enum LocalStoreError: Error { + case couldNotSaveContext + case couldNotFetchCollections + case couldNotFetchPosts(String) + case couldNotPurgePublishedPosts + case couldNotPurgeCollections + case couldNotLoadStore(String) + case couldNotMigrateStore(String) + case couldNotDeleteStoreAfterMigration(String) + case genericError(String) +} + +extension LocalStoreError: LocalizedError { + public var errorDescription: String? { + switch self { + case .couldNotSaveContext: + return NSLocalizedString("Error saving context", comment: "") + case .couldNotFetchCollections: + return NSLocalizedString("Failed to fetch blogs from local store.", comment: "") + case .couldNotFetchPosts(let postFilter): + if postFilter.isEmpty { + return NSLocalizedString("Failed to fetch posts from local store.", comment: "") + } else { + return NSLocalizedString("Failed to fetch \(postFilter) posts from local store.", comment: "") + } + case .couldNotPurgePublishedPosts: + return NSLocalizedString("Failed to purge published posts from local store.", comment: "") + case .couldNotPurgeCollections: + return NSLocalizedString("Failed to purge cached collections", comment: "") + case .couldNotLoadStore(let errorDescription): + return NSLocalizedString("Something went wrong loading local store: \(errorDescription)", comment: "") + case .couldNotMigrateStore(let errorDescription): + return NSLocalizedString("Something went wrong migrating local store: \(errorDescription)", comment: "") + case .couldNotDeleteStoreAfterMigration(let errorDescription): + return NSLocalizedString("Something went wrong deleting old store: \(errorDescription)", comment: "") + case .genericError(let customContent): + if customContent.isEmpty { + return NSLocalizedString("Something went wrong accessing device storage", comment: "") + } else { + return NSLocalizedString(customContent, comment: "") + } + } + } +} + +// MARK: - Application Errors + +enum AppError: Error { + case couldNotGetLoggedInClient + case couldNotGetPostId + case genericError +} + +extension AppError: LocalizedError { + public var errorDescription: String? { + switch self { + case .couldNotGetLoggedInClient: + return NSLocalizedString("Something went wrong trying to access the WriteFreely client.", comment: "") + case .couldNotGetPostId: + return NSLocalizedString("Something went wrong trying to get the post's unique ID.", comment: "") + case .genericError: + return NSLocalizedString("Something went wrong", comment: "") + } + } +} diff --git a/Shared/ErrorHandling/ErrorHandling.swift b/Shared/ErrorHandling/ErrorHandling.swift new file mode 100644 index 0000000..8644b33 --- /dev/null +++ b/Shared/ErrorHandling/ErrorHandling.swift @@ -0,0 +1,42 @@ +// Based on https://www.ralfebert.com/swiftui/generic-error-handling/ + +import SwiftUI + +struct ErrorAlert: Identifiable { + var id = UUID() + var message: String + var dismissAction: (() -> Void)? +} + +class ErrorHandling: ObservableObject { + @Published var currentAlert: ErrorAlert? + + func handle(error: Error) { + currentAlert = ErrorAlert(message: error.localizedDescription) + } +} + +struct HandleErrorByShowingAlertViewModifier: ViewModifier { + @StateObject var errorHandling = ErrorHandling() + + func body(content: Content) -> some View { + content + .environmentObject(errorHandling) + .background( + EmptyView() + .alert(item: $errorHandling.currentAlert) { currentAlert in + Alert(title: Text("Error"), + message: Text(currentAlert.message), + dismissButton: .default(Text("OK")) { + currentAlert.dismissAction?() + }) + } + ) + } +} + +extension View { + func withErrorHandling() -> some View { + modifier(HandleErrorByShowingAlertViewModifier()) + } +} diff --git a/Shared/Extensions/WriteFreelyModel+API.swift b/Shared/Extensions/WriteFreelyModel+API.swift index 939cf76..23e3b61 100644 --- a/Shared/Extensions/WriteFreelyModel+API.swift +++ b/Shared/Extensions/WriteFreelyModel+API.swift @@ -1,152 +1,152 @@ import Foundation import WriteFreely extension WriteFreelyModel { func login(to server: URL, as username: String, password: String) { if !hasNetworkConnection { - isPresentingNetworkErrorAlert = true + self.currentError = NetworkError.noConnectionError return } let secureProtocolPrefix = "https://" let insecureProtocolPrefix = "http://" var serverString = server.absoluteString // If there's neither an http or https prefix, prepend "https://" to the server string. if !(serverString.hasPrefix(secureProtocolPrefix) || serverString.hasPrefix(insecureProtocolPrefix)) { serverString = secureProtocolPrefix + serverString } // If the server string is prefixed with http, upgrade to https before attempting to login. if serverString.hasPrefix(insecureProtocolPrefix) { serverString = serverString.replacingOccurrences(of: insecureProtocolPrefix, with: secureProtocolPrefix) } isLoggingIn = true var serverURL = URL(string: serverString)! if !serverURL.path.isEmpty { serverURL.deleteLastPathComponent() } account.server = serverURL.absoluteString client = WFClient(for: serverURL) client?.login(username: username, password: password, completion: loginHandler) } func logout() { if !hasNetworkConnection { - DispatchQueue.main.async { self.isPresentingNetworkErrorAlert = true } + self.currentError = NetworkError.noConnectionError return } 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() { if !hasNetworkConnection { - DispatchQueue.main.async { self.isPresentingNetworkErrorAlert = true } + self.currentError = NetworkError.noConnectionError return } guard let loggedInClient = client else { return } // We're starting the network request. DispatchQueue.main.async { self.isProcessingRequest = true } loggedInClient.getUserCollections(completion: fetchUserCollectionsHandler) } func fetchUserPosts() { if !hasNetworkConnection { - DispatchQueue.main.async { self.isPresentingNetworkErrorAlert = true } + self.currentError = NetworkError.noConnectionError return } guard let loggedInClient = client else { return } // We're starting the network request. DispatchQueue.main.async { self.isProcessingRequest = true } loggedInClient.getPosts(completion: fetchUserPostsHandler) } func publish(post: WFAPost) { postToUpdate = nil if !hasNetworkConnection { - DispatchQueue.main.async { self.isPresentingNetworkErrorAlert = true } + self.currentError = NetworkError.noConnectionError return } guard let loggedInClient = client else { return } // We're starting the network request. DispatchQueue.main.async { self.isProcessingRequest = true } if post.language == nil { if let languageCode = Locale.current.languageCode { post.language = languageCode post.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft } } var wfPost = WFPost( body: post.body, title: post.title.isEmpty ? "" : post.title, appearance: post.appearance, language: post.language, rtl: post.rtl, createdDate: post.status == PostStatus.local.rawValue ? Date() : post.createdDate ) if let existingPostId = post.postId { // This is an existing post. postToUpdate = post wfPost.postId = post.postId loggedInClient.updatePost( postId: existingPostId, updatedPost: wfPost, completion: publishHandler ) } else { // This is a new local draft. loggedInClient.createPost( post: wfPost, in: post.collectionAlias, completion: publishHandler ) } } func updateFromServer(post: WFAPost) { if !hasNetworkConnection { - DispatchQueue.main.async { self.isPresentingNetworkErrorAlert = true } + self.currentError = NetworkError.noConnectionError return } guard let loggedInClient = client else { return } guard let postId = post.postId else { return } // We're starting the network request. DispatchQueue.main.async { self.selectedPost = post self.isProcessingRequest = true } loggedInClient.getPost(byId: postId, completion: updateFromServerHandler) } func move(post: WFAPost, from oldCollection: WFACollection?, to newCollection: WFACollection?) { if !hasNetworkConnection { - DispatchQueue.main.async { self.isPresentingNetworkErrorAlert = true } + self.currentError = NetworkError.noConnectionError return } guard let loggedInClient = client, let postId = post.postId else { return } // We're starting the network request. DispatchQueue.main.async { self.isProcessingRequest = true } selectedPost = post post.collectionAlias = newCollection?.alias loggedInClient.movePost(postId: postId, to: newCollection?.alias, completion: movePostHandler) } } diff --git a/Shared/Extensions/WriteFreelyModel+APIHandlers.swift b/Shared/Extensions/WriteFreelyModel+APIHandlers.swift index 804dd41..6d0d147 100644 --- a/Shared/Extensions/WriteFreelyModel+APIHandlers.swift +++ b/Shared/Extensions/WriteFreelyModel+APIHandlers.swift @@ -1,278 +1,257 @@ import Foundation import WriteFreely extension WriteFreelyModel { func loginHandler(result: Result) { DispatchQueue.main.async { self.isLoggingIn = false } do { let user = try result.get() fetchUserCollections() fetchUserPosts() do { try saveTokenToKeychain(user.token, username: user.username, server: account.server) DispatchQueue.main.async { self.account.login(user) } } catch { - DispatchQueue.main.async { - self.loginErrorMessage = "There was a problem storing your access token to the Keychain." - self.isPresentingLoginErrorAlert = true - } + self.currentError = KeychainError.couldNotStoreAccessToken } } catch WFError.notFound { - DispatchQueue.main.async { - self.loginErrorMessage = AccountError.usernameNotFound.localizedDescription - self.isPresentingLoginErrorAlert = true - } + self.currentError = AccountError.usernameNotFound } catch WFError.unauthorized { - DispatchQueue.main.async { - self.loginErrorMessage = AccountError.invalidPassword.localizedDescription - self.isPresentingLoginErrorAlert = true - } + self.currentError = AccountError.invalidPassword } catch { if (error as NSError).domain == NSURLErrorDomain, (error as NSError).code == -1003 { - DispatchQueue.main.async { - self.loginErrorMessage = AccountError.serverNotFound.localizedDescription - self.isPresentingLoginErrorAlert = true - } + self.currentError = AccountError.serverNotFound } else { - DispatchQueue.main.async { - self.loginErrorMessage = error.localizedDescription - self.isPresentingLoginErrorAlert = true - } + self.currentError = error } } } 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() LocalStorageManager.standard.purgeUserCollections() self.posts.purgePublishedPosts() } } catch { - print("Something went wrong purging the token from the Keychain.") + print(KeychainError.couldNotPurgeAccessToken.localizedDescription) } } 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() LocalStorageManager.standard.purgeUserCollections() self.posts.purgePublishedPosts() } } catch { - print("Something went wrong purging the token from the Keychain.") + print(KeychainError.couldNotPurgeAccessToken.localizedDescription) } } 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 (error as NSError).domain == NSURLErrorDomain, (error as NSError).code == NSURLErrorCannotParseResponse { if account.isLoggedIn { self.logout() } } } } func fetchUserCollectionsHandler(result: Result<[WFCollection], Error>) { // We're done with the network request. DispatchQueue.main.async { self.isProcessingRequest = false } do { let fetchedCollections = try result.get() for fetchedCollection in fetchedCollections { DispatchQueue.main.async { let localCollection = WFACollection(context: LocalStorageManager.standard.container.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 { LocalStorageManager.standard.saveContext() } } catch WFError.unauthorized { - DispatchQueue.main.async { - self.loginErrorMessage = "Something went wrong, please try logging in again." - self.isPresentingLoginErrorAlert = true - } + self.currentError = AccountError.genericAuthError self.logout() } catch { print(error) } } func fetchUserPostsHandler(result: Result<[WFPost], Error>) { // We're done with the network request. DispatchQueue.main.async { self.isProcessingRequest = false } let request = WFAPost.createFetchRequest() do { let locallyCachedPosts = try LocalStorageManager.standard.container.viewContext.fetch(request) do { var postsToDelete = locallyCachedPosts.filter { $0.status != PostStatus.local.rawValue } let fetchedPosts = try result.get() for fetchedPost in fetchedPosts { if let managedPost = locallyCachedPosts.first(where: { $0.postId == fetchedPost.postId }) { DispatchQueue.main.async { managedPost.wasDeletedFromServer = false 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") } postsToDelete.removeAll(where: { $0.postId == fetchedPost.postId }) } } else { DispatchQueue.main.async { let managedPost = WFAPost(context: LocalStorageManager.standard.container.viewContext) self.importData(from: fetchedPost, into: managedPost) managedPost.collectionAlias = fetchedPost.collectionAlias managedPost.wasDeletedFromServer = false } } } DispatchQueue.main.async { for post in postsToDelete { post.wasDeletedFromServer = true } LocalStorageManager.standard.saveContext() } } catch { print(error) } } catch WFError.unauthorized { - DispatchQueue.main.async { - self.loginErrorMessage = "Something went wrong, please try logging in again." - self.isPresentingLoginErrorAlert = true - } + self.currentError = AccountError.genericAuthError self.logout() } catch { print("Error: Failed to fetch cached posts") } } func publishHandler(result: Result) { // We're done with the network request. DispatchQueue.main.async { self.isProcessingRequest = false } // ⚠️ 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() // If this is an updated post, check it against postToUpdate. if let updatingPost = self.postToUpdate { importData(from: fetchedPost, into: updatingPost) DispatchQueue.main.async { LocalStorageManager.standard.saveContext() } } else { // Otherwise if it's a newly-published post, find it in the local store. let request = WFAPost.createFetchRequest() let matchBodyPredicate = NSPredicate(format: "body == %@", fetchedPost.body) if let fetchedPostTitle = fetchedPost.title { let matchTitlePredicate = NSPredicate(format: "title == %@", fetchedPostTitle) request.predicate = NSCompoundPredicate( andPredicateWithSubpredicates: [ matchTitlePredicate, matchBodyPredicate ] ) } else { request.predicate = matchBodyPredicate } do { let cachedPostsResults = try LocalStorageManager.standard.container.viewContext.fetch(request) guard let cachedPost = cachedPostsResults.first else { return } importData(from: fetchedPost, into: cachedPost) DispatchQueue.main.async { LocalStorageManager.standard.saveContext() } } catch { print("Error: Failed to fetch cached posts") } } } catch { print(error) } } func updateFromServerHandler(result: Result) { // We're done with the network request. DispatchQueue.main.async { self.isProcessingRequest = false } // ⚠️ 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 } importData(from: fetchedPost, into: cachedPost) cachedPost.hasNewerRemoteCopy = false DispatchQueue.main.async { LocalStorageManager.standard.saveContext() } } catch { print(error) } } func movePostHandler(result: Result) { // We're done with the network request. DispatchQueue.main.async { self.isProcessingRequest = false } do { let succeeded = try result.get() if succeeded { if let post = selectedPost { updateFromServer(post: post) } else { return } } } catch { DispatchQueue.main.async { LocalStorageManager.standard.container.viewContext.rollback() } print(error) } } private func importData(from fetchedPost: WFPost, into cachedPost: WFAPost) { 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 } } diff --git a/Shared/Extensions/WriteFreelyModel+Keychain.swift b/Shared/Extensions/WriteFreelyModel+Keychain.swift index f039e31..4984675 100644 --- a/Shared/Extensions/WriteFreelyModel+Keychain.swift +++ b/Shared/Extensions/WriteFreelyModel+Keychain.swift @@ -1,61 +1,55 @@ import Foundation extension WriteFreelyModel { - enum WFKeychainError: Error { - case saveToKeychainFailed - case purgeFromKeychainFailed - case fetchFromKeychainFailed - } - func saveTokenToKeychain(_ token: String, username: String?, server: String) throws { 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 { - throw WFKeychainError.saveToKeychainFailed + throw KeychainError.couldNotStoreAccessToken } } 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 { - throw WFKeychainError.purgeFromKeychainFailed + throw KeychainError.couldNotPurgeAccessToken } } func fetchTokenFromKeychain(username: String?, server: String) throws -> 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 { - throw WFKeychainError.fetchFromKeychainFailed + throw KeychainError.couldNotFetchAccessToken } 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/LocalStorageManager.swift b/Shared/LocalStorageManager.swift index b644faf..af62660 100644 --- a/Shared/LocalStorageManager.swift +++ b/Shared/LocalStorageManager.swift @@ -1,123 +1,125 @@ import CoreData #if os(iOS) import UIKit #elseif os(macOS) import AppKit #endif final class LocalStorageManager { public static var standard = LocalStorageManager() public let container: NSPersistentContainer private let containerName = "LocalStorageModel" private init() { container = NSPersistentContainer(name: containerName) setupStore(in: container) registerObservers() } func saveContext() { if container.viewContext.hasChanges { do { try container.viewContext.save() } catch { - print("Error saving context: \(error)") + print(LocalStoreError.couldNotSaveContext.localizedDescription) } } } func purgeUserCollections() { let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: "WFACollection") let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) do { try container.viewContext.executeAndMergeChanges(using: deleteRequest) } catch { - print("Error: Failed to purge cached collections.") + print(LocalStoreError.couldNotPurgeCollections.localizedDescription) } } } private extension LocalStorageManager { var oldStoreURL: URL { let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first! return appSupport.appendingPathComponent("LocalStorageModel.sqlite") } var sharedStoreURL: URL { let id = "group.com.abunchtell.writefreely" let groupContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: id)! return groupContainer.appendingPathComponent("LocalStorageModel.sqlite") } func setupStore(in container: NSPersistentContainer) { if !FileManager.default.fileExists(atPath: oldStoreURL.path) { container.persistentStoreDescriptions.first!.url = sharedStoreURL } container.loadPersistentStores { _, error in if let error = error { - fatalError("Core Data store failed to load with error: \(error)") + fatalError(LocalStoreError.couldNotLoadStore(error.localizedDescription).localizedDescription) } } migrateStore(for: container) container.viewContext.automaticallyMergesChangesFromParent = true container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy } func migrateStore(for container: NSPersistentContainer) { // Check if the shared store exists before attempting a migration — for example, in case we've already attempted // and successfully completed a migration, but the deletion of the old store failed for some reason. guard !FileManager.default.fileExists(atPath: sharedStoreURL.path) else { return } let coordinator = container.persistentStoreCoordinator // Get a reference to the old store. guard let oldStore = coordinator.persistentStore(for: oldStoreURL) else { return } // Attempt to migrate the old store over to the shared store URL. do { try coordinator.migratePersistentStore(oldStore, to: sharedStoreURL, options: nil, withType: NSSQLiteStoreType) } catch { - fatalError("Something went wrong migrating the store: \(error)") + fatalError(LocalStoreError.couldNotMigrateStore(error.localizedDescription).localizedDescription) } // Attempt to delete the old store. do { try FileManager.default.removeItem(at: oldStoreURL) } catch { - fatalError("Something went wrong while deleting the old store: \(error)") + fatalError( + LocalStoreError.couldNotDeleteStoreAfterMigration(error.localizedDescription).localizedDescription + ) } } func registerObservers() { 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 saveContextOnResignActive(_ notification: Notification) { saveContext() } } diff --git a/Shared/Models/WriteFreelyModel.swift b/Shared/Models/WriteFreelyModel.swift index ecb575f..634aa50 100644 --- a/Shared/Models/WriteFreelyModel.swift +++ b/Shared/Models/WriteFreelyModel.swift @@ -1,83 +1,96 @@ import Foundation import WriteFreely import Security import Network // MARK: - WriteFreelyModel final class WriteFreelyModel: ObservableObject { @Published var account = AccountModel() @Published var preferences = PreferencesModel() @Published var posts = PostListModel() @Published var editor = PostEditorModel() @Published var isLoggingIn: Bool = false @Published var isProcessingRequest: Bool = false @Published var hasNetworkConnection: Bool = true @Published var selectedPost: WFAPost? @Published var selectedCollection: WFACollection? @Published var showAllPosts: Bool = true @Published var isPresentingDeleteAlert: Bool = false - @Published var isPresentingLoginErrorAlert: Bool = false - @Published var isPresentingNetworkErrorAlert: Bool = false @Published var postToDelete: WFAPost? + @Published var hasError: Bool = false #if os(iOS) @Published var isPresentingSettingsView: Bool = false #endif static var shared = WriteFreelyModel() - var loginErrorMessage: String? + var currentError: Error? { + didSet { + #if DEBUG + print("⚠️ currentError -> didSet \(currentError)") + print(" > hasError was: \(self.hasError)") + #endif + DispatchQueue.main.async { + #if DEBUG + print(" > self.currentError != nil: \(self.currentError != nil)") + #endif + self.hasError = self.currentError != nil + #if DEBUG + print(" > hasError is now: \(self.hasError)") + #endif + } + } + } // swiftlint:disable line_length let helpURL = URL(string: "https://discuss.write.as/c/help/5")! let howToURL = URL(string: "https://discuss.write.as/t/using-the-writefreely-ios-app/1946")! let reviewURL = URL(string: "https://apps.apple.com/app/id1531530896?action=write-review")! let licensesURL = URL(string: "https://github.com/writeas/writefreely-swiftui-multiplatform/tree/main/Shared/Resources/Licenses")! // swiftlint:enable line_length internal var client: WFClient? private let defaults = UserDefaults.shared private let monitor = NWPathMonitor() private let queue = DispatchQueue(label: "NetworkMonitor") internal var postToUpdate: WFAPost? init() { DispatchQueue.main.async { self.preferences.appearance = self.defaults.integer(forKey: WFDefaults.colorSchemeIntegerKey) self.preferences.font = self.defaults.integer(forKey: WFDefaults.defaultFontIntegerKey) self.account.restoreState() if self.account.isLoggedIn { guard let serverURL = URL(string: self.account.server) else { - print("Server URL not found") + self.currentError = AccountError.invalidServerURL return } do { guard let token = try self.fetchTokenFromKeychain( username: self.account.username, server: self.account.server ) else { - self.loginErrorMessage = AccountError.couldNotFetchTokenFromKeychain.localizedDescription - self.isPresentingLoginErrorAlert = true + self.currentError = KeychainError.couldNotFetchAccessToken return } self.account.login(WFUser(token: token, username: self.account.username)) self.client = WFClient(for: serverURL) self.client?.user = self.account.user self.fetchUserCollections() self.fetchUserPosts() } catch { - self.loginErrorMessage = AccountError.couldNotFetchTokenFromKeychain.localizedDescription - self.isPresentingLoginErrorAlert = true + self.currentError = KeychainError.couldNotFetchAccessToken } } } monitor.pathUpdateHandler = { path in DispatchQueue.main.async { self.hasNetworkConnection = path.status == .satisfied } } monitor.start(queue: queue) } } diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index 4001266..a3addfe 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -1,71 +1,73 @@ import SwiftUI struct ContentView: View { @EnvironmentObject var model: WriteFreelyModel var body: some View { NavigationView { #if os(macOS) CollectionListView() .toolbar { Button( action: { NSApp.keyWindow?.contentViewController?.tryToPerform( #selector(NSSplitViewController.toggleSidebar(_:)), with: nil ) }, label: { Image(systemName: "sidebar.left") } ) .help("Toggle the sidebar's visibility.") Spacer() Button(action: { withAnimation { // Un-set the currently selected post self.model.selectedPost = nil } // Create the new-post managed object let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font) withAnimation { DispatchQueue.main.asyncAfter(deadline: .now()) { // Load the new post in the editor self.model.selectedPost = managedPost } } }, label: { Image(systemName: "square.and.pencil") }) .help("Create a new local draft.") } #else CollectionListView() #endif #if os(macOS) ZStack { PostListView(selectedCollection: model.selectedCollection, showAllPosts: model.showAllPosts) + .withErrorHandling() if model.isProcessingRequest { ZStack { Color(NSColor.controlBackgroundColor).opacity(0.75) ProgressView() } } } #else PostListView(selectedCollection: model.selectedCollection, showAllPosts: model.showAllPosts) + .withErrorHandling() #endif Text("Select a post, or create a new local draft.") .foregroundColor(.secondary) } .environmentObject(model) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.standard.container.viewContext let model = WriteFreelyModel() return ContentView() .environment(\.managedObjectContext, context) .environmentObject(model) } } diff --git a/Shared/PostList/PostListView.swift b/Shared/PostList/PostListView.swift index d59a706..2158aa4 100644 --- a/Shared/PostList/PostListView.swift +++ b/Shared/PostList/PostListView.swift @@ -1,188 +1,186 @@ import SwiftUI import Combine struct PostListView: View { @EnvironmentObject var model: WriteFreelyModel + @EnvironmentObject var errorHandling: ErrorHandling @Environment(\.managedObjectContext) var managedObjectContext @State private var postCount: Int = 0 @State private var filteredListViewId: Int = 0 var selectedCollection: WFACollection? var showAllPosts: Bool #if os(iOS) private var frameHeight: CGFloat { var height: CGFloat = 50 let bottom = UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0 height += bottom return height } #endif var body: some View { #if os(iOS) ZStack(alignment: .bottom) { PostListFilteredView( collection: selectedCollection, showAllPosts: showAllPosts, postCount: $postCount ) .id(self.filteredListViewId) .navigationTitle( showAllPosts ? "All Posts" : selectedCollection?.title ?? ( model.account.server == "https://write.as" ? "Anonymous" : "Drafts" ) ) .toolbar { ToolbarItem(placement: .primaryAction) { // We have to add a Spacer as a sibling view to the Button in some kind of Stack, so that any // a11y modifiers are applied as expected: bug report filed as FB8956392. ZStack { Spacer() Button(action: { let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font) withAnimation { self.model.showAllPosts = false self.model.selectedPost = managedPost } }, label: { ZStack { Image("does.not.exist") .accessibilityHidden(true) Image(systemName: "square.and.pencil") .accessibilityHidden(true) .imageScale(.large) // These modifiers compensate for the resizing .padding(.vertical, 12) // done to the Image (and the button tap target) .padding(.leading, 12) // by the SwiftUI layout system from adding a .padding(.trailing, 8) // Spacer in this ZStack (FB8956392). } .frame(maxWidth: .infinity, maxHeight: .infinity) }) .accessibilityLabel(Text("Compose")) .accessibilityHint(Text("Compose a new local draft")) } } } VStack { HStack(spacing: 0) { Button(action: { model.isPresentingSettingsView = true }, label: { Image(systemName: "gear") .padding(.vertical, 4) .padding(.horizontal, 8) }) .accessibilityLabel(Text("Settings")) .accessibilityHint(Text("Open the Settings sheet")) .sheet( isPresented: $model.isPresentingSettingsView, onDismiss: { model.isPresentingSettingsView = false }, content: { SettingsView() .environmentObject(model) } ) Spacer() Text(postCount == 1 ? "\(postCount) post" : "\(postCount) posts") .foregroundColor(.secondary) - .alert(isPresented: $model.isPresentingNetworkErrorAlert, content: { - Alert( - title: Text("Connection Error"), - message: Text(""" - There is no internet connection at the moment. Please reconnect or try again later. - """), - dismissButton: .default(Text("OK"), action: { - model.isPresentingNetworkErrorAlert = false - }) - ) - }) Spacer() if model.isProcessingRequest { ProgressView() .padding(.vertical, 4) .padding(.horizontal, 8) } else { Button(action: { DispatchQueue.main.async { model.fetchUserCollections() model.fetchUserPosts() } }, label: { Image(systemName: "arrow.clockwise") .padding(.vertical, 4) .padding(.horizontal, 8) }) .accessibilityLabel(Text("Refresh Posts")) .accessibilityHint(Text("Fetch changes from the server")) .disabled(!model.account.isLoggedIn) } } .padding(.top, 8) .padding(.horizontal, 8) Spacer() } .frame(height: frameHeight) .background(Color(UIColor.systemGray5)) .overlay(Divider(), alignment: .top) .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in // We use this to invalidate and refresh the view, so that new posts created outside of the app (e.g., // in the action extension) show up. withAnimation { self.filteredListViewId += 1 } } } .ignoresSafeArea(.all, edges: .bottom) .onAppear { model.selectedCollection = selectedCollection model.showAllPosts = showAllPosts } + .onChange(of: model.hasError) { value in + if value { + if let error = model.currentError { + self.errorHandling.handle(error: error) + } else { + self.errorHandling.handle(error: AppError.genericError) + } + model.hasError = false + } + } #else PostListFilteredView( collection: selectedCollection, showAllPosts: showAllPosts, postCount: $postCount ) .toolbar { ToolbarItemGroup(placement: .primaryAction) { if model.selectedPost != nil { ActivePostToolbarView(activePost: model.selectedPost!) - .alert(isPresented: $model.isPresentingNetworkErrorAlert, content: { - Alert( - title: Text("Connection Error"), - message: Text(""" - There is no internet connection at the moment. \ - Please reconnect or try again later. - """), - dismissButton: .default(Text("OK"), action: { - model.isPresentingNetworkErrorAlert = false - }) - ) - }) } } } .navigationTitle( showAllPosts ? "All Posts" : selectedCollection?.title ?? ( model.account.server == "https://write.as" ? "Anonymous" : "Drafts" ) ) .onAppear { model.selectedCollection = selectedCollection model.showAllPosts = showAllPosts } + .onChange(of: model.hasError) { value in + if value { + if let error = model.currentError { + self.errorHandling.handle(error: error) + } else { + self.errorHandling.handle(error: AppError.genericError) + } + model.hasError = false + } + } #endif } } struct PostListView_Previews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.standard.container.viewContext let model = WriteFreelyModel() return PostListView(showAllPosts: true) .environment(\.managedObjectContext, context) .environmentObject(model) } } diff --git a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj index 742d691..c2f33e2 100644 --- a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj +++ b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj @@ -1,1498 +1,1522 @@ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 52; objects = { /* Begin PBXBuildFile section */ 170A7EC126F5186A00F1CBD4 /* CollectionListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170A7EC026F5186A00F1CBD4 /* CollectionListModel.swift */; }; 170A7EC226F5186A00F1CBD4 /* CollectionListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170A7EC026F5186A00F1CBD4 /* CollectionListModel.swift */; }; 170DFA34251BBC44001D82A0 /* PostEditorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170DFA33251BBC44001D82A0 /* PostEditorModel.swift */; }; 170DFA35251BBC44001D82A0 /* PostEditorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170DFA33251BBC44001D82A0 /* PostEditorModel.swift */; }; 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 */; }; 171BFDFA24D4AF8300888236 /* CollectionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF924D4AF8300888236 /* CollectionListView.swift */; }; 171BFDFB24D4AF8300888236 /* CollectionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF924D4AF8300888236 /* CollectionListView.swift */; }; 171DC677272C7D0B002B9B8A /* UserDefaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171DC676272C7D0B002B9B8A /* UserDefaults+Extensions.swift */; }; 171DC678272C7D0B002B9B8A /* UserDefaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171DC676272C7D0B002B9B8A /* UserDefaults+Extensions.swift */; }; + 1727526628099802003D0A6A /* ErrorConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1727526528099802003D0A6A /* ErrorConstants.swift */; }; + 1727526728099802003D0A6A /* ErrorConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1727526528099802003D0A6A /* ErrorConstants.swift */; }; + 1727526828099802003D0A6A /* ErrorConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1727526528099802003D0A6A /* ErrorConstants.swift */; }; + 1727526A2809991A003D0A6A /* ErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172752692809991A003D0A6A /* ErrorHandling.swift */; }; + 1727526B2809991A003D0A6A /* ErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172752692809991A003D0A6A /* ErrorHandling.swift */; }; + 1727526C2809991A003D0A6A /* ErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172752692809991A003D0A6A /* ErrorHandling.swift */; }; 172C492E2593981900E20ADF /* MacUpdatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172C492D2593981900E20ADF /* MacUpdatesView.swift */; }; 172E10012735B83E00061372 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 172E10002735B83E00061372 /* UniformTypeIdentifiers.framework */; platformFilter = maccatalyst; }; 172E10042735B83E00061372 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 172E10032735B83E00061372 /* Media.xcassets */; }; 172E10062735B83E00061372 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10052735B83E00061372 /* ActionViewController.swift */; }; 172E100D2735B83E00061372 /* ActionExtension-iOS.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 172E0FFF2735B83E00061372 /* ActionExtension-iOS.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 172E10132735BB6200061372 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = 172E10122735BB6200061372 /* Action.js */; }; 172E10152735C2BD00061372 /* UIHostingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10142735C2BD00061372 /* UIHostingView.swift */; }; 172E10172735C2DF00061372 /* EnvironmentValues+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10162735C2DF00061372 /* EnvironmentValues+Extensions.swift */; }; 172E10192735C3DB00061372 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10182735C3DB00061372 /* ContentView.swift */; }; 172E101C2735C57400061372 /* LocalStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */; }; 172E101D2735C5AB00061372 /* LocalStorageModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */; }; 172E101E2735C62F00061372 /* PostStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E612507D8E600072984 /* PostStatus.swift */; }; 172E101F2735C64600061372 /* WFAPost+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */; }; 172E10202735C64600061372 /* WFACollection+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */; }; 172E10212735C64600061372 /* WFACollection+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */; }; 172E10222735C64600061372 /* WFAPost+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */; }; 172E10232735C6FF00061372 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */; }; 172E10242735C72500061372 /* PreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D435E724E3128F0036B539 /* PreferencesModel.swift */; }; 173E19D1254318F600440F0F /* RemoteChangePromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173E19D0254318F600440F0F /* RemoteChangePromptView.swift */; }; 173E19E3254329CC00440F0F /* PostTextEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173E19E2254329CC00440F0F /* PostTextEditingView.swift */; }; 17466626256C0D0600629997 /* MacEditorTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17466625256C0D0600629997 /* MacEditorTextView.swift */; }; 17479F152583D8E40072B7FB /* PostEditorSharingPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17479F142583D8E40072B7FB /* PostEditorSharingPicker.swift */; }; 17480CA5251272EE00EB7765 /* Bundle+AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17480CA4251272EE00EB7765 /* Bundle+AppVersion.swift */; }; 17480CA6251272EE00EB7765 /* Bundle+AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17480CA4251272EE00EB7765 /* Bundle+AppVersion.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 */; }; 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 */; }; 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 */; }; 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 */; }; 1780F6EF25895EDB00FE45FF /* PostCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1780F6EE25895EDB00FE45FF /* PostCommands.swift */; }; 17836C14273EFB870047AF61 /* UserDefaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171DC676272C7D0B002B9B8A /* UserDefaults+Extensions.swift */; }; 17836C15273F0FBB0047AF61 /* Hack-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F3A42514F1E900517CE6 /* Hack-Regular.ttf */; }; 17836C16273F0FBB0047AF61 /* LoraGX.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F36B2514EE2F00517CE6 /* LoraGX.ttf */; }; 17836C17273F0FBB0047AF61 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F39D2514F0E500517CE6 /* OpenSans-Regular.ttf */; }; 1784D2ED27946D880033E72E /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 1784D2EC27946D880033E72E /* WriteFreely */; }; 1784D2EF27946D9A0033E72E /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 1784D2EE27946D9A0033E72E /* WriteFreely */; }; 1784D2F127946DA10033E72E /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 1784D2F027946DA10033E72E /* WriteFreely */; }; 17A4FEED25927E730037E96B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A4FEEC25927E730037E96B /* AppDelegate.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 */; }; 17A67CAF251A5DD7002F163D /* PostEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A67CAE251A5DD7002F163D /* PostEditorView.swift */; }; 17AD0A5E25489E810057D763 /* PostTitleTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17AD0A5D25489E810057D763 /* PostTitleTextView.swift */; }; 17AD0A6425489E900057D763 /* PostBodyTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17AD0A6325489E900057D763 /* PostBodyTextView.swift */; }; 17B37C4B25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C4A25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift */; }; 17B37C4C25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C4A25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift */; }; 17B37C5625C8679800FE75E9 /* WriteFreelyModel+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C5525C8679800FE75E9 /* WriteFreelyModel+API.swift */; }; 17B37C5725C8679800FE75E9 /* WriteFreelyModel+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C5525C8679800FE75E9 /* WriteFreelyModel+API.swift */; }; 17B37C5D25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C5C25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift */; }; 17B37C5E25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C5C25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift */; }; 17B3E965250FAA9000EE9748 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 17B3E964250FAA9000EE9748 /* LaunchScreen.storyboard */; }; 17B5103B2515448D00E9631F /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 17B5103A2515448D00E9631F /* Credits.rtf */; }; 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 */; }; 17BC618A25715318003363CA /* ActivePostToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17BC617825715068003363CA /* ActivePostToolbarView.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 */; }; 17D4926527947B4D0035BD7E /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 17D4926427947B4D0035BD7E /* Sparkle */; }; 17D4926727947D780035BD7E /* MacUpdatesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D4926627947D780035BD7E /* MacUpdatesViewModel.swift */; }; 17D4F36C2514EE2F00517CE6 /* LoraGX.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F36B2514EE2F00517CE6 /* LoraGX.ttf */; }; 17D4F36D2514EE2F00517CE6 /* LoraGX.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F36B2514EE2F00517CE6 /* LoraGX.ttf */; }; 17D4F39E2514F0E500517CE6 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F39D2514F0E500517CE6 /* OpenSans-Regular.ttf */; }; 17D4F39F2514F0E500517CE6 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F39D2514F0E500517CE6 /* OpenSans-Regular.ttf */; }; 17D4F3A52514F1E900517CE6 /* Hack-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F3A42514F1E900517CE6 /* Hack-Regular.ttf */; }; 17D4F3A62514F1E900517CE6 /* Hack-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F3A42514F1E900517CE6 /* Hack-Regular.ttf */; }; 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 */; }; 17DF32D524C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF32D424C8CA3400BCE2E3 /* PostStatusBadgeView.swift */; }; 17DF32D624C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF32D424C8CA3400BCE2E3 /* PostStatusBadgeView.swift */; }; 17DFDE87251D309400A25F31 /* Hack-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE84251D309400A25F31 /* Hack-License.txt */; }; 17DFDE88251D309400A25F31 /* Hack-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE84251D309400A25F31 /* Hack-License.txt */; }; 17DFDE89251D309400A25F31 /* Lora-Cyrillic-OFL.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE85251D309400A25F31 /* Lora-Cyrillic-OFL.txt */; }; 17DFDE8A251D309400A25F31 /* Lora-Cyrillic-OFL.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE85251D309400A25F31 /* Lora-Cyrillic-OFL.txt */; }; 17DFDE8B251D309400A25F31 /* OpenSans-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE86251D309400A25F31 /* OpenSans-License.txt */; }; 17DFDE8C251D309400A25F31 /* OpenSans-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE86251D309400A25F31 /* OpenSans-License.txt */; }; 17E5DF8A2543610700DCDC9B /* PostTextEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 172E100B2735B83E00061372 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 17DF327C24C87D3300BCE2E3 /* Project object */; proxyType = 1; remoteGlobalIDString = 172E0FFE2735B83E00061372; remoteInfo = "ActionExtension-iOS"; }; 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 PBXCopyFilesBuildPhase section */ 172E100E2735B83E00061372 /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( 172E100D2735B83E00061372 /* ActionExtension-iOS.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1709ADDF251B9A110053AF79 /* EditorLaunchingPolicy.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = EditorLaunchingPolicy.md; sourceTree = ""; }; 170A7EC026F5186A00F1CBD4 /* CollectionListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionListModel.swift; sourceTree = ""; }; 170DFA33251BBC44001D82A0 /* PostEditorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorModel.swift; sourceTree = ""; }; 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 = ""; }; 171BFDF924D4AF8300888236 /* CollectionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionListView.swift; sourceTree = ""; }; 171DC676272C7D0B002B9B8A /* UserDefaults+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Extensions.swift"; sourceTree = ""; }; + 1727526528099802003D0A6A /* ErrorConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorConstants.swift; sourceTree = ""; }; + 172752692809991A003D0A6A /* ErrorHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandling.swift; sourceTree = ""; }; 172C492D2593981900E20ADF /* MacUpdatesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacUpdatesView.swift; sourceTree = ""; }; 172E0FFF2735B83E00061372 /* ActionExtension-iOS.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "ActionExtension-iOS.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 172E10002735B83E00061372 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; 172E10032735B83E00061372 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; 172E10052735B83E00061372 /* ActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionViewController.swift; sourceTree = ""; }; 172E100A2735B83E00061372 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; usesTabs = 1; }; 172E10122735BB6200061372 /* Action.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Action.js; sourceTree = ""; }; 172E10142735C2BD00061372 /* UIHostingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIHostingView.swift; sourceTree = ""; }; 172E10162735C2DF00061372 /* EnvironmentValues+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentValues+Extensions.swift"; sourceTree = ""; }; 172E10182735C3DB00061372 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 173E19D0254318F600440F0F /* RemoteChangePromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteChangePromptView.swift; sourceTree = ""; }; 173E19E2254329CC00440F0F /* PostTextEditingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTextEditingView.swift; sourceTree = ""; }; 17466625256C0D0600629997 /* MacEditorTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacEditorTextView.swift; sourceTree = ""; }; 17479F142583D8E40072B7FB /* PostEditorSharingPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorSharingPicker.swift; sourceTree = ""; }; 17480CA4251272EE00EB7765 /* Bundle+AppVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+AppVersion.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 = ""; }; 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 = ""; }; 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; }; 1780F6EE25895EDB00FE45FF /* PostCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCommands.swift; sourceTree = ""; }; 17836C18273F10C40047AF61 /* ActionExtension-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "ActionExtension-iOS.entitlements"; sourceTree = ""; }; 17A355D3271A052C007C7A47 /* WriteFreely-MultiPlatform (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "WriteFreely-MultiPlatform (iOS).entitlements"; sourceTree = ""; }; 17A4FEDF25924E810037E96B /* MacSoftwareUpdater.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = MacSoftwareUpdater.md; sourceTree = ""; }; 17A4FEEC25927E730037E96B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.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 = ""; }; 17A67CAE251A5DD7002F163D /* PostEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorView.swift; sourceTree = ""; }; 17AD0A5D25489E810057D763 /* PostTitleTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTitleTextView.swift; sourceTree = ""; }; 17AD0A6325489E900057D763 /* PostBodyTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostBodyTextView.swift; sourceTree = ""; }; 17B37C4A25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WriteFreelyModel+Keychain.swift"; sourceTree = ""; }; 17B37C5525C8679800FE75E9 /* WriteFreelyModel+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WriteFreelyModel+API.swift"; sourceTree = ""; }; 17B37C5C25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WriteFreelyModel+APIHandlers.swift"; sourceTree = ""; }; 17B3E964250FAA9000EE9748 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 17B5103A2515448D00E9631F /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; 17B68D4F25A4FED2005ED37C /* Sparkle-License.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Sparkle-License.txt"; 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; }; 17BC617825715068003363CA /* ActivePostToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivePostToolbarView.swift; sourceTree = ""; }; 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 = ""; }; 17D4926627947D780035BD7E /* MacUpdatesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacUpdatesViewModel.swift; sourceTree = ""; }; 17D4F36B2514EE2F00517CE6 /* LoraGX.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = LoraGX.ttf; sourceTree = ""; }; 17D4F39D2514F0E500517CE6 /* OpenSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "OpenSans-Regular.ttf"; sourceTree = ""; }; 17D4F3A42514F1E900517CE6 /* Hack-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Hack-Regular.ttf"; 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 for Mac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "WriteFreely for Mac.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 = ""; }; 17DFDE84251D309400A25F31 /* Hack-License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Hack-License.txt"; sourceTree = ""; }; 17DFDE85251D309400A25F31 /* Lora-Cyrillic-OFL.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Lora-Cyrillic-OFL.txt"; sourceTree = ""; }; 17DFDE86251D309400A25F31 /* OpenSans-License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "OpenSans-License.txt"; sourceTree = ""; }; 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTextEditingView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 172E0FFC2735B83E00061372 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 172E10012735B83E00061372 /* UniformTypeIdentifiers.framework in Frameworks */, 1784D2F127946DA10033E72E /* WriteFreely in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF328524C87D3500BCE2E3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 1784D2ED27946D880033E72E /* WriteFreely in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF328D24C87D3500BCE2E3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 1784D2EF27946D9A0033E72E /* WriteFreely in Frameworks */, 17D4926527947B4D0035BD7E /* Sparkle 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 */ 1709ADDE251B99D40053AF79 /* Technotes */ = { isa = PBXGroup; children = ( 1709ADDF251B9A110053AF79 /* EditorLaunchingPolicy.md */, 17A4FEDF25924E810037E96B /* MacSoftwareUpdater.md */, ); path = Technotes; sourceTree = ""; }; 17120DA624E19CE2002B9F6C /* Settings */ = { isa = PBXGroup; children = ( 17120DB124E1E19C002B9F6C /* SettingsHeaderView.swift */, 17120DA424E19CBF002B9F6C /* SettingsView.swift */, ); path = Settings; sourceTree = ""; }; + 17275264280997BF003D0A6A /* ErrorHandling */ = { + isa = PBXGroup; + children = ( + 1727526528099802003D0A6A /* ErrorConstants.swift */, + 172752692809991A003D0A6A /* ErrorHandling.swift */, + ); + path = ErrorHandling; + sourceTree = ""; + }; 172E10022735B83E00061372 /* ActionExtension-iOS */ = { isa = PBXGroup; children = ( 17836C18273F10C40047AF61 /* ActionExtension-iOS.entitlements */, 172E10032735B83E00061372 /* Media.xcassets */, 172E10182735C3DB00061372 /* ContentView.swift */, 172E10052735B83E00061372 /* ActionViewController.swift */, 172E100A2735B83E00061372 /* Info.plist */, 172E10122735BB6200061372 /* Action.js */, ); path = "ActionExtension-iOS"; sourceTree = ""; }; 1739B8D324EAFAB700DA7421 /* PostEditor */ = { isa = PBXGroup; children = ( 170DFA33251BBC44001D82A0 /* PostEditorModel.swift */, 1756DBB224FECDBB00207AB8 /* PostEditorStatusToolbarView.swift */, ); path = PostEditor; sourceTree = ""; }; 1756AE7F24CB841200FD7257 /* Extensions */ = { isa = PBXGroup; children = ( 17480CA4251272EE00EB7765 /* Bundle+AppVersion.swift */, 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */, 17B37C5525C8679800FE75E9 /* WriteFreelyModel+API.swift */, 17B37C5C25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift */, 17B37C4A25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift */, 171DC676272C7D0B002B9B8A /* UserDefaults+Extensions.swift */, ); path = Extensions; sourceTree = ""; }; 1762DCB124EB07680019C4EB /* Models */ = { isa = PBXGroup; children = ( 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 = ""; }; 17681E3F251940F200D394AE /* Extensions */ = { isa = PBXGroup; children = ( 1756AE8024CB844500FD7257 /* View+Keyboard.swift */, 172E10142735C2BD00061372 /* UIHostingView.swift */, 172E10162735C2DF00061372 /* EnvironmentValues+Extensions.swift */, ); path = Extensions; sourceTree = ""; }; 17A5388924DDA50500DEFF9A /* Settings */ = { isa = PBXGroup; children = ( 17A5388724DDA31F00DEFF9A /* MacAccountView.swift */, 1753F6AB24E431CC00309365 /* MacPreferencesView.swift */, 172C492D2593981900E20ADF /* MacUpdatesView.swift */, 17D4926627947D780035BD7E /* MacUpdatesViewModel.swift */, ); path = Settings; sourceTree = ""; }; 17A67CAB251A5D7E002F163D /* PostEditor */ = { isa = PBXGroup; children = ( 1756AE7624CB2EDD00FD7257 /* PostEditorView.swift */, 173E19D0254318F600440F0F /* RemoteChangePromptView.swift */, 173E19E2254329CC00440F0F /* PostTextEditingView.swift */, 17AD0A5D25489E810057D763 /* PostTitleTextView.swift */, 17AD0A6325489E900057D763 /* PostBodyTextView.swift */, ); path = PostEditor; sourceTree = ""; }; 17A67CAC251A5D8D002F163D /* PostEditor */ = { isa = PBXGroup; children = ( 17479F142583D8E40072B7FB /* PostEditorSharingPicker.swift */, 17A67CAE251A5DD7002F163D /* PostEditorView.swift */, 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */, 17466625256C0D0600629997 /* MacEditorTextView.swift */, ); path = PostEditor; sourceTree = ""; }; 17BC617725715042003363CA /* Navigation */ = { isa = PBXGroup; children = ( 17BC617825715068003363CA /* ActivePostToolbarView.swift */, 1780F6EE25895EDB00FE45FF /* PostCommands.swift */, ); path = Navigation; sourceTree = ""; }; 17D4F3722514EE4400517CE6 /* Resources */ = { isa = PBXGroup; children = ( 17DFDE83251D309400A25F31 /* Licenses */, 17D4F3A42514F1E900517CE6 /* Hack-Regular.ttf */, 17D4F39D2514F0E500517CE6 /* OpenSans-Regular.ttf */, 17D4F36B2514EE2F00517CE6 /* LoraGX.ttf */, ); path = Resources; sourceTree = ""; }; 17DF327B24C87D3300BCE2E3 = { isa = PBXGroup; children = ( 17A355D3271A052C007C7A47 /* WriteFreely-MultiPlatform (iOS).entitlements */, 17DF32C624C884FF00BCE2E3 /* README.md */, 17DF32C924C8855E00BCE2E3 /* LICENSE.md */, 17DF32CA24C8856C00BCE2E3 /* CHANGELOG.md */, 17DF32C724C8853700BCE2E3 /* CODE_OF_CONDUCT.md */, 17DF32C824C8854B00BCE2E3 /* CONTRIBUTING.md */, 1709ADDE251B99D40053AF79 /* Technotes */, 17DF328024C87D3300BCE2E3 /* Shared */, 17DF328A24C87D3500BCE2E3 /* iOS */, 17DF329124C87D3500BCE2E3 /* macOS */, 17DF329B24C87D3500BCE2E3 /* Tests iOS */, 17DF32A624C87D3500BCE2E3 /* Tests macOS */, 172E10022735B83E00061372 /* ActionExtension-iOS */, 17DF328924C87D3500BCE2E3 /* Products */, 17DF32C124C87D8D00BCE2E3 /* Frameworks */, ); sourceTree = ""; }; 17DF328024C87D3300BCE2E3 /* Shared */ = { isa = PBXGroup; children = ( 17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */, 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */, 17DF328324C87D3500BCE2E3 /* Assets.xcassets */, 17DF32D024C8B75C00BCE2E3 /* Account */, 1756AE7F24CB841200FD7257 /* Extensions */, + 17275264280997BF003D0A6A /* ErrorHandling */, 1762DCB124EB07680019C4EB /* Models */, 17DF32CC24C8B72300BCE2E3 /* Navigation */, 1739B8D324EAFAB700DA7421 /* PostEditor */, 17DF32D124C8B78500BCE2E3 /* PostList */, 17DF32D224C8B78D00BCE2E3 /* PostCollection */, 1765F62C24E1924800C9EBF0 /* Preferences */, 17D4F3722514EE4400517CE6 /* Resources */, ); path = Shared; sourceTree = ""; }; 17DF328924C87D3500BCE2E3 /* Products */ = { isa = PBXGroup; children = ( 17DF328824C87D3500BCE2E3 /* WriteFreely-MultiPlatform.app */, 17DF329024C87D3500BCE2E3 /* WriteFreely for Mac.app */, 17DF329824C87D3500BCE2E3 /* Tests iOS.xctest */, 17DF32A324C87D3500BCE2E3 /* Tests macOS.xctest */, 172E0FFF2735B83E00061372 /* ActionExtension-iOS.appex */, ); name = Products; sourceTree = ""; }; 17DF328A24C87D3500BCE2E3 /* iOS */ = { isa = PBXGroup; children = ( 17DF328B24C87D3500BCE2E3 /* Info.plist */, 17B3E964250FAA9000EE9748 /* LaunchScreen.storyboard */, 17681E3F251940F200D394AE /* Extensions */, 17A67CAB251A5D7E002F163D /* PostEditor */, 17120DA624E19CE2002B9F6C /* Settings */, ); path = iOS; sourceTree = ""; }; 17DF329124C87D3500BCE2E3 /* macOS */ = { isa = PBXGroup; children = ( 17DF329224C87D3500BCE2E3 /* Info.plist */, 17DF329324C87D3500BCE2E3 /* macOS.entitlements */, 17A4FEEC25927E730037E96B /* AppDelegate.swift */, 17BC617725715042003363CA /* Navigation */, 17A67CAC251A5D8D002F163D /* PostEditor */, 17A5388924DDA50500DEFF9A /* Settings */, 17B5103A2515448D00E9631F /* Credits.rtf */, ); 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 = ( 172E10002735B83E00061372 /* UniformTypeIdentifiers.framework */, ); name = Frameworks; sourceTree = ""; }; 17DF32CC24C8B72300BCE2E3 /* Navigation */ = { isa = PBXGroup; children = ( 17DF328224C87D3300BCE2E3 /* ContentView.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 = ( 170A7EC026F5186A00F1CBD4 /* CollectionListModel.swift */, 171BFDF924D4AF8300888236 /* CollectionListView.swift */, ); path = PostCollection; sourceTree = ""; }; 17DFDE83251D309400A25F31 /* Licenses */ = { isa = PBXGroup; children = ( 17B68D4F25A4FED2005ED37C /* Sparkle-License.txt */, 17DFDE84251D309400A25F31 /* Hack-License.txt */, 17DFDE85251D309400A25F31 /* Lora-Cyrillic-OFL.txt */, 17DFDE86251D309400A25F31 /* OpenSans-License.txt */, ); path = Licenses; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 172E0FFE2735B83E00061372 /* ActionExtension-iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 172E10112735B83E00061372 /* Build configuration list for PBXNativeTarget "ActionExtension-iOS" */; buildPhases = ( 172E0FFB2735B83E00061372 /* Sources */, 172E0FFC2735B83E00061372 /* Frameworks */, 172E0FFD2735B83E00061372 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "ActionExtension-iOS"; packageProductDependencies = ( 1784D2F027946DA10033E72E /* WriteFreely */, ); productName = "ActionExtension-iOS"; productReference = 172E0FFF2735B83E00061372 /* ActionExtension-iOS.appex */; productType = "com.apple.product-type.app-extension"; }; 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 */, 172E100E2735B83E00061372 /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( 172E100C2735B83E00061372 /* PBXTargetDependency */, ); name = "WriteFreely-MultiPlatform (iOS)"; packageProductDependencies = ( 1784D2EC27946D880033E72E /* 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 = ( 1784D2EE27946D9A0033E72E /* WriteFreely */, 17D4926427947B4D0035BD7E /* Sparkle */, ); productName = "WriteFreely-MultiPlatform (macOS)"; productReference = 17DF329024C87D3500BCE2E3 /* WriteFreely for Mac.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 = 1310; LastUpgradeCheck = 1320; TargetAttributes = { 172E0FFE2735B83E00061372 = { CreatedOnToolsVersion = 13.1; }; 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 = ( 1784D2EB27946D880033E72E /* XCRemoteSwiftPackageReference "writefreely-swift" */, 17D4926327947B4D0035BD7E /* XCRemoteSwiftPackageReference "Sparkle" */, ); productRefGroup = 17DF328924C87D3500BCE2E3 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 17DF328724C87D3500BCE2E3 /* WriteFreely-MultiPlatform (iOS) */, 17DF328F24C87D3500BCE2E3 /* WriteFreely-MultiPlatform (macOS) */, 17DF329724C87D3500BCE2E3 /* Tests iOS */, 17DF32A224C87D3500BCE2E3 /* Tests macOS */, 172E0FFE2735B83E00061372 /* ActionExtension-iOS */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 172E0FFD2735B83E00061372 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 17836C17273F0FBB0047AF61 /* OpenSans-Regular.ttf in Resources */, 172E10132735BB6200061372 /* Action.js in Resources */, 172E10042735B83E00061372 /* Media.xcassets in Resources */, 17836C15273F0FBB0047AF61 /* Hack-Regular.ttf in Resources */, 17836C16273F0FBB0047AF61 /* LoraGX.ttf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF328624C87D3500BCE2E3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 17B3E965250FAA9000EE9748 /* LaunchScreen.storyboard in Resources */, 17DFDE8B251D309400A25F31 /* OpenSans-License.txt in Resources */, 17DF32AE24C87D3500BCE2E3 /* Assets.xcassets in Resources */, 17D4F39E2514F0E500517CE6 /* OpenSans-Regular.ttf in Resources */, 17D4F36C2514EE2F00517CE6 /* LoraGX.ttf in Resources */, 17D4F3A52514F1E900517CE6 /* Hack-Regular.ttf in Resources */, 17DFDE89251D309400A25F31 /* Lora-Cyrillic-OFL.txt in Resources */, 17DFDE87251D309400A25F31 /* Hack-License.txt in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF328E24C87D3500BCE2E3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 17DF32AF24C87D3500BCE2E3 /* Assets.xcassets in Resources */, 17DFDE8C251D309400A25F31 /* OpenSans-License.txt in Resources */, 17B5103B2515448D00E9631F /* Credits.rtf in Resources */, 17D4F39F2514F0E500517CE6 /* OpenSans-Regular.ttf in Resources */, 17D4F3A62514F1E900517CE6 /* Hack-Regular.ttf in Resources */, 17D4F36D2514EE2F00517CE6 /* LoraGX.ttf in Resources */, 17DFDE8A251D309400A25F31 /* Lora-Cyrillic-OFL.txt in Resources */, 17DFDE88251D309400A25F31 /* Hack-License.txt 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 */ 172E0FFB2735B83E00061372 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 172E10062735B83E00061372 /* ActionViewController.swift in Sources */, 172E10202735C64600061372 /* WFACollection+CoreDataClass.swift in Sources */, 172E10222735C64600061372 /* WFAPost+CoreDataProperties.swift in Sources */, 172E101D2735C5AB00061372 /* LocalStorageModel.xcdatamodeld in Sources */, 17836C14273EFB870047AF61 /* UserDefaults+Extensions.swift in Sources */, 172E10242735C72500061372 /* PreferencesModel.swift in Sources */, 172E10172735C2DF00061372 /* EnvironmentValues+Extensions.swift in Sources */, 172E10212735C64600061372 /* WFACollection+CoreDataProperties.swift in Sources */, 172E101C2735C57400061372 /* LocalStorageManager.swift in Sources */, 172E10192735C3DB00061372 /* ContentView.swift in Sources */, + 1727526828099802003D0A6A /* ErrorConstants.swift in Sources */, 172E10152735C2BD00061372 /* UIHostingView.swift in Sources */, 172E101F2735C64600061372 /* WFAPost+CoreDataClass.swift in Sources */, + 1727526C2809991A003D0A6A /* ErrorHandling.swift in Sources */, 172E10232735C6FF00061372 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */, 172E101E2735C62F00061372 /* PostStatus.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF328424C87D3500BCE2E3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 17DF32AC24C87D3500BCE2E3 /* ContentView.swift in Sources */, 173E19D1254318F600440F0F /* RemoteChangePromptView.swift in Sources */, 17B37C5625C8679800FE75E9 /* WriteFreelyModel+API.swift in Sources */, 17C42E622507D8E600072984 /* PostStatus.swift in Sources */, 1756DBBA24FED45500207AB8 /* LocalStorageManager.swift in Sources */, + 1727526A2809991A003D0A6A /* ErrorHandling.swift in Sources */, 1756AE8124CB844500FD7257 /* View+Keyboard.swift in Sources */, 17C42E652509237800072984 /* PostListFilteredView.swift in Sources */, 170DFA34251BBC44001D82A0 /* PostEditorModel.swift in Sources */, 17120DAC24E1B99F002B9F6C /* AccountLoginView.swift in Sources */, 17B37C4B25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift in Sources */, 17480CA5251272EE00EB7765 /* Bundle+AppVersion.swift in Sources */, 17AD0A6425489E900057D763 /* PostBodyTextView.swift in Sources */, 17B37C5D25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift in Sources */, 17AD0A5E25489E810057D763 /* PostTitleTextView.swift in Sources */, 17120DA924E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */, 171BFDFA24D4AF8300888236 /* CollectionListView.swift in Sources */, 1756DBB324FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */, 17120DB224E1E19C002B9F6C /* SettingsHeaderView.swift in Sources */, 171DC677272C7D0B002B9B8A /* UserDefaults+Extensions.swift in Sources */, 1756DBB724FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */, 17B996DA2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */, 1756AE7724CB2EDD00FD7257 /* PostEditorView.swift in Sources */, 17DF32D524C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */, 170A7EC126F5186A00F1CBD4 /* CollectionListModel.swift in Sources */, 17D435E824E3128F0036B539 /* PreferencesModel.swift in Sources */, 1756AE7A24CB65DF00FD7257 /* PostListView.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 */, + 1727526628099802003D0A6A /* ErrorConstants.swift in Sources */, 1756DC0324FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */, 17120DA224E1985C002B9F6C /* AccountModel.swift in Sources */, 17120DA324E19A42002B9F6C /* PreferencesView.swift in Sources */, 1756AE6E24CB255B00FD7257 /* PostListModel.swift in Sources */, 173E19E3254329CC00440F0F /* PostTextEditingView.swift in Sources */, 174D313224EC2831006CA9EE /* WriteFreelyModel.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 = ( 171DC678272C7D0B002B9B8A /* UserDefaults+Extensions.swift in Sources */, 17DF32AD24C87D3500BCE2E3 /* ContentView.swift in Sources */, 1756DBBB24FED45500207AB8 /* LocalStorageManager.swift in Sources */, 17A4FEED25927E730037E96B /* AppDelegate.swift in Sources */, 174D313324EC2831006CA9EE /* WriteFreelyModel.swift in Sources */, 17D435E924E3128F0036B539 /* PreferencesModel.swift in Sources */, 17120DAA24E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */, 17DF32D624C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */, 172C492E2593981900E20ADF /* MacUpdatesView.swift in Sources */, + 1727526728099802003D0A6A /* ErrorConstants.swift in Sources */, 17479F152583D8E40072B7FB /* PostEditorSharingPicker.swift in Sources */, 17480CA6251272EE00EB7765 /* Bundle+AppVersion.swift in Sources */, 17C42E662509237800072984 /* PostListFilteredView.swift in Sources */, 17120DAD24E1B99F002B9F6C /* AccountLoginView.swift in Sources */, 17D4926727947D780035BD7E /* MacUpdatesViewModel.swift in Sources */, 17466626256C0D0600629997 /* MacEditorTextView.swift in Sources */, 170A7EC226F5186A00F1CBD4 /* CollectionListModel.swift in Sources */, + 1727526B2809991A003D0A6A /* ErrorHandling.swift in Sources */, 17E5DF8A2543610700DCDC9B /* PostTextEditingView.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 */, 17BC618A25715318003363CA /* ActivePostToolbarView.swift in Sources */, 171BFDFB24D4AF8300888236 /* CollectionListView.swift in Sources */, 17A67CAF251A5DD7002F163D /* PostEditorView.swift in Sources */, 17DF32AB24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */, 17B37C5725C8679800FE75E9 /* WriteFreelyModel+API.swift in Sources */, 17A5388C24DDC83F00DEFF9A /* AccountModel.swift in Sources */, 17B996D92502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */, 1756DBB824FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */, 17B37C4C25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift in Sources */, 17A5389324DDED0000DEFF9A /* PreferencesView.swift in Sources */, 1756AE6F24CB255B00FD7257 /* PostListModel.swift in Sources */, 1756DC0224FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */, 1756DBB424FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */, 17A5388F24DDEC7400DEFF9A /* AccountView.swift in Sources */, 1780F6EF25895EDB00FE45FF /* PostCommands.swift in Sources */, 17B37C5E25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift in Sources */, 170DFA35251BBC44001D82A0 /* PostEditorModel.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 */ 172E100C2735B83E00061372 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 172E0FFE2735B83E00061372 /* ActionExtension-iOS */; targetProxy = 172E100B2735B83E00061372 /* PBXContainerItemProxy */; }; 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 */ 172E100F2735B83E00061372 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = ""; ASSETCATALOG_COMPILER_APPICON_NAME = AppIconExtension; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 650; DEVELOPMENT_TEAM = TPPAB4YBA6; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "ActionExtension-iOS/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = "Create WriteFreely draft"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0.9; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform.ActionExtension-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 172E10102735B83E00061372 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = ""; ASSETCATALOG_COMPILER_APPICON_NAME = AppIconExtension; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 650; DEVELOPMENT_TEAM = TPPAB4YBA6; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "ActionExtension-iOS/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = "Create WriteFreely draft"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); MARKETING_VERSION = 1.0.9; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform.ActionExtension-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 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 = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 650; 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 = 1.0.9; 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 = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 650; 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 = 1.0.9; 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_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 620; 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.6.0; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; PRODUCT_NAME = "WriteFreely for Mac"; 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_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 620; 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.6.0; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; PRODUCT_NAME = "WriteFreely for Mac"; 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 */ 172E10112735B83E00061372 /* Build configuration list for PBXNativeTarget "ActionExtension-iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 172E100F2735B83E00061372 /* Debug */, 172E10102735B83E00061372 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 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 */ 1784D2EB27946D880033E72E /* XCRemoteSwiftPackageReference "writefreely-swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/writefreely/writefreely-swift"; requirement = { kind = upToNextMajorVersion; minimumVersion = 0.3.4; }; }; 17D4926327947B4D0035BD7E /* XCRemoteSwiftPackageReference "Sparkle" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/sparkle-project/Sparkle"; requirement = { kind = upToNextMinorVersion; minimumVersion = 2.0.0; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 1784D2EC27946D880033E72E /* WriteFreely */ = { isa = XCSwiftPackageProductDependency; package = 1784D2EB27946D880033E72E /* XCRemoteSwiftPackageReference "writefreely-swift" */; productName = WriteFreely; }; 1784D2EE27946D9A0033E72E /* WriteFreely */ = { isa = XCSwiftPackageProductDependency; package = 1784D2EB27946D880033E72E /* XCRemoteSwiftPackageReference "writefreely-swift" */; productName = WriteFreely; }; 1784D2F027946DA10033E72E /* WriteFreely */ = { isa = XCSwiftPackageProductDependency; package = 1784D2EB27946D880033E72E /* XCRemoteSwiftPackageReference "writefreely-swift" */; productName = WriteFreely; }; 17D4926427947B4D0035BD7E /* Sparkle */ = { isa = XCSwiftPackageProductDependency; package = 17D4926327947B4D0035BD7E /* XCRemoteSwiftPackageReference "Sparkle" */; productName = Sparkle; }; /* 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 33a3444..155f2da 100644 --- a/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist @@ -1,24 +1,24 @@ SchemeUserState ActionExtension-iOS.xcscheme_^#shared#^_ orderHint - 1 + 0 WriteFreely-MultiPlatform (iOS).xcscheme_^#shared#^_ orderHint - 2 + 1 WriteFreely-MultiPlatform (macOS).xcscheme_^#shared#^_ orderHint - 0 + 2