Skip to content

Commit 8f0d21c

Browse files
Merge pull request #118 from writeas/fix-crash-on-launch
Fix crash on launch
2 parents d09ddc3 + efae520 commit 8f0d21c

File tree

11 files changed

+165
-205
lines changed

11 files changed

+165
-205
lines changed

Shared/Account/AccountLoginView.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ struct AccountLoginView: View {
5555
.padding()
5656
} else {
5757
Button(action: {
58+
#if os(iOS)
5859
hideKeyboard()
60+
#endif
5961
model.login(
6062
to: URL(string: server)!,
6163
as: username, password: password

Shared/Account/AccountLogoutView.swift

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import SwiftUI
22

33
struct AccountLogoutView: View {
44
@EnvironmentObject var model: WriteFreelyModel
5-
@Environment(\.managedObjectContext) var moc
65

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

109
var body: some View {
10+
#if os(iOS)
1111
VStack {
1212
Spacer()
1313
VStack {
@@ -31,6 +31,36 @@ struct AccountLogoutView: View {
3131
]
3232
)
3333
})
34+
#else
35+
VStack {
36+
Spacer()
37+
VStack {
38+
Text("Logged in as \(model.account.username)")
39+
Text("on \(model.account.server)")
40+
}
41+
Spacer()
42+
Button(action: logoutHandler, label: {
43+
Text("Log Out")
44+
})
45+
}
46+
.sheet(isPresented: $isPresentingLogoutConfirmation) {
47+
VStack {
48+
Text("Log Out?")
49+
.font(.title)
50+
Text("\(editedPostsWarningString)You won't lose any local posts. Are you sure?")
51+
HStack {
52+
Button(action: model.logout, label: {
53+
Text("Log Out")
54+
})
55+
Button(action: {
56+
self.isPresentingLogoutConfirmation = false
57+
}, label: {
58+
Text("Cancel")
59+
}).keyboardShortcut(.cancelAction)
60+
}
61+
}
62+
}
63+
#endif
3464
}
3565

3666
func logoutHandler() {

Shared/Models/WriteFreelyModel.swift

Lines changed: 69 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,7 @@ class WriteFreelyModel: ObservableObject {
1313
@Published var isLoggingIn: Bool = false
1414
@Published var isProcessingRequest: Bool = false
1515
@Published var hasNetworkConnection: Bool = true
16-
@Published var selectedPost: WFAPost? {
17-
didSet {
18-
if let post = selectedPost {
19-
if post.status != PostStatus.published.rawValue {
20-
editor.setLastDraft(post)
21-
} else {
22-
editor.clearLastDraft()
23-
}
24-
} else {
25-
editor.clearLastDraft()
26-
}
27-
}
28-
}
16+
@Published var selectedPost: WFAPost?
2917
@Published var isPresentingDeleteAlert: Bool = false
3018
@Published var isPresentingLoginErrorAlert: Bool = false
3119
@Published var isPresentingNetworkErrorAlert: Bool = false
@@ -347,44 +335,49 @@ private extension WriteFreelyModel {
347335
DispatchQueue.main.async {
348336
self.isProcessingRequest = false
349337
}
338+
let request = WFAPost.createFetchRequest()
350339
do {
351-
var postsToDelete = posts.userPosts.filter { $0.status != PostStatus.local.rawValue }
352-
let fetchedPosts = try result.get()
353-
for fetchedPost in fetchedPosts {
354-
if let managedPost = posts.userPosts.first(where: { $0.postId == fetchedPost.postId }) {
355-
managedPost.wasDeletedFromServer = false
356-
if let fetchedPostUpdatedDate = fetchedPost.updatedDate,
357-
let localPostUpdatedDate = managedPost.updatedDate {
358-
managedPost.hasNewerRemoteCopy = fetchedPostUpdatedDate > localPostUpdatedDate
340+
let locallyCachedPosts = try LocalStorageManager.persistentContainer.viewContext.fetch(request)
341+
do {
342+
var postsToDelete = locallyCachedPosts.filter { $0.status != PostStatus.local.rawValue }
343+
let fetchedPosts = try result.get()
344+
for fetchedPost in fetchedPosts {
345+
if let managedPost = locallyCachedPosts.first(where: { $0.postId == fetchedPost.postId }) {
346+
DispatchQueue.main.async {
347+
managedPost.wasDeletedFromServer = false
348+
if let fetchedPostUpdatedDate = fetchedPost.updatedDate,
349+
let localPostUpdatedDate = managedPost.updatedDate {
350+
managedPost.hasNewerRemoteCopy = fetchedPostUpdatedDate > localPostUpdatedDate
351+
} else { print("Error: could not determine which copy of post is newer") }
352+
postsToDelete.removeAll(where: { $0.postId == fetchedPost.postId })
353+
}
359354
} else {
360-
print("Error: could not determine which copy of post is newer")
355+
DispatchQueue.main.async {
356+
let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext)
357+
managedPost.postId = fetchedPost.postId
358+
managedPost.slug = fetchedPost.slug
359+
managedPost.appearance = fetchedPost.appearance
360+
managedPost.language = fetchedPost.language
361+
managedPost.rtl = fetchedPost.rtl ?? false
362+
managedPost.createdDate = fetchedPost.createdDate
363+
managedPost.updatedDate = fetchedPost.updatedDate
364+
managedPost.title = fetchedPost.title ?? ""
365+
managedPost.body = fetchedPost.body
366+
managedPost.collectionAlias = fetchedPost.collectionAlias
367+
managedPost.status = PostStatus.published.rawValue
368+
managedPost.wasDeletedFromServer = false
369+
}
361370
}
362-
postsToDelete.removeAll(where: { $0.postId == fetchedPost.postId })
363-
} else {
364-
let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext)
365-
managedPost.postId = fetchedPost.postId
366-
managedPost.slug = fetchedPost.slug
367-
managedPost.appearance = fetchedPost.appearance
368-
managedPost.language = fetchedPost.language
369-
managedPost.rtl = fetchedPost.rtl ?? false
370-
managedPost.createdDate = fetchedPost.createdDate
371-
managedPost.updatedDate = fetchedPost.updatedDate
372-
managedPost.title = fetchedPost.title ?? ""
373-
managedPost.body = fetchedPost.body
374-
managedPost.collectionAlias = fetchedPost.collectionAlias
375-
managedPost.status = PostStatus.published.rawValue
376-
managedPost.wasDeletedFromServer = false
377371
}
378-
}
379-
for post in postsToDelete {
380-
post.wasDeletedFromServer = true
381-
}
382-
DispatchQueue.main.async {
383-
LocalStorageManager().saveContext()
384-
self.posts.loadCachedPosts()
372+
DispatchQueue.main.async {
373+
for post in postsToDelete { post.wasDeletedFromServer = true }
374+
LocalStorageManager().saveContext()
375+
}
376+
} catch {
377+
print(error)
385378
}
386379
} catch {
387-
print(error)
380+
print("Error: Failed to fetch cached posts")
388381
}
389382
}
390383

@@ -399,23 +392,37 @@ private extension WriteFreelyModel {
399392
// See: https://github.com/writeas/writefreely-swift/issues/20
400393
do {
401394
let fetchedPost = try result.get()
402-
let foundPostIndex = posts.userPosts.firstIndex(where: {
403-
$0.title == fetchedPost.title && $0.body == fetchedPost.body
404-
})
405-
guard let index = foundPostIndex else { return }
406-
let cachedPost = self.posts.userPosts[index]
407-
cachedPost.appearance = fetchedPost.appearance
408-
cachedPost.body = fetchedPost.body
409-
cachedPost.createdDate = fetchedPost.createdDate
410-
cachedPost.language = fetchedPost.language
411-
cachedPost.postId = fetchedPost.postId
412-
cachedPost.rtl = fetchedPost.rtl ?? false
413-
cachedPost.slug = fetchedPost.slug
414-
cachedPost.status = PostStatus.published.rawValue
415-
cachedPost.title = fetchedPost.title ?? ""
416-
cachedPost.updatedDate = fetchedPost.updatedDate
417-
DispatchQueue.main.async {
418-
LocalStorageManager().saveContext()
395+
let request = WFAPost.createFetchRequest()
396+
let matchBodyPredicate = NSPredicate(format: "body == %@", fetchedPost.body)
397+
if let fetchedPostTitle = fetchedPost.title {
398+
let matchTitlePredicate = NSPredicate(format: "title == %@", fetchedPostTitle)
399+
request.predicate = NSCompoundPredicate(
400+
andPredicateWithSubpredicates: [
401+
matchTitlePredicate,
402+
matchBodyPredicate
403+
]
404+
)
405+
} else {
406+
request.predicate = matchBodyPredicate
407+
}
408+
do {
409+
let cachedPostsResults = try LocalStorageManager.persistentContainer.viewContext.fetch(request)
410+
guard let cachedPost = cachedPostsResults.first else { return }
411+
cachedPost.appearance = fetchedPost.appearance
412+
cachedPost.body = fetchedPost.body
413+
cachedPost.createdDate = fetchedPost.createdDate
414+
cachedPost.language = fetchedPost.language
415+
cachedPost.postId = fetchedPost.postId
416+
cachedPost.rtl = fetchedPost.rtl ?? false
417+
cachedPost.slug = fetchedPost.slug
418+
cachedPost.status = PostStatus.published.rawValue
419+
cachedPost.title = fetchedPost.title ?? ""
420+
cachedPost.updatedDate = fetchedPost.updatedDate
421+
DispatchQueue.main.async {
422+
LocalStorageManager().saveContext()
423+
}
424+
} catch {
425+
print("Error: Failed to fetch cached posts")
419426
}
420427
} catch {
421428
print(error)
@@ -447,7 +454,6 @@ private extension WriteFreelyModel {
447454
cachedPost.hasNewerRemoteCopy = false
448455
DispatchQueue.main.async {
449456
LocalStorageManager().saveContext()
450-
self.posts.loadCachedPosts()
451457
}
452458
} catch {
453459
print(error)

Shared/Navigation/ContentView.swift

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,6 @@ struct ContentView: View {
1212
Text("Select a post, or create a new local draft.")
1313
.foregroundColor(.secondary)
1414
}
15-
.onAppear(perform: {
16-
if let lastDraft = self.model.editor.fetchLastDraft() {
17-
model.selectedPost = lastDraft
18-
} else {
19-
let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext)
20-
managedPost.createdDate = Date()
21-
managedPost.title = ""
22-
managedPost.body = ""
23-
managedPost.status = PostStatus.local.rawValue
24-
switch self.model.preferences.font {
25-
case 1:
26-
managedPost.appearance = "sans"
27-
case 2:
28-
managedPost.appearance = "wrap"
29-
default:
30-
managedPost.appearance = "serif"
31-
}
32-
if let languageCode = Locale.current.languageCode {
33-
managedPost.language = languageCode
34-
managedPost.rtl = Locale.characterDirection(forLanguage: languageCode) == .rightToLeft
35-
}
36-
model.selectedPost = managedPost
37-
}
38-
})
3915
.environmentObject(model)
4016
.alert(isPresented: $model.isPresentingDeleteAlert) {
4117
Alert(
@@ -44,7 +20,7 @@ struct ContentView: View {
4420
primaryButton: .destructive(Text("Delete"), action: {
4521
if let postToDelete = model.postToDelete {
4622
model.selectedPost = nil
47-
withAnimation {
23+
DispatchQueue.main.async {
4824
model.posts.remove(postToDelete)
4925
}
5026
model.postToDelete = nil

Shared/PostCollection/CollectionListView.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import SwiftUI
22

33
struct CollectionListView: View {
44
@EnvironmentObject var model: WriteFreelyModel
5-
@Environment(\.managedObjectContext) var moc
65

76
@FetchRequest(
87
entity: WFACollection.entity(),
Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import Foundation
1+
import SwiftUI
22
import CoreData
33

44
enum PostAppearance: String {
@@ -8,31 +8,25 @@ enum PostAppearance: String {
88
}
99

1010
struct PostEditorModel {
11-
let lastDraftObjectURLKey = "lastDraftObjectURLKey"
12-
private(set) var lastDraft: WFAPost?
11+
@AppStorage("lastDraftURL") private var lastDraftURL: URL?
1312

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

19-
mutating func fetchLastDraft() -> WFAPost? {
20-
let coordinator = LocalStorageManager.persistentContainer.persistentStoreCoordinator
21-
22-
// See if we have a lastDraftObjectURI
23-
guard let lastDraftObjectURI = UserDefaults.standard.url(forKey: lastDraftObjectURLKey) else { return nil }
17+
func clearLastDraft() {
18+
self.lastDraftURL = nil
19+
}
2420

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

30-
lastDraft = LocalStorageManager.persistentContainer.viewContext.object(with: lastDraftObjectID) as? WFAPost
31-
return lastDraft
32-
}
24+
let coordinator = LocalStorageManager.persistentContainer.persistentStoreCoordinator
25+
guard let postManagedObjectID = coordinator.managedObjectID(forURIRepresentation: postURL) else { return nil }
26+
guard let post = LocalStorageManager.persistentContainer.viewContext.object(
27+
with: postManagedObjectID
28+
) as? WFAPost else { return nil }
3329

34-
mutating func clearLastDraft() {
35-
lastDraft = nil
36-
UserDefaults.standard.removeObject(forKey: lastDraftObjectURLKey)
30+
return post
3731
}
3832
}

Shared/PostList/PostListFilteredView.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import SwiftUI
22

33
struct PostListFilteredView: View {
44
@EnvironmentObject var model: WriteFreelyModel
5-
5+
@Binding var postCount: Int
66
@FetchRequest(entity: WFACollection.entity(), sortDescriptors: []) var collections: FetchedResults<WFACollection>
77
var fetchRequest: FetchRequest<WFAPost>
88
var showAllPosts: Bool
99

10-
init(filter: String?, showAllPosts: Bool) {
10+
init(filter: String?, showAllPosts: Bool, postCount: Binding<Int>) {
1111
self.showAllPosts = showAllPosts
1212
if showAllPosts {
1313
fetchRequest = FetchRequest<WFAPost>(
@@ -29,6 +29,7 @@ struct PostListFilteredView: View {
2929
)
3030
}
3131
}
32+
_postCount = postCount
3233
}
3334

3435
var body: some View {
@@ -60,6 +61,12 @@ struct PostListFilteredView: View {
6061
}
6162
})
6263
}
64+
.onAppear(perform: {
65+
self.postCount = fetchRequest.wrappedValue.count
66+
})
67+
.onChange(of: fetchRequest.wrappedValue.count, perform: { value in
68+
self.postCount = value
69+
})
6370
#else
6471
List {
6572
ForEach(fetchRequest.wrappedValue, id: \.self) { post in
@@ -79,6 +86,12 @@ struct PostListFilteredView: View {
7986
}
8087
})
8188
}
89+
.onAppear(perform: {
90+
self.postCount = fetchRequest.wrappedValue.count
91+
})
92+
.onChange(of: fetchRequest.wrappedValue.count, perform: { value in
93+
self.postCount = value
94+
})
8295
.onDeleteCommand(perform: {
8396
guard let selectedPost = model.selectedPost else { return }
8497
if selectedPost.status == PostStatus.local.rawValue {
@@ -96,6 +109,6 @@ struct PostListFilteredView: View {
96109

97110
struct PostListFilteredView_Previews: PreviewProvider {
98111
static var previews: some View {
99-
return PostListFilteredView(filter: nil, showAllPosts: false)
112+
return PostListFilteredView(filter: nil, showAllPosts: false, postCount: .constant(999))
100113
}
101114
}

0 commit comments

Comments
 (0)