Skip to content
2 changes: 2 additions & 0 deletions Shared/Account/AccountLoginView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ struct AccountLoginView: View {
.padding()
} else {
Button(action: {
#if os(iOS)
hideKeyboard()
#endif
model.login(
to: URL(string: server)!,
as: username, password: password
Expand Down
32 changes: 31 additions & 1 deletion Shared/Account/AccountLogoutView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import SwiftUI

struct AccountLogoutView: View {
@EnvironmentObject var model: WriteFreelyModel
@Environment(\.managedObjectContext) var moc

@State private var isPresentingLogoutConfirmation: Bool = false
@State private var editedPostsWarningString: String = ""

var body: some View {
#if os(iOS)
VStack {
Spacer()
VStack {
Expand All @@ -31,6 +31,36 @@ struct AccountLogoutView: View {
]
)
})
#else
VStack {
Spacer()
VStack {
Text("Logged in as \(model.account.username)")
Text("on \(model.account.server)")
}
Spacer()
Button(action: logoutHandler, label: {
Text("Log Out")
})
}
.sheet(isPresented: $isPresentingLogoutConfirmation) {
VStack {
Text("Log Out?")
.font(.title)
Text("\(editedPostsWarningString)You won't lose any local posts. Are you sure?")
HStack {
Button(action: model.logout, label: {
Text("Log Out")
})
Button(action: {
self.isPresentingLogoutConfirmation = false
}, label: {
Text("Cancel")
}).keyboardShortcut(.cancelAction)
}
}
}
#endif
}

func logoutHandler() {
Expand Down
132 changes: 69 additions & 63 deletions Shared/Models/WriteFreelyModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,7 @@ class WriteFreelyModel: ObservableObject {
@Published var isLoggingIn: Bool = false
@Published var isProcessingRequest: Bool = false
@Published var hasNetworkConnection: Bool = true
@Published var selectedPost: WFAPost? {
didSet {
if let post = selectedPost {
if post.status != PostStatus.published.rawValue {
editor.setLastDraft(post)
} else {
editor.clearLastDraft()
}
} else {
editor.clearLastDraft()
}
}
}
@Published var selectedPost: WFAPost?
@Published var isPresentingDeleteAlert: Bool = false
@Published var isPresentingLoginErrorAlert: Bool = false
@Published var isPresentingNetworkErrorAlert: Bool = false
Expand Down Expand Up @@ -347,44 +335,49 @@ private extension WriteFreelyModel {
DispatchQueue.main.async {
self.isProcessingRequest = false
}
let request = WFAPost.createFetchRequest()
do {
var postsToDelete = posts.userPosts.filter { $0.status != PostStatus.local.rawValue }
let fetchedPosts = try result.get()
for fetchedPost in fetchedPosts {
if let managedPost = posts.userPosts.first(where: { $0.postId == fetchedPost.postId }) {
managedPost.wasDeletedFromServer = false
if let fetchedPostUpdatedDate = fetchedPost.updatedDate,
let localPostUpdatedDate = managedPost.updatedDate {
managedPost.hasNewerRemoteCopy = fetchedPostUpdatedDate > localPostUpdatedDate
let locallyCachedPosts = try LocalStorageManager.persistentContainer.viewContext.fetch(request)
do {
var postsToDelete = locallyCachedPosts.filter { $0.status != PostStatus.local.rawValue }
let fetchedPosts = try result.get()
for fetchedPost in fetchedPosts {
if let managedPost = locallyCachedPosts.first(where: { $0.postId == fetchedPost.postId }) {
DispatchQueue.main.async {
managedPost.wasDeletedFromServer = false
if let fetchedPostUpdatedDate = fetchedPost.updatedDate,
let localPostUpdatedDate = managedPost.updatedDate {
managedPost.hasNewerRemoteCopy = fetchedPostUpdatedDate > localPostUpdatedDate
} else { print("Error: could not determine which copy of post is newer") }
postsToDelete.removeAll(where: { $0.postId == fetchedPost.postId })
}
} else {
print("Error: could not determine which copy of post is newer")
DispatchQueue.main.async {
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
managedPost.wasDeletedFromServer = false
}
}
postsToDelete.removeAll(where: { $0.postId == fetchedPost.postId })
} else {
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
managedPost.wasDeletedFromServer = false
}
}
for post in postsToDelete {
post.wasDeletedFromServer = true
}
DispatchQueue.main.async {
LocalStorageManager().saveContext()
self.posts.loadCachedPosts()
DispatchQueue.main.async {
for post in postsToDelete { post.wasDeletedFromServer = true }
LocalStorageManager().saveContext()
}
} catch {
print(error)
}
} catch {
print(error)
print("Error: Failed to fetch cached posts")
}
}

Expand All @@ -399,23 +392,37 @@ private extension WriteFreelyModel {
// See: https://github.com/writeas/writefreely-swift/issues/20
do {
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.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 {
LocalStorageManager().saveContext()
let request = WFAPost.createFetchRequest()
let matchBodyPredicate = NSPredicate(format: "body == %@", fetchedPost.body)
if let fetchedPostTitle = fetchedPost.title {
let matchTitlePredicate = NSPredicate(format: "title == %@", fetchedPostTitle)
request.predicate = NSCompoundPredicate(
andPredicateWithSubpredicates: [
matchTitlePredicate,
matchBodyPredicate
]
)
} else {
request.predicate = matchBodyPredicate
}
do {
let cachedPostsResults = try LocalStorageManager.persistentContainer.viewContext.fetch(request)
guard let cachedPost = cachedPostsResults.first 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
DispatchQueue.main.async {
LocalStorageManager().saveContext()
}
} catch {
print("Error: Failed to fetch cached posts")
}
} catch {
print(error)
Expand Down Expand Up @@ -447,7 +454,6 @@ private extension WriteFreelyModel {
cachedPost.hasNewerRemoteCopy = false
DispatchQueue.main.async {
LocalStorageManager().saveContext()
self.posts.loadCachedPosts()
}
} catch {
print(error)
Expand Down
26 changes: 1 addition & 25 deletions Shared/Navigation/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,6 @@ struct ContentView: View {
Text("Select a post, or create a new local draft.")
.foregroundColor(.secondary)
}
.onAppear(perform: {
if let lastDraft = self.model.editor.fetchLastDraft() {
model.selectedPost = lastDraft
} else {
let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext)
managedPost.createdDate = Date()
managedPost.title = ""
managedPost.body = ""
managedPost.status = PostStatus.local.rawValue
switch self.model.preferences.font {
case 1:
managedPost.appearance = "sans"
case 2:
managedPost.appearance = "wrap"
default:
managedPost.appearance = "serif"
}
if let languageCode = Locale.current.languageCode {
managedPost.language = languageCode
managedPost.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft
}
model.selectedPost = managedPost
}
})
.environmentObject(model)
.alert(isPresented: $model.isPresentingDeleteAlert) {
Alert(
Expand All @@ -44,7 +20,7 @@ struct ContentView: View {
primaryButton: .destructive(Text("Delete"), action: {
if let postToDelete = model.postToDelete {
model.selectedPost = nil
withAnimation {
DispatchQueue.main.async {
model.posts.remove(postToDelete)
}
model.postToDelete = nil
Expand Down
1 change: 0 additions & 1 deletion Shared/PostCollection/CollectionListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import SwiftUI

struct CollectionListView: View {
@EnvironmentObject var model: WriteFreelyModel
@Environment(\.managedObjectContext) var moc

@FetchRequest(
entity: WFACollection.entity(),
Expand Down
36 changes: 15 additions & 21 deletions Shared/PostEditor/PostEditorModel.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Foundation
import SwiftUI
import CoreData

enum PostAppearance: String {
Expand All @@ -8,31 +8,25 @@ enum PostAppearance: String {
}

struct PostEditorModel {
let lastDraftObjectURLKey = "lastDraftObjectURLKey"
private(set) var lastDraft: WFAPost?
@AppStorage("lastDraftURL") private var lastDraftURL: URL?

mutating func setLastDraft(_ post: WFAPost) {
lastDraft = post
UserDefaults.standard.set(post.objectID.uriRepresentation(), forKey: lastDraftObjectURLKey)
func saveLastDraft(_ post: WFAPost) {
self.lastDraftURL = post.status != PostStatus.published.rawValue ? post.objectID.uriRepresentation() : nil
}

mutating func fetchLastDraft() -> WFAPost? {
let coordinator = LocalStorageManager.persistentContainer.persistentStoreCoordinator

// See if we have a lastDraftObjectURI
guard let lastDraftObjectURI = UserDefaults.standard.url(forKey: lastDraftObjectURLKey) else { return nil }
func clearLastDraft() {
self.lastDraftURL = nil
}

// See if we can get an ObjectID from the URI representation
guard let lastDraftObjectID = coordinator.managedObjectID(forURIRepresentation: lastDraftObjectURI) else {
return nil
}
func fetchLastDraftFromUserDefaults() -> WFAPost? {
guard let postURL = lastDraftURL else { return nil }

lastDraft = LocalStorageManager.persistentContainer.viewContext.object(with: lastDraftObjectID) as? WFAPost
return lastDraft
}
let coordinator = LocalStorageManager.persistentContainer.persistentStoreCoordinator
guard let postManagedObjectID = coordinator.managedObjectID(forURIRepresentation: postURL) else { return nil }
guard let post = LocalStorageManager.persistentContainer.viewContext.object(
with: postManagedObjectID
) as? WFAPost else { return nil }

mutating func clearLastDraft() {
lastDraft = nil
UserDefaults.standard.removeObject(forKey: lastDraftObjectURLKey)
return post
}
}
19 changes: 16 additions & 3 deletions Shared/PostList/PostListFilteredView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import SwiftUI

struct PostListFilteredView: View {
@EnvironmentObject var model: WriteFreelyModel

@Binding var postCount: Int
@FetchRequest(entity: WFACollection.entity(), sortDescriptors: []) var collections: FetchedResults<WFACollection>
var fetchRequest: FetchRequest<WFAPost>
var showAllPosts: Bool

init(filter: String?, showAllPosts: Bool) {
init(filter: String?, showAllPosts: Bool, postCount: Binding<Int>) {
self.showAllPosts = showAllPosts
if showAllPosts {
fetchRequest = FetchRequest<WFAPost>(
Expand All @@ -29,6 +29,7 @@ struct PostListFilteredView: View {
)
}
}
_postCount = postCount
}

var body: some View {
Expand Down Expand Up @@ -60,6 +61,12 @@ struct PostListFilteredView: View {
}
})
}
.onAppear(perform: {
self.postCount = fetchRequest.wrappedValue.count
})
.onChange(of: fetchRequest.wrappedValue.count, perform: { value in
self.postCount = value
})
#else
List {
ForEach(fetchRequest.wrappedValue, id: \.self) { post in
Expand All @@ -79,6 +86,12 @@ struct PostListFilteredView: View {
}
})
}
.onAppear(perform: {
self.postCount = fetchRequest.wrappedValue.count
})
.onChange(of: fetchRequest.wrappedValue.count, perform: { value in
self.postCount = value
})
.onDeleteCommand(perform: {
guard let selectedPost = model.selectedPost else { return }
if selectedPost.status == PostStatus.local.rawValue {
Expand All @@ -96,6 +109,6 @@ struct PostListFilteredView: View {

struct PostListFilteredView_Previews: PreviewProvider {
static var previews: some View {
return PostListFilteredView(filter: nil, showAllPosts: false)
return PostListFilteredView(filter: nil, showAllPosts: false, postCount: .constant(999))
}
}
Loading