Browse Source

Merge pull request #42 from writeas/persist-content-locally

Persist content locally
tags/v0.1.0
Angelo Stavrow 3 years ago
committed by GitHub
parent
commit
353c9e6665
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 632 additions and 472 deletions
  1. +17
    -0
      Shared/Extensions/NSManagedObjectContext+ExecuteAndMergeChanges.swift
  2. +64
    -0
      Shared/LocalStorageManager.swift
  3. +40
    -0
      Shared/Models/LocalStorageModel.xcdatamodeld/LocalStorageModel.xcdatamodel/contents
  4. +0
    -108
      Shared/Models/Post.swift
  5. +0
    -23
      Shared/Models/PostCollection.swift
  6. +7
    -0
      Shared/Models/PostStatus.swift
  7. +0
    -66
      Shared/Models/PostStore.swift
  8. +109
    -47
      Shared/Models/WriteFreelyModel.swift
  9. +16
    -5
      Shared/Navigation/ContentView.swift
  10. +3
    -1
      Shared/Navigation/SidebarView.swift
  11. +0
    -18
      Shared/PostCollection/CollectionListModel.swift
  12. +23
    -7
      Shared/PostCollection/CollectionListView.swift
  13. +12
    -43
      Shared/PostEditor/PostEditorStatusToolbarView.swift
  14. +30
    -52
      Shared/PostEditor/PostEditorView.swift
  15. +11
    -4
      Shared/PostList/PostCellView.swift
  16. +45
    -0
      Shared/PostList/PostListFilteredView.swift
  17. +36
    -0
      Shared/PostList/PostListModel.swift
  18. +47
    -58
      Shared/PostList/PostListView.swift
  19. +20
    -7
      Shared/PostList/PostStatusBadgeView.swift
  20. +1
    -0
      Shared/WriteFreely_MultiPlatformApp.swift
  21. +7
    -0
      WFACollection+CoreDataClass.swift
  22. +22
    -0
      WFACollection+CoreDataProperties.swift
  23. +7
    -0
      WFAPost+CoreDataClass.swift
  24. +35
    -0
      WFAPost+CoreDataProperties.swift
  25. +73
    -24
      WriteFreely-MultiPlatform.xcodeproj/project.pbxproj
  26. +2
    -2
      WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist
  27. +3
    -3
      iOS/Settings/SettingsHeaderView.swift
  28. +2
    -4
      iOS/Settings/SettingsView.swift

+ 17
- 0
Shared/Extensions/NSManagedObjectContext+ExecuteAndMergeChanges.swift View File

@@ -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])
}
}

+ 64
- 0
Shared/LocalStorageManager.swift View File

@@ -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()
}
}

+ 40
- 0
Shared/Models/LocalStorageModel.xcdatamodeld/LocalStorageModel.xcdatamodel/contents View File

@@ -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>

+ 0
- 108
Shared/Models/Post.swift View File

@@ -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

+ 0
- 23
Shared/Models/PostCollection.swift View File

@@ -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

+ 7
- 0
Shared/Models/PostStatus.swift View File

@@ -0,0 +1,7 @@
import Foundation

enum PostStatus: Int32 {
case local = 0
case edited = 1
case published = 2
}

+ 0
- 66
Shared/Models/PostStore.swift View File

@@ -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)
}
}
}
}

+ 109
- 47
Shared/Models/WriteFreelyModel.swift View File

@@ -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)


+ 16
- 5
Shared/Navigation/ContentView.swift View File

@@ -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)
}
}

+ 3
- 1
Shared/Navigation/SidebarView.swift View File

@@ -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)
}
}

+ 0
- 18
Shared/PostCollection/CollectionListModel.swift View File

@@ -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 ]
}
}

+ 23
- 7
Shared/PostCollection/CollectionListView.swift View File

@@ -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)
}
}

+ 12
- 43
Shared/PostEditor/PostEditorStatusToolbarView.swift View File

@@ -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

+ 30
- 52
Shared/PostEditor/PostEditorView.swift View File

@@ -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)
}
}

+ 11
- 4
Shared/PostList/PostCellView.swift View File

@@ -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)
}
}

+ 45
- 0
Shared/PostList/PostListFilteredView.swift View File

@@ -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)
}
}

+ 36
- 0
Shared/PostList/PostListModel.swift View File

@@ -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.")
}
}
}

+ 47
- 58
Shared/PostList/PostListView.swift View File

@@ -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)
}
}

+ 20
- 7
Shared/PostList/PostStatusBadgeView.swift View File

@@ -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)
}
}

+ 1
- 0
Shared/WriteFreely_MultiPlatformApp.swift View File

@@ -12,6 +12,7 @@ struct WriteFreely_MultiPlatformApp: App {
WindowGroup {
ContentView()
.environmentObject(model)
.environment(\.managedObjectContext, LocalStorageManager.persistentContainer.viewContext)
// .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info.
}



+ 7
- 0
WFACollection+CoreDataClass.swift View File

@@ -0,0 +1,7 @@
import Foundation
import CoreData

@objc(WFACollection)
public class WFACollection: NSManagedObject {

}

+ 22
- 0
WFACollection+CoreDataProperties.swift View File

@@ -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 {

}

+ 7
- 0
WFAPost+CoreDataClass.swift View File

@@ -0,0 +1,7 @@
import Foundation
import CoreData

@objc(WFAPost)
public class WFAPost: NSManagedObject {

}

+ 35
- 0
WFAPost+CoreDataProperties.swift View File

@@ -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 {

}

+ 73
- 24
WriteFreely-MultiPlatform.xcodeproj/project.pbxproj View File

@@ -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 */;
}

+ 2
- 2
WriteFreely-MultiPlatform.xcodeproj/xcuserdata/angelo.xcuserdatad/xcschemes/xcschememanagement.plist View File

@@ -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>


+ 3
- 3
iOS/Settings/SettingsHeaderView.swift View File

@@ -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()
}
}

+ 2
- 4
iOS/Settings/SettingsView.swift View File

@@ -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())
}
}

Loading…
Cancel
Save