Persist content locallytags/v0.1.0
@@ -0,0 +1,17 @@ | |||
import CoreData | |||
extension NSManagedObjectContext { | |||
/// Executes the given `NSBatchDeleteRequest` and directly merges the changes to bring the given | |||
/// managed object context up to date. | |||
/// | |||
/// Credit: https://www.avanderlee.com/swift/nsbatchdeleterequest-core-data/ | |||
/// | |||
/// - Parameter batchDeleteRequest: The `NSBatchDeleteRequest` to execute. | |||
/// - Throws: An error if anything went wrong executing the batch deletion. | |||
public func executeAndMergeChanges(using batchDeleteRequest: NSBatchDeleteRequest) throws { | |||
batchDeleteRequest.resultType = .resultTypeObjectIDs | |||
let result = try execute(batchDeleteRequest) as? NSBatchDeleteResult | |||
let changes: [AnyHashable: Any] = [NSDeletedObjectsKey: result?.result as? [NSManagedObjectID] ?? []] | |||
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [self]) | |||
} | |||
} |
@@ -0,0 +1,64 @@ | |||
import CoreData | |||
#if os(iOS) | |||
import UIKit | |||
#elseif os(macOS) | |||
import AppKit | |||
#endif | |||
class LocalStorageManager { | |||
static let persistentContainer: NSPersistentContainer = { | |||
let container = NSPersistentContainer(name: "LocalStorageModel") | |||
container.loadPersistentStores { _, error in | |||
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy | |||
if let error = error { | |||
fatalError("Unresolved error loading persistent store: \(error)") | |||
} | |||
} | |||
return container | |||
}() | |||
init() { | |||
let center = NotificationCenter.default | |||
#if os(iOS) | |||
let notification = UIApplication.willResignActiveNotification | |||
#elseif os(macOS) | |||
let notification = NSApplication.willResignActiveNotification | |||
#endif | |||
// We don't need to worry about removing this observer because we're targeting iOS 9+ / macOS 10.11+; the | |||
// system will clean this up the next time it would be posted to. | |||
// See: https://developer.apple.com/documentation/foundation/notificationcenter/1413994-removeobserver | |||
// And: https://developer.apple.com/documentation/foundation/notificationcenter/1407263-removeobserver | |||
// swiftlint:disable:next discarded_notification_center_observer | |||
center.addObserver(forName: notification, object: nil, queue: nil, using: self.saveContextOnResignActive) | |||
} | |||
func saveContext() { | |||
if LocalStorageManager.persistentContainer.viewContext.hasChanges { | |||
do { | |||
try LocalStorageManager.persistentContainer.viewContext.save() | |||
} catch { | |||
print("Error saving context: \(error)") | |||
} | |||
} | |||
} | |||
func purgeUserCollections() { | |||
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "WFACollection") | |||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) | |||
do { | |||
try LocalStorageManager.persistentContainer.viewContext.executeAndMergeChanges(using: deleteRequest) | |||
} catch { | |||
print("Error: Failed to purge cached collections.") | |||
} | |||
} | |||
} | |||
private extension LocalStorageManager { | |||
func saveContextOnResignActive(_ notification: Notification) { | |||
saveContext() | |||
} | |||
} |
@@ -0,0 +1,40 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> | |||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17189" systemVersion="20A5364e" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier=""> | |||
<entity name="WFACollection" representedClassName="WFACollection" syncable="YES"> | |||
<attribute name="alias" optional="YES" attributeType="String"/> | |||
<attribute name="blogDescription" optional="YES" attributeType="String"/> | |||
<attribute name="email" optional="YES" attributeType="String"/> | |||
<attribute name="isPublic" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> | |||
<attribute name="styleSheet" optional="YES" attributeType="String"/> | |||
<attribute name="title" attributeType="String"/> | |||
<attribute name="url" optional="YES" attributeType="String"/> | |||
<uniquenessConstraints> | |||
<uniquenessConstraint> | |||
<constraint value="alias"/> | |||
</uniquenessConstraint> | |||
</uniquenessConstraints> | |||
</entity> | |||
<entity name="WFAPost" representedClassName="WFAPost" syncable="YES"> | |||
<attribute name="appearance" optional="YES" attributeType="String"/> | |||
<attribute name="body" attributeType="String"/> | |||
<attribute name="collectionAlias" optional="YES" attributeType="String"/> | |||
<attribute name="createdDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/> | |||
<attribute name="hasNewerRemoteCopy" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> | |||
<attribute name="language" optional="YES" attributeType="String"/> | |||
<attribute name="postId" optional="YES" attributeType="String"/> | |||
<attribute name="rtl" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> | |||
<attribute name="slug" optional="YES" attributeType="String"/> | |||
<attribute name="status" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/> | |||
<attribute name="title" optional="YES" attributeType="String"/> | |||
<attribute name="updatedDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/> | |||
<uniquenessConstraints> | |||
<uniquenessConstraint> | |||
<constraint value="postId"/> | |||
</uniquenessConstraint> | |||
</uniquenessConstraints> | |||
</entity> | |||
<elements> | |||
<element name="WFACollection" positionX="14.806640625" positionY="202.9156341552734" width="128" height="148"/> | |||
<element name="WFAPost" positionX="287.377197265625" positionY="243.2452697753906" width="128" height="209"/> | |||
</elements> | |||
</model> |
@@ -1,108 +0,0 @@ | |||
import Foundation | |||
import WriteFreely | |||
enum PostStatus { | |||
case local | |||
case edited | |||
case published | |||
} | |||
class Post: Identifiable, ObservableObject, Hashable { | |||
@Published var wfPost: WFPost | |||
@Published var status: PostStatus | |||
@Published var collection: PostCollection | |||
@Published var hasNewerRemoteCopy: Bool = false | |||
let id = UUID() | |||
init( | |||
title: String = "Title", | |||
body: String = "Write your post here...", | |||
createdDate: Date = Date(), | |||
status: PostStatus = .local, | |||
collection: PostCollection = draftsCollection | |||
) { | |||
self.wfPost = WFPost(body: body, title: title, createdDate: createdDate) | |||
self.status = status | |||
self.collection = collection | |||
} | |||
convenience init(wfPost: WFPost, in collection: PostCollection = draftsCollection) { | |||
self.init( | |||
title: wfPost.title ?? "", | |||
body: wfPost.body, | |||
createdDate: wfPost.createdDate ?? Date(), | |||
status: .published, | |||
collection: collection | |||
) | |||
self.wfPost = wfPost | |||
} | |||
} | |||
extension Post { | |||
static func == (lhs: Post, rhs: Post) -> Bool { | |||
return lhs.id == rhs.id | |||
} | |||
func hash(into hasher: inout Hasher) { | |||
hasher.combine(id) | |||
} | |||
} | |||
#if DEBUG | |||
let testPost = Post( | |||
title: "Test Post Title", | |||
body: """ | |||
Here's some cool sample body text. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ultrices \ | |||
posuere dignissim. Vestibulum a libero tempor, lacinia nulla vitae, congue purus. Nunc ac nulla quam. Duis \ | |||
tincidunt eros augue, et volutpat tortor pulvinar ut. Nullam sit amet maximus urna. Phasellus non dignissim lacus.\ | |||
Nulla ac posuere ex. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec \ | |||
non molestie mauris. Suspendisse potenti. Vivamus at erat turpis. | |||
Pellentesque porttitor gravida tincidunt. Sed vitae eros non metus aliquam hendrerit. Aliquam sed risus suscipit \ | |||
turpis dictum dictum. Duis lacus lectus, dictum vel felis in, rhoncus fringilla felis. Nunc id dolor nisl. Aliquam \ | |||
euismod purus elit. Nullam egestas neque leo, sed aliquet ligula ultrices nec. | |||
""", | |||
createdDate: Date() | |||
) | |||
let testPostData = [ | |||
Post( | |||
title: "My First Post", | |||
body: "Look at me, creating a first post! That's cool.", | |||
createdDate: Date(timeIntervalSince1970: 1595429452), | |||
status: .published, | |||
collection: userCollection1 | |||
), | |||
Post( | |||
title: "Post 2: The Quickening", | |||
body: "See, here's the rule about Highlander jokes: _there can be only one_.", | |||
createdDate: Date(timeIntervalSince1970: 1595514125), | |||
status: .edited, | |||
collection: userCollection1 | |||
), | |||
Post( | |||
title: "The Post Revolutions", | |||
body: "I can never keep the Matrix movie order straight. Why not just call them part 2 and part 3?", | |||
createdDate: Date(timeIntervalSince1970: 1595600006) | |||
), | |||
Post( | |||
title: "Episode IV: A New Post", | |||
body: "How many movies does this person watch? How many movie-title jokes will they make?", | |||
createdDate: Date(timeIntervalSince1970: 1596219877), | |||
status: .published, | |||
collection: userCollection2 | |||
), | |||
Post( | |||
title: "Fast (Post) Five", | |||
body: "Look, it was either a Fast and the Furious reference, or a Resident Evil reference." | |||
), | |||
Post( | |||
title: "Post: The Final Chapter", | |||
body: "And there you have it, a Resident Evil movie reference.", | |||
createdDate: Date(timeIntervalSince1970: 1596043684), | |||
status: .edited, | |||
collection: userCollection3 | |||
) | |||
] | |||
#endif |
@@ -1,23 +0,0 @@ | |||
import Foundation | |||
import WriteFreely | |||
struct PostCollection: Identifiable { | |||
let id = UUID() | |||
let title: String | |||
var wfCollection: WFCollection? | |||
} | |||
extension PostCollection { | |||
static func == (lhs: PostCollection, rhs: PostCollection) -> Bool { | |||
return lhs.id == rhs.id | |||
} | |||
} | |||
let allPostsCollection = PostCollection(title: "All Posts") | |||
let draftsCollection = PostCollection(title: "Drafts") | |||
#if DEBUG | |||
let userCollection1 = PostCollection(title: "Collection 1") | |||
let userCollection2 = PostCollection(title: "Collection 2") | |||
let userCollection3 = PostCollection(title: "Collection 3") | |||
#endif |
@@ -0,0 +1,7 @@ | |||
import Foundation | |||
enum PostStatus: Int32 { | |||
case local = 0 | |||
case edited = 1 | |||
case published = 2 | |||
} |
@@ -1,66 +0,0 @@ | |||
import Foundation | |||
import WriteFreely | |||
struct PostStore { | |||
var posts: [Post] | |||
init(posts: [Post] = []) { | |||
self.posts = posts | |||
} | |||
mutating func add(_ post: Post) { | |||
posts.append(post) | |||
} | |||
mutating func purgeAllPosts() { | |||
posts = [] | |||
} | |||
mutating func update(_ post: Post) { | |||
// Find the local copy in the store | |||
let localCopy = posts.first(where: { $0.id == post.id }) | |||
// If there's a local copy, update the updatedDate property of its WFPost | |||
if let localCopy = localCopy { | |||
localCopy.wfPost.updatedDate = Date() | |||
} else { | |||
print("Error: Local copy not found") | |||
} | |||
} | |||
mutating func replace(post: Post, with fetchedPost: WFPost) { | |||
// Find the local copy in the store. | |||
let localCopy = posts.first(where: { $0.id == post.id }) | |||
// Replace the local copy's wfPost property with the fetched copy. | |||
if let localCopy = localCopy { | |||
localCopy.wfPost = fetchedPost | |||
DispatchQueue.main.async { | |||
localCopy.hasNewerRemoteCopy = false | |||
localCopy.status = .published | |||
} | |||
} else { | |||
print("Error: Local copy not found") | |||
} | |||
} | |||
mutating func updateStore(with fetchedPosts: [Post]) { | |||
for fetchedPost in fetchedPosts { | |||
// Find the local copy in the store. | |||
let localCopy = posts.first(where: { $0.wfPost.postId == fetchedPost.wfPost.postId }) | |||
// If there's a local copy, check which is newer; if not, add the fetched post to the store. | |||
if let localCopy = localCopy { | |||
// We do not discard the local copy; we simply set the hasNewerRemoteCopy flag accordingly. | |||
if let remoteCopyUpdatedDate = fetchedPost.wfPost.updatedDate, | |||
let localCopyUpdatedDate = localCopy.wfPost.updatedDate { | |||
localCopy.hasNewerRemoteCopy = remoteCopyUpdatedDate > localCopyUpdatedDate | |||
} else { | |||
print("Error: could not determine which copy of post is newer") | |||
} | |||
} else { | |||
add(fetchedPost) | |||
} | |||
} | |||
} | |||
} |
@@ -7,10 +7,13 @@ import Security | |||
class WriteFreelyModel: ObservableObject { | |||
@Published var account = AccountModel() | |||
@Published var preferences = PreferencesModel() | |||
@Published var store = PostStore() | |||
@Published var collections = CollectionListModel(with: []) | |||
@Published var posts = PostListModel() | |||
@Published var isLoggingIn: Bool = false | |||
@Published var selectedPost: Post? | |||
@Published var selectedPost: WFAPost? | |||
#if os(iOS) | |||
@Published var isPresentingSettingsView: Bool = false | |||
#endif | |||
private var client: WFClient? | |||
private let defaults = UserDefaults.standard | |||
@@ -19,13 +22,6 @@ class WriteFreelyModel: ObservableObject { | |||
// Set the color scheme based on what's been saved in UserDefaults. | |||
DispatchQueue.main.async { | |||
self.preferences.appearance = self.defaults.integer(forKey: self.preferences.colorSchemeIntegerKey) | |||
} | |||
#if DEBUG | |||
// for post in testPostData { store.add(post) } | |||
#endif | |||
DispatchQueue.main.async { | |||
self.account.restoreState() | |||
if self.account.isLoggedIn { | |||
guard let serverURL = URL(string: self.account.server) else { | |||
@@ -42,7 +38,6 @@ class WriteFreelyModel: ObservableObject { | |||
self.account.login(WFUser(token: token, username: self.account.username)) | |||
self.client = WFClient(for: serverURL) | |||
self.client?.user = self.account.user | |||
self.collections.clearUserCollection() | |||
self.fetchUserCollections() | |||
self.fetchUserPosts() | |||
} | |||
@@ -83,31 +78,50 @@ extension WriteFreelyModel { | |||
loggedInClient.getPosts(completion: fetchUserPostsHandler) | |||
} | |||
func publish(post: Post) { | |||
func publish(post: WFAPost) { | |||
guard let loggedInClient = client else { return } | |||
if let existingPostId = post.wfPost.postId { | |||
var wfPost = WFPost( | |||
body: post.body, | |||
title: post.title.isEmpty ? "" : post.title, | |||
appearance: post.appearance, | |||
language: post.language, | |||
rtl: post.rtl, | |||
createdDate: post.createdDate | |||
) | |||
if let existingPostId = post.postId { | |||
// This is an existing post. | |||
wfPost.postId = post.postId | |||
wfPost.slug = post.slug | |||
wfPost.updatedDate = post.updatedDate | |||
wfPost.collectionAlias = post.collectionAlias | |||
loggedInClient.updatePost( | |||
postId: existingPostId, | |||
updatedPost: post.wfPost, | |||
updatedPost: wfPost, | |||
completion: publishHandler | |||
) | |||
} else { | |||
// This is a new local draft. | |||
loggedInClient.createPost( | |||
post: post.wfPost, in: post.collection.wfCollection?.alias, completion: publishHandler | |||
post: wfPost, in: post.collectionAlias, completion: publishHandler | |||
) | |||
} | |||
} | |||
func updateFromServer(post: Post) { | |||
func updateFromServer(post: WFAPost) { | |||
guard let loggedInClient = client else { return } | |||
guard let postId = post.wfPost.postId else { return } | |||
guard let postId = post.postId else { return } | |||
DispatchQueue.main.async { | |||
self.selectedPost = post | |||
} | |||
loggedInClient.getPost(byId: postId, completion: updateFromServerHandler) | |||
if let postCollectionAlias = post.collectionAlias, | |||
let postSlug = post.slug { | |||
loggedInClient.getPost(bySlug: postSlug, from: postCollectionAlias, completion: updateFromServerHandler) | |||
} else { | |||
loggedInClient.getPost(byId: postId, completion: updateFromServerHandler) | |||
} | |||
} | |||
} | |||
@@ -133,7 +147,8 @@ private extension WriteFreelyModel { | |||
self.account.currentError = AccountError.invalidPassword | |||
} | |||
} catch { | |||
if let error = error as? NSError, error.domain == NSURLErrorDomain, error.code == -1003 { | |||
if (error as NSError).domain == NSURLErrorDomain, | |||
(error as NSError).code == -1003 { | |||
DispatchQueue.main.async { | |||
self.account.currentError = AccountError.serverNotFound | |||
} | |||
@@ -149,8 +164,8 @@ private extension WriteFreelyModel { | |||
client = nil | |||
DispatchQueue.main.async { | |||
self.account.logout() | |||
self.collections.clearUserCollection() | |||
self.store.purgeAllPosts() | |||
LocalStorageManager().purgeUserCollections() | |||
self.posts.purgeAllPosts() | |||
} | |||
} catch { | |||
print("Something went wrong purging the token from the Keychain.") | |||
@@ -164,8 +179,8 @@ private extension WriteFreelyModel { | |||
client = nil | |||
DispatchQueue.main.async { | |||
self.account.logout() | |||
self.collections.clearUserCollection() | |||
self.store.purgeAllPosts() | |||
LocalStorageManager().purgeUserCollections() | |||
self.posts.purgeAllPosts() | |||
} | |||
} catch { | |||
print("Something went wrong purging the token from the Keychain.") | |||
@@ -175,9 +190,8 @@ private extension WriteFreelyModel { | |||
// so we're using a hacky workaround — if we get the NSURLError, but the AccountModel still thinks we're | |||
// logged in, try calling the logout function again and see what we get. | |||
// Conditional cast from 'Error' to 'NSError' always succeeds but is the only way to check error properties. | |||
if let error = error as? NSError, | |||
error.domain == NSURLErrorDomain, | |||
error.code == NSURLErrorCannotParseResponse { | |||
if (error as NSError).domain == NSURLErrorDomain, | |||
(error as NSError).code == NSURLErrorCannotParseResponse { | |||
if account.isLoggedIn { | |||
self.logout() | |||
} | |||
@@ -188,14 +202,20 @@ private extension WriteFreelyModel { | |||
func fetchUserCollectionsHandler(result: Result<[WFCollection], Error>) { | |||
do { | |||
let fetchedCollections = try result.get() | |||
var fetchedCollectionsArray: [PostCollection] = [] | |||
for fetchedCollection in fetchedCollections { | |||
var postCollection = PostCollection(title: fetchedCollection.title) | |||
postCollection.wfCollection = fetchedCollection | |||
fetchedCollectionsArray.append(postCollection) | |||
DispatchQueue.main.async { | |||
let localCollection = WFACollection(context: LocalStorageManager.persistentContainer.viewContext) | |||
localCollection.alias = fetchedCollection.alias | |||
localCollection.blogDescription = fetchedCollection.description | |||
localCollection.email = fetchedCollection.email | |||
localCollection.isPublic = fetchedCollection.isPublic ?? false | |||
localCollection.styleSheet = fetchedCollection.styleSheet | |||
localCollection.title = fetchedCollection.title | |||
localCollection.url = fetchedCollection.url | |||
} | |||
} | |||
DispatchQueue.main.async { | |||
self.collections = CollectionListModel(with: fetchedCollectionsArray) | |||
LocalStorageManager().saveContext() | |||
} | |||
} catch { | |||
print(error) | |||
@@ -205,21 +225,36 @@ private extension WriteFreelyModel { | |||
func fetchUserPostsHandler(result: Result<[WFPost], Error>) { | |||
do { | |||
let fetchedPosts = try result.get() | |||
var fetchedPostsArray: [Post] = [] | |||
for fetchedPost in fetchedPosts { | |||
var post: Post | |||
if let matchingAlias = fetchedPost.collectionAlias { | |||
let postCollection = ( | |||
collections.userCollections.filter { $0.wfCollection?.alias == matchingAlias } | |||
).first | |||
post = Post(wfPost: fetchedPost, in: postCollection ?? draftsCollection) | |||
// For each fetched post, we | |||
// 1. check to see if a matching post exists | |||
if let managedPost = posts.userPosts.first(where: { $0.postId == fetchedPost.postId }) { | |||
// If it exists, we set the hasNewerRemoteCopy flag as appropriate. | |||
if let fetchedPostUpdatedDate = fetchedPost.updatedDate, | |||
let localPostUpdatedDate = managedPost.updatedDate { | |||
managedPost.hasNewerRemoteCopy = fetchedPostUpdatedDate > localPostUpdatedDate | |||
} else { | |||
print("Error: could not determine which copy of post is newer") | |||
} | |||
} else { | |||
post = Post(wfPost: fetchedPost) | |||
// If it doesn't exist, we create the managed object. | |||
let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext) | |||
managedPost.postId = fetchedPost.postId | |||
managedPost.slug = fetchedPost.slug | |||
managedPost.appearance = fetchedPost.appearance | |||
managedPost.language = fetchedPost.language | |||
managedPost.rtl = fetchedPost.rtl ?? false | |||
managedPost.createdDate = fetchedPost.createdDate | |||
managedPost.updatedDate = fetchedPost.updatedDate | |||
managedPost.title = fetchedPost.title ?? "" | |||
managedPost.body = fetchedPost.body | |||
managedPost.collectionAlias = fetchedPost.collectionAlias | |||
managedPost.status = PostStatus.published.rawValue | |||
} | |||
fetchedPostsArray.append(post) | |||
} | |||
DispatchQueue.main.async { | |||
self.store.updateStore(with: fetchedPostsArray) | |||
LocalStorageManager().saveContext() | |||
self.posts.loadCachedPosts() | |||
} | |||
} catch { | |||
print(error) | |||
@@ -228,13 +263,25 @@ private extension WriteFreelyModel { | |||
func publishHandler(result: Result<WFPost, Error>) { | |||
do { | |||
let wfPost = try result.get() | |||
let foundPostIndex = store.posts.firstIndex(where: { | |||
$0.wfPost.title == wfPost.title && $0.wfPost.body == wfPost.body | |||
let fetchedPost = try result.get() | |||
let foundPostIndex = posts.userPosts.firstIndex(where: { | |||
$0.title == fetchedPost.title && $0.body == fetchedPost.body | |||
}) | |||
guard let index = foundPostIndex else { return } | |||
let cachedPost = self.posts.userPosts[index] | |||
cachedPost.appearance = fetchedPost.appearance | |||
cachedPost.body = fetchedPost.body | |||
cachedPost.collectionAlias = fetchedPost.collectionAlias | |||
cachedPost.createdDate = fetchedPost.createdDate | |||
cachedPost.language = fetchedPost.language | |||
cachedPost.postId = fetchedPost.postId | |||
cachedPost.rtl = fetchedPost.rtl ?? false | |||
cachedPost.slug = fetchedPost.slug | |||
cachedPost.status = PostStatus.published.rawValue | |||
cachedPost.title = fetchedPost.title ?? "" | |||
cachedPost.updatedDate = fetchedPost.updatedDate | |||
DispatchQueue.main.async { | |||
self.store.posts[index].wfPost = wfPost | |||
LocalStorageManager().saveContext() | |||
} | |||
} catch { | |||
print(error) | |||
@@ -242,11 +289,26 @@ private extension WriteFreelyModel { | |||
} | |||
func updateFromServerHandler(result: Result<WFPost, Error>) { | |||
// ⚠️ NOTE: | |||
// The API does not return a collection alias, so we take care not to overwrite the | |||
// cached post's collection alias with the 'nil' value from the fetched post. | |||
// See: https://github.com/writeas/writefreely-swift/issues/20 | |||
do { | |||
let fetchedPost = try result.get() | |||
guard let cachedPost = self.selectedPost else { return } | |||
cachedPost.appearance = fetchedPost.appearance | |||
cachedPost.body = fetchedPost.body | |||
cachedPost.createdDate = fetchedPost.createdDate | |||
cachedPost.language = fetchedPost.language | |||
cachedPost.postId = fetchedPost.postId | |||
cachedPost.rtl = fetchedPost.rtl ?? false | |||
cachedPost.slug = fetchedPost.slug | |||
cachedPost.status = PostStatus.published.rawValue | |||
cachedPost.title = fetchedPost.title ?? "" | |||
cachedPost.updatedDate = fetchedPost.updatedDate | |||
cachedPost.hasNewerRemoteCopy = false | |||
DispatchQueue.main.async { | |||
guard let selectedPost = self.selectedPost else { return } | |||
self.store.replace(post: selectedPost, with: fetchedPost) | |||
LocalStorageManager().saveContext() | |||
} | |||
} catch { | |||
print(error) | |||
@@ -7,23 +7,34 @@ struct ContentView: View { | |||
NavigationView { | |||
SidebarView() | |||
PostListView(selectedCollection: allPostsCollection) | |||
PostListView(selectedCollection: nil, showAllPosts: true) | |||
Text("Select a post, or create a new local draft.") | |||
.foregroundColor(.secondary) | |||
} | |||
.environmentObject(model) | |||
#if os(iOS) | |||
EmptyView() | |||
.sheet( | |||
isPresented: $model.isPresentingSettingsView, | |||
onDismiss: { model.isPresentingSettingsView = false }, | |||
content: { | |||
SettingsView() | |||
.environmentObject(model) | |||
} | |||
) | |||
#endif | |||
} | |||
} | |||
struct ContentView_Previews: PreviewProvider { | |||
static var previews: some View { | |||
let context = LocalStorageManager.persistentContainer.viewContext | |||
let model = WriteFreelyModel() | |||
model.collections = CollectionListModel(with: [userCollection1, userCollection2, userCollection3]) | |||
for post in testPostData { | |||
model.store.add(post) | |||
} | |||
return ContentView() | |||
.environment(\.managedObjectContext, context) | |||
.environmentObject(model) | |||
} | |||
} |
@@ -8,9 +8,11 @@ struct SidebarView: View { | |||
struct SidebarView_Previews: PreviewProvider { | |||
static var previews: some View { | |||
let context = LocalStorageManager.persistentContainer.viewContext | |||
let model = WriteFreelyModel() | |||
model.collections = CollectionListModel(with: [userCollection1, userCollection2, userCollection3]) | |||
return SidebarView() | |||
.environment(\.managedObjectContext, context) | |||
.environmentObject(model) | |||
} | |||
} |
@@ -1,18 +0,0 @@ | |||
import SwiftUI | |||
class CollectionListModel: ObservableObject { | |||
private(set) var userCollections: [PostCollection] = [] | |||
@Published private(set) var collectionsList: [PostCollection] = [ allPostsCollection, draftsCollection ] | |||
init(with userCollections: [PostCollection]) { | |||
for userCollection in userCollections { | |||
self.userCollections.append(userCollection) | |||
} | |||
collectionsList.append(contentsOf: self.userCollections) | |||
} | |||
func clearUserCollection() { | |||
userCollections = [] | |||
collectionsList = [ allPostsCollection, draftsCollection ] | |||
} | |||
} |
@@ -2,14 +2,28 @@ import SwiftUI | |||
struct CollectionListView: View { | |||
@EnvironmentObject var model: WriteFreelyModel | |||
@Environment(\.managedObjectContext) var moc | |||
@FetchRequest( | |||
entity: WFACollection.entity(), | |||
sortDescriptors: [NSSortDescriptor(keyPath: \WFACollection.title, ascending: true)] | |||
) var collections: FetchedResults<WFACollection> | |||
var body: some View { | |||
List { | |||
ForEach(model.collections.collectionsList) { collection in | |||
NavigationLink( | |||
destination: PostListView(selectedCollection: collection) | |||
) { | |||
Text(collection.title) | |||
NavigationLink(destination: PostListView(selectedCollection: nil, showAllPosts: true)) { | |||
Text("All Posts") | |||
} | |||
NavigationLink(destination: PostListView(selectedCollection: nil, showAllPosts: false)) { | |||
Text(model.account.server == "https://write.as" ? "Anonymous" : "Drafts") | |||
} | |||
Section(header: Text("Your Blogs")) { | |||
ForEach(collections, id: \.alias) { collection in | |||
NavigationLink( | |||
destination: PostListView(selectedCollection: collection, showAllPosts: false) | |||
) { | |||
Text(collection.title) | |||
} | |||
} | |||
} | |||
} | |||
@@ -18,11 +32,13 @@ struct CollectionListView: View { | |||
} | |||
} | |||
struct CollectionSidebar_Previews: PreviewProvider { | |||
struct CollectionListView_Previews: PreviewProvider { | |||
static var previews: some View { | |||
let context = LocalStorageManager.persistentContainer.viewContext | |||
let model = WriteFreelyModel() | |||
model.collections = CollectionListModel(with: [userCollection1, userCollection2, userCollection3]) | |||
return CollectionListView() | |||
.environment(\.managedObjectContext, context) | |||
.environmentObject(model) | |||
} | |||
} |
@@ -6,7 +6,7 @@ struct PostEditorStatusToolbarView: View { | |||
#endif | |||
@EnvironmentObject var model: WriteFreelyModel | |||
@ObservedObject var post: Post | |||
@ObservedObject var post: WFAPost | |||
var body: some View { | |||
if post.hasNewerRemoteCopy { | |||
@@ -61,58 +61,27 @@ struct PostEditorStatusToolbarView: View { | |||
} | |||
} | |||
struct ToolbarView_LocalPreviews: PreviewProvider { | |||
struct PESTView_StandardPreviews: PreviewProvider { | |||
static var previews: some View { | |||
let context = LocalStorageManager.persistentContainer.viewContext | |||
let model = WriteFreelyModel() | |||
let post = testPost | |||
return PostEditorStatusToolbarView(post: post) | |||
.environmentObject(model) | |||
} | |||
} | |||
let testPost = WFAPost(context: context) | |||
testPost.status = PostStatus.published.rawValue | |||
struct ToolbarView_RemotePreviews: PreviewProvider { | |||
static var previews: some View { | |||
let model = WriteFreelyModel() | |||
let newerRemotePost = Post( | |||
title: testPost.wfPost.title ?? "", | |||
body: testPost.wfPost.body, | |||
createdDate: testPost.wfPost.createdDate ?? Date(), | |||
status: testPost.status, | |||
collection: testPost.collection | |||
) | |||
newerRemotePost.hasNewerRemoteCopy = true | |||
return PostEditorStatusToolbarView(post: newerRemotePost) | |||
return PostEditorStatusToolbarView(post: testPost) | |||
.environmentObject(model) | |||
} | |||
} | |||
#if os(iOS) | |||
struct ToolbarView_CompactLocalPreviews: PreviewProvider { | |||
struct PESTView_OutdatedLocalCopyPreviews: PreviewProvider { | |||
static var previews: some View { | |||
let context = LocalStorageManager.persistentContainer.viewContext | |||
let model = WriteFreelyModel() | |||
let post = testPost | |||
return PostEditorStatusToolbarView(post: post) | |||
.environmentObject(model) | |||
.environment(\.horizontalSizeClass, .compact) | |||
} | |||
} | |||
#endif | |||
let testPost = WFAPost(context: context) | |||
testPost.status = PostStatus.published.rawValue | |||
testPost.hasNewerRemoteCopy = true | |||
#if os(iOS) | |||
struct ToolbarView_CompactRemotePreviews: PreviewProvider { | |||
static var previews: some View { | |||
let model = WriteFreelyModel() | |||
let newerRemotePost = Post( | |||
title: testPost.wfPost.title ?? "", | |||
body: testPost.wfPost.body, | |||
createdDate: testPost.wfPost.createdDate ?? Date(), | |||
status: testPost.status, | |||
collection: testPost.collection | |||
) | |||
newerRemotePost.hasNewerRemoteCopy = true | |||
return PostEditorStatusToolbarView(post: newerRemotePost) | |||
return PostEditorStatusToolbarView(post: testPost) | |||
.environmentObject(model) | |||
.environment(\.horizontalSizeClass, .compact) | |||
} | |||
} | |||
#endif |
@@ -3,26 +3,23 @@ import SwiftUI | |||
struct PostEditorView: View { | |||
@EnvironmentObject var model: WriteFreelyModel | |||
@ObservedObject var post: Post | |||
@ObservedObject var post: WFAPost | |||
@State private var isNewPost = false | |||
@State private var title = "" | |||
var body: some View { | |||
VStack { | |||
TextEditor(text: $title) | |||
TextEditor(text: $post.title) | |||
.font(.title) | |||
.frame(height: 100) | |||
.onChange(of: title) { _ in | |||
if post.status == .published && post.wfPost.title != title { | |||
post.status = .edited | |||
.onChange(of: post.title) { _ in | |||
if post.status == PostStatus.published.rawValue { | |||
post.status = PostStatus.edited.rawValue | |||
} | |||
post.wfPost.title = title | |||
} | |||
TextEditor(text: $post.wfPost.body) | |||
TextEditor(text: $post.body) | |||
.font(.body) | |||
.onChange(of: post.wfPost.body) { _ in | |||
if post.status == .published { | |||
post.status = .edited | |||
.onChange(of: post.body) { _ in | |||
if post.status == PostStatus.published.rawValue { | |||
post.status = PostStatus.edited.rawValue | |||
} | |||
} | |||
} | |||
@@ -33,66 +30,47 @@ struct PostEditorView: View { | |||
} | |||
ToolbarItem(placement: .primaryAction) { | |||
Button(action: { | |||
model.publish(post: post) | |||
post.status = .published | |||
publishPost() | |||
}, label: { | |||
Image(systemName: "paperplane") | |||
}) | |||
} | |||
} | |||
.onAppear(perform: { | |||
title = post.wfPost.title ?? "" | |||
checkIfNewPost() | |||
if self.isNewPost { | |||
addNewPostToStore() | |||
.onChange(of: post.hasNewerRemoteCopy, perform: { _ in | |||
if post.status == PostStatus.edited.rawValue && !post.hasNewerRemoteCopy { | |||
post.status = PostStatus.published.rawValue | |||
} | |||
}) | |||
.onDisappear(perform: { | |||
if post.status == .edited { | |||
if post.status < PostStatus.published.rawValue { | |||
DispatchQueue.main.async { | |||
model.store.update(post) | |||
LocalStorageManager().saveContext() | |||
} | |||
} | |||
}) | |||
} | |||
private func checkIfNewPost() { | |||
self.isNewPost = !model.store.posts.contains(where: { $0.id == post.id }) | |||
} | |||
private func addNewPostToStore() { | |||
withAnimation { | |||
model.store.add(post) | |||
self.isNewPost = false | |||
private func publishPost() { | |||
DispatchQueue.main.async { | |||
LocalStorageManager().saveContext() | |||
model.posts.loadCachedPosts() | |||
model.publish(post: post) | |||
} | |||
} | |||
} | |||
struct PostEditorView_NewLocalDraftPreviews: PreviewProvider { | |||
struct PostEditorView_Previews: PreviewProvider { | |||
static var previews: some View { | |||
PostEditorView(post: Post()) | |||
.environmentObject(WriteFreelyModel()) | |||
} | |||
} | |||
let context = LocalStorageManager.persistentContainer.viewContext | |||
let testPost = WFAPost(context: context) | |||
testPost.title = "Test Post Title" | |||
testPost.body = "Here's some cool sample body text." | |||
testPost.createdDate = Date() | |||
struct PostEditorView_NewerLocalPostPreviews: PreviewProvider { | |||
static var previews: some View { | |||
return PostEditorView(post: testPost) | |||
.environmentObject(WriteFreelyModel()) | |||
} | |||
} | |||
let model = WriteFreelyModel() | |||
struct PostEditorView_NewerRemotePostPreviews: PreviewProvider { | |||
static var previews: some View { | |||
let newerRemotePost = Post( | |||
title: testPost.wfPost.title ?? "", | |||
body: testPost.wfPost.body, | |||
createdDate: testPost.wfPost.createdDate ?? Date(), | |||
status: testPost.status, | |||
collection: testPost.collection | |||
) | |||
newerRemotePost.hasNewerRemoteCopy = true | |||
return PostEditorView(post: newerRemotePost) | |||
.environmentObject(WriteFreelyModel()) | |||
return PostEditorView(post: testPost) | |||
.environment(\.managedObjectContext, context) | |||
.environmentObject(model) | |||
} | |||
} |
@@ -1,15 +1,15 @@ | |||
import SwiftUI | |||
struct PostCellView: View { | |||
@ObservedObject var post: Post | |||
@ObservedObject var post: WFAPost | |||
var body: some View { | |||
HStack { | |||
VStack(alignment: .leading) { | |||
Text(post.wfPost.title ?? "") | |||
Text(post.title) | |||
.font(.headline) | |||
.lineLimit(1) | |||
Text(buildDateString(from: post.wfPost.createdDate ?? Date())) | |||
Text(buildDateString(from: post.createdDate ?? Date())) | |||
.font(.caption) | |||
.lineLimit(1) | |||
} | |||
@@ -30,6 +30,13 @@ struct PostCellView: View { | |||
struct PostCell_Previews: PreviewProvider { | |||
static var previews: some View { | |||
PostCellView(post: testPost) | |||
let context = LocalStorageManager.persistentContainer.viewContext | |||
let testPost = WFAPost(context: context) | |||
testPost.title = "Test Post Title" | |||
testPost.body = "Here's some cool sample body text." | |||
testPost.createdDate = Date() | |||
return PostCellView(post: testPost) | |||
.environment(\.managedObjectContext, context) | |||
} | |||
} |
@@ -0,0 +1,45 @@ | |||
import SwiftUI | |||
struct PostListFilteredView: View { | |||
var fetchRequest: FetchRequest<WFAPost> | |||
init(filter: String?, showAllPosts: Bool) { | |||
if showAllPosts { | |||
fetchRequest = FetchRequest<WFAPost>( | |||
entity: WFAPost.entity(), | |||
sortDescriptors: [NSSortDescriptor(key: "createdDate", ascending: false)] | |||
) | |||
} else { | |||
if let filter = filter { | |||
fetchRequest = FetchRequest<WFAPost>( | |||
entity: WFAPost.entity(), | |||
sortDescriptors: [NSSortDescriptor(key: "createdDate", ascending: false)], | |||
predicate: NSPredicate(format: "collectionAlias == %@", filter) | |||
) | |||
} else { | |||
fetchRequest = FetchRequest<WFAPost>( | |||
entity: WFAPost.entity(), | |||
sortDescriptors: [NSSortDescriptor(key: "createdDate", ascending: false)], | |||
predicate: NSPredicate(format: "collectionAlias == nil") | |||
) | |||
} | |||
} | |||
} | |||
var body: some View { | |||
List(fetchRequest.wrappedValue, id: \.self) { post in | |||
NavigationLink(destination: PostEditorView(post: post)) { | |||
PostCellView(post: post) | |||
} | |||
} | |||
} | |||
} | |||
struct PostListFilteredView_Previews: PreviewProvider { | |||
static var previews: some View { | |||
let context = LocalStorageManager.persistentContainer.viewContext | |||
return PostListFilteredView(filter: nil, showAllPosts: false) | |||
.environment(\.managedObjectContext, context) | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
import SwiftUI | |||
import CoreData | |||
class PostListModel: ObservableObject { | |||
@Published var userPosts = [WFAPost]() | |||
init() { | |||
loadCachedPosts() | |||
} | |||
func loadCachedPosts() { | |||
let request = WFAPost.createFetchRequest() | |||
let sort = NSSortDescriptor(key: "createdDate", ascending: false) | |||
request.sortDescriptors = [sort] | |||
userPosts = [] | |||
do { | |||
let cachedPosts = try LocalStorageManager.persistentContainer.viewContext.fetch(request) | |||
userPosts.append(contentsOf: cachedPosts) | |||
} catch { | |||
print("Error: Failed to fetch cached posts.") | |||
} | |||
} | |||
func purgeAllPosts() { | |||
userPosts = [] | |||
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "WFAPost") | |||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) | |||
do { | |||
try LocalStorageManager.persistentContainer.viewContext.executeAndMergeChanges(using: deleteRequest) | |||
} catch { | |||
print("Error: Failed to purge cached posts.") | |||
} | |||
} | |||
} |
@@ -2,33 +2,24 @@ import SwiftUI | |||
struct PostListView: View { | |||
@EnvironmentObject var model: WriteFreelyModel | |||
@State var selectedCollection: PostCollection | |||
@Environment(\.managedObjectContext) var moc | |||
#if os(iOS) | |||
@State private var isPresentingSettings = false | |||
#endif | |||
@State var selectedCollection: WFACollection? | |||
@State var showAllPosts: Bool = false | |||
var body: some View { | |||
#if os(iOS) | |||
GeometryReader { geometry in | |||
List { | |||
ForEach(showPosts(for: selectedCollection)) { post in | |||
NavigationLink( | |||
destination: PostEditorView(post: post) | |||
) { | |||
PostCellView( | |||
post: post | |||
) | |||
} | |||
} | |||
} | |||
.environmentObject(model) | |||
.navigationTitle(selectedCollection.title) | |||
PostListFilteredView(filter: selectedCollection?.alias, showAllPosts: showAllPosts) | |||
.navigationTitle( | |||
showAllPosts ? "All Posts" : selectedCollection?.title ?? ( | |||
model.account.server == "https://write.as" ? "Anonymous" : "Drafts" | |||
) | |||
) | |||
.toolbar { | |||
ToolbarItem(placement: .primaryAction) { | |||
Button(action: { | |||
let post = Post() | |||
model.store.add(post) | |||
createNewLocalDraft() | |||
}, label: { | |||
Image(systemName: "square.and.pencil") | |||
}) | |||
@@ -36,18 +27,10 @@ struct PostListView: View { | |||
ToolbarItem(placement: .bottomBar) { | |||
HStack { | |||
Button(action: { | |||
isPresentingSettings = true | |||
model.isPresentingSettingsView = true | |||
}, label: { | |||
Image(systemName: "gear") | |||
}).sheet( | |||
isPresented: $isPresentingSettings, | |||
onDismiss: { | |||
isPresentingSettings = false | |||
}, | |||
content: { | |||
SettingsView(isPresented: $isPresentingSettings) | |||
} | |||
) | |||
}) | |||
.padding(.leading) | |||
Spacer() | |||
Text(pluralizedPostCount(for: showPosts(for: selectedCollection))) | |||
@@ -66,23 +49,16 @@ struct PostListView: View { | |||
} | |||
} | |||
#else //if os(macOS) | |||
List { | |||
ForEach(showPosts(for: selectedCollection)) { post in | |||
NavigationLink( | |||
destination: PostEditorView(post: post) | |||
) { | |||
PostCellView( | |||
post: post | |||
) | |||
} | |||
} | |||
} | |||
.navigationTitle(selectedCollection.title) | |||
PostListFilteredView(filter: selectedCollection?.alias, showAllPosts: showAllPosts) | |||
.navigationTitle( | |||
showAllPosts ? "All Posts" : selectedCollection?.title ?? ( | |||
model.account.server == "https://write.as" ? "Anonymous" : "Drafts" | |||
) | |||
) | |||
.navigationSubtitle(pluralizedPostCount(for: showPosts(for: selectedCollection))) | |||
.toolbar { | |||
Button(action: { | |||
let post = Post() | |||
model.store.add(post) | |||
createNewLocalDraft() | |||
}, label: { | |||
Image(systemName: "square.and.pencil") | |||
}) | |||
@@ -96,7 +72,7 @@ struct PostListView: View { | |||
#endif | |||
} | |||
private func pluralizedPostCount(for posts: [Post]) -> String { | |||
private func pluralizedPostCount(for posts: [WFAPost]) -> String { | |||
if posts.count == 1 { | |||
return "1 post" | |||
} else { | |||
@@ -104,34 +80,47 @@ struct PostListView: View { | |||
} | |||
} | |||
private func showPosts(for collection: PostCollection) -> [Post] { | |||
if collection == allPostsCollection { | |||
return model.store.posts | |||
private func showPosts(for collection: WFACollection?) -> [WFAPost] { | |||
if showAllPosts { | |||
return model.posts.userPosts | |||
} else { | |||
return model.store.posts.filter { | |||
$0.collection.title == collection.title | |||
if let selectedCollection = collection { | |||
return model.posts.userPosts.filter { $0.collectionAlias == selectedCollection.alias } | |||
} else { | |||
return model.posts.userPosts.filter { $0.collectionAlias == nil } | |||
} | |||
} | |||
} | |||
private func reloadFromServer() { | |||
DispatchQueue.main.async { | |||
model.collections.clearUserCollection() | |||
model.fetchUserCollections() | |||
model.fetchUserPosts() | |||
} | |||
} | |||
private func createNewLocalDraft() { | |||
let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext) | |||
managedPost.createdDate = Date() | |||
managedPost.title = "" | |||
managedPost.body = "" | |||
managedPost.status = PostStatus.local.rawValue | |||
if let selectedCollectionAlias = selectedCollection?.alias { | |||
managedPost.collectionAlias = selectedCollectionAlias | |||
} | |||
DispatchQueue.main.async { | |||
LocalStorageManager().saveContext() | |||
} | |||
} | |||
} | |||
struct PostList_Previews: PreviewProvider { | |||
struct PostListView_Previews: PreviewProvider { | |||
static var previews: some View { | |||
let context = LocalStorageManager.persistentContainer.viewContext | |||
let model = WriteFreelyModel() | |||
for post in testPostData { | |||
model.store.add(post) | |||
} | |||
return Group { | |||
PostListView(selectedCollection: allPostsCollection) | |||
.environmentObject(model) | |||
} | |||
return PostListView() | |||
.environment(\.managedObjectContext, context) | |||
.environmentObject(model) | |||
} | |||
} |
@@ -1,10 +1,10 @@ | |||
import SwiftUI | |||
struct PostStatusBadgeView: View { | |||
@ObservedObject var post: Post | |||
@ObservedObject var post: WFAPost | |||
var body: some View { | |||
let (badgeLabel, badgeColor) = setupBadgeProperties(for: post.status) | |||
let (badgeLabel, badgeColor) = setupBadgeProperties(for: PostStatus(rawValue: post.status)!) | |||
Text(badgeLabel) | |||
.font(.caption) | |||
.fontWeight(.semibold) | |||
@@ -38,20 +38,33 @@ struct PostStatusBadgeView: View { | |||
struct PostStatusBadge_LocalDraftPreviews: PreviewProvider { | |||
static var previews: some View { | |||
PostStatusBadgeView(post: testPostData[2]) | |||
let context = LocalStorageManager.persistentContainer.viewContext | |||
let testPost = WFAPost(context: context) | |||
testPost.status = PostStatus.local.rawValue | |||
return PostStatusBadgeView(post: testPost) | |||
.environment(\.managedObjectContext, context) | |||
} | |||
} | |||
struct PostStatusBadge_EditedPreviews: PreviewProvider { | |||
static var previews: some View { | |||
Group { | |||
PostStatusBadgeView(post: testPostData[1]) | |||
} | |||
let context = LocalStorageManager.persistentContainer.viewContext | |||
let testPost = WFAPost(context: context) | |||
testPost.status = PostStatus.edited.rawValue | |||
return PostStatusBadgeView(post: testPost) | |||
.environment(\.managedObjectContext, context) | |||
} | |||
} | |||
struct PostStatusBadge_PublishedPreviews: PreviewProvider { | |||
static var previews: some View { | |||
PostStatusBadgeView(post: testPostData[0]) | |||
let context = LocalStorageManager.persistentContainer.viewContext | |||
let testPost = WFAPost(context: context) | |||
testPost.status = PostStatus.published.rawValue | |||
return PostStatusBadgeView(post: testPost) | |||
.environment(\.managedObjectContext, context) | |||
} | |||
} |
@@ -12,6 +12,7 @@ struct WriteFreely_MultiPlatformApp: App { | |||
WindowGroup { | |||
ContentView() | |||
.environmentObject(model) | |||
.environment(\.managedObjectContext, LocalStorageManager.persistentContainer.viewContext) | |||
// .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info. | |||
} | |||
@@ -0,0 +1,7 @@ | |||
import Foundation | |||
import CoreData | |||
@objc(WFACollection) | |||
public class WFACollection: NSManagedObject { | |||
} |
@@ -0,0 +1,22 @@ | |||
import Foundation | |||
import CoreData | |||
extension WFACollection { | |||
@nonobjc public class func createFetchRequest() -> NSFetchRequest<WFACollection> { | |||
return NSFetchRequest<WFACollection>(entityName: "WFACollection") | |||
} | |||
@NSManaged public var alias: String? | |||
@NSManaged public var blogDescription: String? | |||
@NSManaged public var email: String? | |||
@NSManaged public var isPublic: Bool | |||
@NSManaged public var styleSheet: String? | |||
@NSManaged public var title: String | |||
@NSManaged public var url: String? | |||
} | |||
extension WFACollection: Identifiable { | |||
} |
@@ -0,0 +1,7 @@ | |||
import Foundation | |||
import CoreData | |||
@objc(WFAPost) | |||
public class WFAPost: NSManagedObject { | |||
} |
@@ -0,0 +1,35 @@ | |||
// | |||
// WFAPost+CoreDataProperties.swift | |||
// WriteFreely-MultiPlatform | |||
// | |||
// Created by Angelo Stavrow on 2020-09-08. | |||
// | |||
// | |||
import Foundation | |||
import CoreData | |||
extension WFAPost { | |||
@nonobjc public class func createFetchRequest() -> NSFetchRequest<WFAPost> { | |||
return NSFetchRequest<WFAPost>(entityName: "WFAPost") | |||
} | |||
@NSManaged public var appearance: String? | |||
@NSManaged public var body: String | |||
@NSManaged public var collectionAlias: String? | |||
@NSManaged public var createdDate: Date? | |||
@NSManaged public var language: String? | |||
@NSManaged public var postId: String? | |||
@NSManaged public var rtl: Bool | |||
@NSManaged public var slug: String? | |||
@NSManaged public var status: Int32 | |||
@NSManaged public var title: String | |||
@NSManaged public var updatedDate: Date? | |||
@NSManaged public var hasNewerRemoteCopy: Bool | |||
} | |||
extension WFAPost: Identifiable { | |||
} |
@@ -16,17 +16,13 @@ | |||
17120DAC24E1B99F002B9F6C /* AccountLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DAB24E1B99F002B9F6C /* AccountLoginView.swift */; }; | |||
17120DAD24E1B99F002B9F6C /* AccountLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DAB24E1B99F002B9F6C /* AccountLoginView.swift */; }; | |||
17120DB224E1E19C002B9F6C /* SettingsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DB124E1E19C002B9F6C /* SettingsHeaderView.swift */; }; | |||
171BFDF724D49FD400888236 /* PostCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF624D49FD400888236 /* PostCollection.swift */; }; | |||
171BFDF824D49FD400888236 /* PostCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF624D49FD400888236 /* PostCollection.swift */; }; | |||
171BFDFA24D4AF8300888236 /* CollectionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF924D4AF8300888236 /* CollectionListView.swift */; }; | |||
171BFDFB24D4AF8300888236 /* CollectionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF924D4AF8300888236 /* CollectionListView.swift */; }; | |||
174D313224EC2831006CA9EE /* WriteFreelyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174D313124EC2831006CA9EE /* WriteFreelyModel.swift */; }; | |||
174D313324EC2831006CA9EE /* WriteFreelyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174D313124EC2831006CA9EE /* WriteFreelyModel.swift */; }; | |||
1753F6AC24E431CC00309365 /* MacPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1753F6AB24E431CC00309365 /* MacPreferencesView.swift */; }; | |||
1756AE6B24CB1E4B00FD7257 /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6A24CB1E4B00FD7257 /* Post.swift */; }; | |||
1756AE6C24CB1E4B00FD7257 /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6A24CB1E4B00FD7257 /* Post.swift */; }; | |||
1756AE6E24CB255B00FD7257 /* PostStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6D24CB255B00FD7257 /* PostStore.swift */; }; | |||
1756AE6F24CB255B00FD7257 /* PostStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6D24CB255B00FD7257 /* PostStore.swift */; }; | |||
1756AE6E24CB255B00FD7257 /* PostListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6D24CB255B00FD7257 /* PostListModel.swift */; }; | |||
1756AE6F24CB255B00FD7257 /* PostListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6D24CB255B00FD7257 /* PostListModel.swift */; }; | |||
1756AE7424CB26FA00FD7257 /* PostCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7324CB26FA00FD7257 /* PostCellView.swift */; }; | |||
1756AE7524CB26FA00FD7257 /* PostCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7324CB26FA00FD7257 /* PostCellView.swift */; }; | |||
1756AE7724CB2EDD00FD7257 /* PostEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7624CB2EDD00FD7257 /* PostEditorView.swift */; }; | |||
@@ -36,14 +32,30 @@ | |||
1756AE8124CB844500FD7257 /* View+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE8024CB844500FD7257 /* View+Keyboard.swift */; }; | |||
1756DBB324FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB224FECDBB00207AB8 /* PostEditorStatusToolbarView.swift */; }; | |||
1756DBB424FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB224FECDBB00207AB8 /* PostEditorStatusToolbarView.swift */; }; | |||
1762DCB324EB086C0019C4EB /* CollectionListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1762DCB224EB086C0019C4EB /* CollectionListModel.swift */; }; | |||
1762DCB424EB086C0019C4EB /* CollectionListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1762DCB224EB086C0019C4EB /* CollectionListModel.swift */; }; | |||
1756DBB724FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */; }; | |||
1756DBB824FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */; }; | |||
1756DBBA24FED45500207AB8 /* LocalStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */; }; | |||
1756DBBB24FED45500207AB8 /* LocalStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */; }; | |||
1756DC0124FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */; }; | |||
1756DC0224FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */; }; | |||
1756DC0324FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */; }; | |||
1756DC0424FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */; }; | |||
1765F62A24E18EA200C9EBF0 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1765F62924E18EA200C9EBF0 /* SidebarView.swift */; }; | |||
1765F62B24E18EA200C9EBF0 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1765F62924E18EA200C9EBF0 /* SidebarView.swift */; }; | |||
17A5388824DDA31F00DEFF9A /* MacAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388724DDA31F00DEFF9A /* MacAccountView.swift */; }; | |||
17A5388C24DDC83F00DEFF9A /* AccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388B24DDC83F00DEFF9A /* AccountModel.swift */; }; | |||
17A5388F24DDEC7400DEFF9A /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388D24DDEC7400DEFF9A /* AccountView.swift */; }; | |||
17A5389324DDED0000DEFF9A /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5389124DDED0000DEFF9A /* PreferencesView.swift */; }; | |||
17B996D82502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */; }; | |||
17B996D92502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */; }; | |||
17B996DA2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */; }; | |||
17B996DB2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */; }; | |||
17C42E622507D8E600072984 /* PostStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E612507D8E600072984 /* PostStatus.swift */; }; | |||
17C42E632507D8E600072984 /* PostStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E612507D8E600072984 /* PostStatus.swift */; }; | |||
17C42E652509237800072984 /* PostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E642509237800072984 /* PostListFilteredView.swift */; }; | |||
17C42E662509237800072984 /* PostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E642509237800072984 /* PostListFilteredView.swift */; }; | |||
17C42E70250AA12300072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */; }; | |||
17C42E71250AAFD500072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */; }; | |||
17D435E824E3128F0036B539 /* PreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D435E724E3128F0036B539 /* PreferencesModel.swift */; }; | |||
17D435E924E3128F0036B539 /* PreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D435E724E3128F0036B539 /* PreferencesModel.swift */; }; | |||
17DF329D24C87D3500BCE2E3 /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF329C24C87D3500BCE2E3 /* Tests_iOS.swift */; }; | |||
@@ -82,23 +94,29 @@ | |||
17120DA824E1B2F5002B9F6C /* AccountLogoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountLogoutView.swift; sourceTree = "<group>"; }; | |||
17120DAB24E1B99F002B9F6C /* AccountLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountLoginView.swift; sourceTree = "<group>"; }; | |||
17120DB124E1E19C002B9F6C /* SettingsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHeaderView.swift; sourceTree = "<group>"; }; | |||
171BFDF624D49FD400888236 /* PostCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCollection.swift; sourceTree = "<group>"; }; | |||
171BFDF924D4AF8300888236 /* CollectionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionListView.swift; sourceTree = "<group>"; }; | |||
174D313124EC2831006CA9EE /* WriteFreelyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteFreelyModel.swift; sourceTree = "<group>"; }; | |||
1753F6AB24E431CC00309365 /* MacPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPreferencesView.swift; sourceTree = "<group>"; }; | |||
1756AE6A24CB1E4B00FD7257 /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = "<group>"; }; | |||
1756AE6D24CB255B00FD7257 /* PostStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostStore.swift; sourceTree = "<group>"; }; | |||
1756AE6D24CB255B00FD7257 /* PostListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListModel.swift; sourceTree = "<group>"; }; | |||
1756AE7324CB26FA00FD7257 /* PostCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCellView.swift; sourceTree = "<group>"; }; | |||
1756AE7624CB2EDD00FD7257 /* PostEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorView.swift; sourceTree = "<group>"; }; | |||
1756AE7924CB65DF00FD7257 /* PostListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListView.swift; sourceTree = "<group>"; }; | |||
1756AE8024CB844500FD7257 /* View+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Keyboard.swift"; sourceTree = "<group>"; }; | |||
1756DBB224FECDBB00207AB8 /* PostEditorStatusToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorStatusToolbarView.swift; sourceTree = "<group>"; }; | |||
1762DCB224EB086C0019C4EB /* CollectionListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionListModel.swift; sourceTree = "<group>"; }; | |||
1756DBB624FED3A400207AB8 /* LocalStorageModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LocalStorageModel.xcdatamodel; sourceTree = "<group>"; }; | |||
1756DBB924FED45500207AB8 /* LocalStorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStorageManager.swift; sourceTree = "<group>"; }; | |||
1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFACollection+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; }; | |||
1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFACollection+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; | |||
1765F62924E18EA200C9EBF0 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = "<group>"; }; | |||
17A5388724DDA31F00DEFF9A /* MacAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacAccountView.swift; sourceTree = "<group>"; }; | |||
17A5388B24DDC83F00DEFF9A /* AccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountModel.swift; sourceTree = "<group>"; }; | |||
17A5388D24DDEC7400DEFF9A /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = "<group>"; }; | |||
17A5389124DDED0000DEFF9A /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; }; | |||
17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFAPost+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; }; | |||
17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFAPost+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; | |||
17C42E612507D8E600072984 /* PostStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostStatus.swift; sourceTree = "<group>"; }; | |||
17C42E642509237800072984 /* PostListFilteredView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListFilteredView.swift; sourceTree = "<group>"; }; | |||
17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+ExecuteAndMergeChanges.swift"; sourceTree = "<group>"; }; | |||
17D435E724E3128F0036B539 /* PreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesModel.swift; sourceTree = "<group>"; }; | |||
17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteFreely_MultiPlatformApp.swift; sourceTree = "<group>"; }; | |||
17DF328224C87D3300BCE2E3 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; }; | |||
@@ -178,6 +196,7 @@ | |||
isa = PBXGroup; | |||
children = ( | |||
1756AE8024CB844500FD7257 /* View+Keyboard.swift */, | |||
17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */, | |||
); | |||
path = Extensions; | |||
sourceTree = "<group>"; | |||
@@ -185,10 +204,13 @@ | |||
1762DCB124EB07680019C4EB /* Models */ = { | |||
isa = PBXGroup; | |||
children = ( | |||
1756AE6A24CB1E4B00FD7257 /* Post.swift */, | |||
171BFDF624D49FD400888236 /* PostCollection.swift */, | |||
1756AE6D24CB255B00FD7257 /* PostStore.swift */, | |||
1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */, | |||
1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */, | |||
17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */, | |||
17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */, | |||
17C42E612507D8E600072984 /* PostStatus.swift */, | |||
174D313124EC2831006CA9EE /* WriteFreelyModel.swift */, | |||
1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */, | |||
); | |||
path = Models; | |||
sourceTree = "<group>"; | |||
@@ -233,6 +255,7 @@ | |||
isa = PBXGroup; | |||
children = ( | |||
17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */, | |||
1756DBB924FED45500207AB8 /* LocalStorageManager.swift */, | |||
17DF328324C87D3500BCE2E3 /* Assets.xcassets */, | |||
17DF32D024C8B75C00BCE2E3 /* Account */, | |||
1756AE7F24CB841200FD7257 /* Extensions */, | |||
@@ -325,8 +348,10 @@ | |||
isa = PBXGroup; | |||
children = ( | |||
1756AE7324CB26FA00FD7257 /* PostCellView.swift */, | |||
1756AE6D24CB255B00FD7257 /* PostListModel.swift */, | |||
1756AE7924CB65DF00FD7257 /* PostListView.swift */, | |||
17DF32D424C8CA3400BCE2E3 /* PostStatusBadgeView.swift */, | |||
17C42E642509237800072984 /* PostListFilteredView.swift */, | |||
); | |||
path = PostList; | |||
sourceTree = "<group>"; | |||
@@ -334,7 +359,6 @@ | |||
17DF32D224C8B78D00BCE2E3 /* PostCollection */ = { | |||
isa = PBXGroup; | |||
children = ( | |||
1762DCB224EB086C0019C4EB /* CollectionListModel.swift */, | |||
171BFDF924D4AF8300888236 /* CollectionListView.swift */, | |||
); | |||
path = PostCollection; | |||
@@ -546,26 +570,32 @@ | |||
buildActionMask = 2147483647; | |||
files = ( | |||
17DF32AC24C87D3500BCE2E3 /* ContentView.swift in Sources */, | |||
17C42E622507D8E600072984 /* PostStatus.swift in Sources */, | |||
1756DBBA24FED45500207AB8 /* LocalStorageManager.swift in Sources */, | |||
1756AE8124CB844500FD7257 /* View+Keyboard.swift in Sources */, | |||
17C42E652509237800072984 /* PostListFilteredView.swift in Sources */, | |||
17120DAC24E1B99F002B9F6C /* AccountLoginView.swift in Sources */, | |||
17120DA924E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */, | |||
171BFDFA24D4AF8300888236 /* CollectionListView.swift in Sources */, | |||
1756DBB324FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */, | |||
17120DB224E1E19C002B9F6C /* SettingsHeaderView.swift in Sources */, | |||
1756DBB724FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */, | |||
17B996DA2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */, | |||
1756AE7724CB2EDD00FD7257 /* PostEditorView.swift in Sources */, | |||
17DF32D524C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */, | |||
17D435E824E3128F0036B539 /* PreferencesModel.swift in Sources */, | |||
1765F62A24E18EA200C9EBF0 /* SidebarView.swift in Sources */, | |||
1762DCB324EB086C0019C4EB /* CollectionListModel.swift in Sources */, | |||
1756AE7A24CB65DF00FD7257 /* PostListView.swift in Sources */, | |||
171BFDF724D49FD400888236 /* PostCollection.swift in Sources */, | |||
17B996D82502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */, | |||
1756DC0124FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */, | |||
17DF32AA24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */, | |||
17120DA724E19D11002B9F6C /* SettingsView.swift in Sources */, | |||
1756DC0324FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */, | |||
17120DA224E1985C002B9F6C /* AccountModel.swift in Sources */, | |||
17120DA324E19A42002B9F6C /* PreferencesView.swift in Sources */, | |||
1756AE6E24CB255B00FD7257 /* PostStore.swift in Sources */, | |||
1756AE6E24CB255B00FD7257 /* PostListModel.swift in Sources */, | |||
174D313224EC2831006CA9EE /* WriteFreelyModel.swift in Sources */, | |||
1756AE6B24CB1E4B00FD7257 /* Post.swift in Sources */, | |||
17C42E70250AA12300072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */, | |||
17120DA124E19839002B9F6C /* AccountView.swift in Sources */, | |||
1756AE7424CB26FA00FD7257 /* PostCellView.swift in Sources */, | |||
); | |||
@@ -575,28 +605,34 @@ | |||
isa = PBXSourcesBuildPhase; | |||
buildActionMask = 2147483647; | |||
files = ( | |||
171BFDF824D49FD400888236 /* PostCollection.swift in Sources */, | |||
17DF32AD24C87D3500BCE2E3 /* ContentView.swift in Sources */, | |||
1765F62B24E18EA200C9EBF0 /* SidebarView.swift in Sources */, | |||
1756DBBB24FED45500207AB8 /* LocalStorageManager.swift in Sources */, | |||
174D313324EC2831006CA9EE /* WriteFreelyModel.swift in Sources */, | |||
1756AE7824CB2EDD00FD7257 /* PostEditorView.swift in Sources */, | |||
17D435E924E3128F0036B539 /* PreferencesModel.swift in Sources */, | |||
17120DAA24E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */, | |||
17DF32D624C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */, | |||
17C42E662509237800072984 /* PostListFilteredView.swift in Sources */, | |||
17120DAD24E1B99F002B9F6C /* AccountLoginView.swift in Sources */, | |||
17C42E71250AAFD500072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */, | |||
1756AE7B24CB65DF00FD7257 /* PostListView.swift in Sources */, | |||
1753F6AC24E431CC00309365 /* MacPreferencesView.swift in Sources */, | |||
1756DC0424FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */, | |||
17B996DB2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */, | |||
171BFDFB24D4AF8300888236 /* CollectionListView.swift in Sources */, | |||
17DF32AB24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */, | |||
17A5388C24DDC83F00DEFF9A /* AccountModel.swift in Sources */, | |||
1762DCB424EB086C0019C4EB /* CollectionListModel.swift in Sources */, | |||
17B996D92502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */, | |||
1756DBB824FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */, | |||
17A5389324DDED0000DEFF9A /* PreferencesView.swift in Sources */, | |||
1756AE6F24CB255B00FD7257 /* PostStore.swift in Sources */, | |||
1756AE6F24CB255B00FD7257 /* PostListModel.swift in Sources */, | |||
1756DC0224FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */, | |||
1756DBB424FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */, | |||
1756AE6C24CB1E4B00FD7257 /* Post.swift in Sources */, | |||
17A5388F24DDEC7400DEFF9A /* AccountView.swift in Sources */, | |||
1756AE7524CB26FA00FD7257 /* PostCellView.swift in Sources */, | |||
17A5388824DDA31F00DEFF9A /* MacAccountView.swift in Sources */, | |||
17C42E632507D8E600072984 /* PostStatus.swift in Sources */, | |||
); | |||
runOnlyForDeploymentPostprocessing = 0; | |||
}; | |||
@@ -1002,6 +1038,19 @@ | |||
productName = WriteFreely; | |||
}; | |||
/* End XCSwiftPackageProductDependency section */ | |||
/* Begin XCVersionGroup section */ | |||
1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */ = { | |||
isa = XCVersionGroup; | |||
children = ( | |||
1756DBB624FED3A400207AB8 /* LocalStorageModel.xcdatamodel */, | |||
); | |||
currentVersion = 1756DBB624FED3A400207AB8 /* LocalStorageModel.xcdatamodel */; | |||
path = LocalStorageModel.xcdatamodeld; | |||
sourceTree = "<group>"; | |||
versionGroupType = wrapper.xcdatamodel; | |||
}; | |||
/* End XCVersionGroup section */ | |||
}; | |||
rootObject = 17DF327C24C87D3300BCE2E3 /* Project object */; | |||
} |
@@ -7,12 +7,12 @@ | |||
<key>WriteFreely-MultiPlatform (iOS).xcscheme_^#shared#^_</key> | |||
<dict> | |||
<key>orderHint</key> | |||
<integer>1</integer> | |||
<integer>0</integer> | |||
</dict> | |||
<key>WriteFreely-MultiPlatform (macOS).xcscheme_^#shared#^_</key> | |||
<dict> | |||
<key>orderHint</key> | |||
<integer>0</integer> | |||
<integer>1</integer> | |||
</dict> | |||
</dict> | |||
</dict> | |||
@@ -1,7 +1,7 @@ | |||
import SwiftUI | |||
struct SettingsHeaderView: View { | |||
@Binding var isPresented: Bool | |||
@Environment(\.presentationMode) var presentationMode | |||
var body: some View { | |||
HStack { | |||
@@ -10,7 +10,7 @@ struct SettingsHeaderView: View { | |||
.fontWeight(.bold) | |||
Spacer() | |||
Button(action: { | |||
isPresented = false | |||
presentationMode.wrappedValue.dismiss() | |||
}, label: { | |||
Image(systemName: "xmark.circle") | |||
}) | |||
@@ -21,6 +21,6 @@ struct SettingsHeaderView: View { | |||
struct SettingsHeaderView_Previews: PreviewProvider { | |||
static var previews: some View { | |||
SettingsHeaderView(isPresented: .constant(true)) | |||
SettingsHeaderView() | |||
} | |||
} |
@@ -3,11 +3,9 @@ import SwiftUI | |||
struct SettingsView: View { | |||
@EnvironmentObject var model: WriteFreelyModel | |||
@Binding var isPresented: Bool | |||
var body: some View { | |||
VStack { | |||
SettingsHeaderView(isPresented: $isPresented) | |||
SettingsHeaderView() | |||
Form { | |||
Section(header: Text("Login Details")) { | |||
AccountView() | |||
@@ -23,7 +21,7 @@ struct SettingsView: View { | |||
struct SettingsView_Previews: PreviewProvider { | |||
static var previews: some View { | |||
SettingsView(isPresented: .constant(true)) | |||
SettingsView() | |||
.environmentObject(WriteFreelyModel()) | |||
} | |||
} |