Use LocalStorageManager 'standard' singleton

This commit is contained in:
Angelo Stavrow 2021-10-08 17:07:06 -04:00
parent 0c41416314
commit 11ad3bc2ff
No known key found for this signature in database
GPG Key ID: 1A49C7064E060EEE
17 changed files with 251 additions and 59 deletions

View File

@ -58,7 +58,7 @@ struct AccountLogoutView: View {
let request = WFAPost.createFetchRequest()
request.predicate = NSPredicate(format: "status == %i", 1)
do {
let editedPosts = try LocalStorageManager.persistentContainer.viewContext.fetch(request)
let editedPosts = try LocalStorageManager.standard.persistentContainer.viewContext.fetch(request)
if editedPosts.count == 1 {
editedPostsWarningString = "You'll lose unpublished changes to \(editedPosts.count) edited post. "
}

View File

@ -55,7 +55,7 @@ extension WriteFreelyModel {
client = nil
DispatchQueue.main.async {
self.account.logout()
LocalStorageManager().purgeUserCollections()
LocalStorageManager.standard.purgeUserCollections()
self.posts.purgePublishedPosts()
}
} catch {
@ -70,7 +70,7 @@ extension WriteFreelyModel {
client = nil
DispatchQueue.main.async {
self.account.logout()
LocalStorageManager().purgeUserCollections()
LocalStorageManager.standard.purgeUserCollections()
self.posts.purgePublishedPosts()
}
} catch {
@ -99,7 +99,7 @@ extension WriteFreelyModel {
let fetchedCollections = try result.get()
for fetchedCollection in fetchedCollections {
DispatchQueue.main.async {
let localCollection = WFACollection(context: LocalStorageManager.persistentContainer.viewContext)
let localCollection = WFACollection(context: LocalStorageManager.standard.persistentContainer.viewContext)
localCollection.alias = fetchedCollection.alias
localCollection.blogDescription = fetchedCollection.description
localCollection.email = fetchedCollection.email
@ -110,7 +110,7 @@ extension WriteFreelyModel {
}
}
DispatchQueue.main.async {
LocalStorageManager().saveContext()
LocalStorageManager.standard.saveContext()
}
} catch WFError.unauthorized {
DispatchQueue.main.async {
@ -130,7 +130,7 @@ extension WriteFreelyModel {
}
let request = WFAPost.createFetchRequest()
do {
let locallyCachedPosts = try LocalStorageManager.persistentContainer.viewContext.fetch(request)
let locallyCachedPosts = try LocalStorageManager.standard.persistentContainer.viewContext.fetch(request)
do {
var postsToDelete = locallyCachedPosts.filter { $0.status != PostStatus.local.rawValue }
let fetchedPosts = try result.get()
@ -146,7 +146,7 @@ extension WriteFreelyModel {
}
} else {
DispatchQueue.main.async {
let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext)
let managedPost = WFAPost(context: LocalStorageManager.standard.persistentContainer.viewContext)
managedPost.postId = fetchedPost.postId
managedPost.slug = fetchedPost.slug
managedPost.appearance = fetchedPost.appearance
@ -164,7 +164,7 @@ extension WriteFreelyModel {
}
DispatchQueue.main.async {
for post in postsToDelete { post.wasDeletedFromServer = true }
LocalStorageManager().saveContext()
LocalStorageManager.standard.saveContext()
}
} catch {
print(error)
@ -204,7 +204,7 @@ extension WriteFreelyModel {
updatingPost.title = fetchedPost.title ?? ""
updatingPost.updatedDate = fetchedPost.updatedDate
DispatchQueue.main.async {
LocalStorageManager().saveContext()
LocalStorageManager.standard.saveContext()
}
} else {
// Otherwise if it's a newly-published post, find it in the local store.
@ -222,7 +222,7 @@ extension WriteFreelyModel {
request.predicate = matchBodyPredicate
}
do {
let cachedPostsResults = try LocalStorageManager.persistentContainer.viewContext.fetch(request)
let cachedPostsResults = try LocalStorageManager.standard.persistentContainer.viewContext.fetch(request)
guard let cachedPost = cachedPostsResults.first else { return }
cachedPost.appearance = fetchedPost.appearance
cachedPost.body = fetchedPost.body
@ -235,7 +235,7 @@ extension WriteFreelyModel {
cachedPost.title = fetchedPost.title ?? ""
cachedPost.updatedDate = fetchedPost.updatedDate
DispatchQueue.main.async {
LocalStorageManager().saveContext()
LocalStorageManager.standard.saveContext()
}
} catch {
print("Error: Failed to fetch cached posts")
@ -270,7 +270,7 @@ extension WriteFreelyModel {
cachedPost.updatedDate = fetchedPost.updatedDate
cachedPost.hasNewerRemoteCopy = false
DispatchQueue.main.async {
LocalStorageManager().saveContext()
LocalStorageManager.standard.saveContext()
}
} catch {
print(error)
@ -293,7 +293,7 @@ extension WriteFreelyModel {
}
} catch {
DispatchQueue.main.async {
LocalStorageManager.persistentContainer.viewContext.rollback()
LocalStorageManager.standard.persistentContainer.viewContext.rollback()
}
print(error)
}

View File

@ -6,19 +6,20 @@ import UIKit
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
}()
final class LocalStorageManager {
public static var standard = LocalStorageManager()
public let persistentContainer: NSPersistentContainer
init() {
// Set up the persistent container.
persistentContainer = NSPersistentContainer(name: "LocalStorageModel")
persistentContainer.loadPersistentStores { description, error in
if let error = error {
fatalError("Core Data store failed to load with error: \(error)")
}
}
persistentContainer.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
let center = NotificationCenter.default
#if os(iOS)
@ -36,9 +37,9 @@ class LocalStorageManager {
}
func saveContext() {
if LocalStorageManager.persistentContainer.viewContext.hasChanges {
if persistentContainer.viewContext.hasChanges {
do {
try LocalStorageManager.persistentContainer.viewContext.save()
try persistentContainer.viewContext.save()
} catch {
print("Error saving context: \(error)")
}
@ -50,7 +51,7 @@ class LocalStorageManager {
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try LocalStorageManager.persistentContainer.viewContext.executeAndMergeChanges(using: deleteRequest)
try persistentContainer.viewContext.executeAndMergeChanges(using: deleteRequest)
} catch {
print("Error: Failed to purge cached collections.")
}

View File

@ -61,7 +61,7 @@ struct ContentView: View {
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let model = WriteFreelyModel()
return ContentView()

View File

@ -2,7 +2,7 @@ import SwiftUI
struct CollectionListView: View {
@EnvironmentObject var model: WriteFreelyModel
@ObservedObject var collections = CollectionListModel(managedObjectContext: LocalStorageManager.persistentContainer.viewContext)
@ObservedObject var collections = CollectionListModel(managedObjectContext: LocalStorageManager.standard.persistentContainer.viewContext)
@State var selectedCollection: WFACollection?
var body: some View {
@ -43,7 +43,7 @@ struct CollectionListView: View {
struct CollectionListView_LoggedOutPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let model = WriteFreelyModel()
return CollectionListView()

View File

@ -27,7 +27,7 @@ struct PostEditorModel {
}
func generateNewLocalPost(withFont appearance: Int) -> WFAPost {
let managedPost = WFAPost(context: LocalStorageManager.persistentContainer.viewContext)
let managedPost = WFAPost(context: LocalStorageManager.standard.persistentContainer.viewContext)
managedPost.createdDate = Date()
managedPost.title = ""
managedPost.body = ""
@ -55,9 +55,9 @@ struct PostEditorModel {
}
private func fetchManagedObject(from objectURL: URL) -> NSManagedObject? {
let coordinator = LocalStorageManager.persistentContainer.persistentStoreCoordinator
let coordinator = LocalStorageManager.standard.persistentContainer.persistentStoreCoordinator
guard let managedObjectID = coordinator.managedObjectID(forURIRepresentation: objectURL) else { return nil }
let object = LocalStorageManager.persistentContainer.viewContext.object(with: managedObjectID)
let object = LocalStorageManager.standard.persistentContainer.viewContext.object(with: managedObjectID)
return object
}
}

View File

@ -65,7 +65,7 @@ struct PostEditorStatusToolbarView: View {
struct PESTView_StandardPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let model = WriteFreelyModel()
let testPost = WFAPost(context: context)
testPost.status = PostStatus.published.rawValue
@ -77,7 +77,7 @@ struct PESTView_StandardPreviews: PreviewProvider {
struct PESTView_OutdatedLocalCopyPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let model = WriteFreelyModel()
let updatedPost = WFAPost(context: context)
updatedPost.status = PostStatus.published.rawValue
@ -90,7 +90,7 @@ struct PESTView_OutdatedLocalCopyPreviews: PreviewProvider {
struct PESTView_DeletedRemoteCopyPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let model = WriteFreelyModel()
let deletedPost = WFAPost(context: context)
deletedPost.status = PostStatus.published.rawValue

View File

@ -46,7 +46,7 @@ struct PostCellView: View {
struct PostCell_AllPostsPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let testPost = WFAPost(context: context)
testPost.title = "Test Post Title"
testPost.body = "Here's some cool sample body text."
@ -59,7 +59,7 @@ struct PostCell_AllPostsPreviews: PreviewProvider {
struct PostCell_NormalPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let testPost = WFAPost(context: context)
testPost.title = "Test Post Title"
testPost.body = "Here's some cool sample body text."
@ -73,7 +73,7 @@ struct PostCell_NormalPreviews: PreviewProvider {
struct PostCell_NoTitlePreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let testPost = WFAPost(context: context)
testPost.title = ""
testPost.body = "Here's some cool sample body text."

View File

@ -4,8 +4,8 @@ import CoreData
class PostListModel: ObservableObject {
func remove(_ post: WFAPost) {
withAnimation {
LocalStorageManager.persistentContainer.viewContext.delete(post)
LocalStorageManager().saveContext()
LocalStorageManager.standard.persistentContainer.viewContext.delete(post)
LocalStorageManager.standard.saveContext()
}
}
@ -15,7 +15,7 @@ class PostListModel: ObservableObject {
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try LocalStorageManager.persistentContainer.viewContext.executeAndMergeChanges(using: deleteRequest)
try LocalStorageManager.standard.persistentContainer.viewContext.executeAndMergeChanges(using: deleteRequest)
} catch {
print("Error: Failed to purge cached posts.")
}

View File

@ -165,7 +165,7 @@ struct PostListView: View {
struct PostListView_Previews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let model = WriteFreelyModel()
return PostListView(showAllPosts: true)

View File

@ -0,0 +1,191 @@
import SwiftUI
import Combine
struct PostListView: View {
@EnvironmentObject var model: WriteFreelyModel
@Environment(\.managedObjectContext) var managedObjectContext
@State private var postCount: Int = 0
@State private var filteredListViewId: Int = 0
var selectedCollection: WFACollection?
var showAllPosts: Bool
#if os(iOS)
private var frameHeight: CGFloat {
var height: CGFloat = 50
let bottom = UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0
height += bottom
return height
}
#endif
var body: some View {
#if os(iOS)
ZStack(alignment: .bottom) {
PostListFilteredView(
collection: selectedCollection,
showAllPosts: showAllPosts,
postCount: $postCount
)
<<<<<<< HEAD
.navigationTitle(
showAllPosts ? "All Posts" : selectedCollection?.title ?? (
model.account.server == "https://write.as" ? "Anonymous" : "Drafts"
)
=======
.id(self.filteredListViewId)
.navigationTitle(
model.showAllPosts ? "All Posts" : model.selectedCollection?.title ?? (
model.account.server == "https://write.as" ? "Anonymous" : "Drafts"
>>>>>>> c9322d1 (Invalidate PostListView on didBecomeActive)
)
)
.toolbar {
ToolbarItem(placement: .primaryAction) {
// We have to add a Spacer as a sibling view to the Button in some kind of Stack, so that any
// a11y modifiers are applied as expected: bug report filed as FB8956392.
ZStack {
Spacer()
Button(action: {
let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font)
withAnimation {
self.model.showAllPosts = false
self.model.selectedPost = managedPost
}
}, label: {
ZStack {
Image("does.not.exist")
.accessibilityHidden(true)
Image(systemName: "square.and.pencil")
.accessibilityHidden(true)
.imageScale(.large) // These modifiers compensate for the resizing
.padding(.vertical, 12) // done to the Image (and the button tap target)
.padding(.leading, 12) // by the SwiftUI layout system from adding a
.padding(.trailing, 8) // Spacer in this ZStack (FB8956392).
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
})
.accessibilityLabel(Text("Compose"))
.accessibilityHint(Text("Compose a new local draft"))
}
}
}
VStack {
HStack(spacing: 0) {
Button(action: {
model.isPresentingSettingsView = true
}, label: {
Image(systemName: "gear")
.padding(.vertical, 4)
.padding(.horizontal, 8)
})
.accessibilityLabel(Text("Settings"))
.accessibilityHint(Text("Open the Settings sheet"))
.sheet(
isPresented: $model.isPresentingSettingsView,
onDismiss: { model.isPresentingSettingsView = false },
content: {
SettingsView()
.environmentObject(model)
}
)
Spacer()
Text(postCount == 1 ? "\(postCount) post" : "\(postCount) posts")
.foregroundColor(.secondary)
.alert(isPresented: $model.isPresentingNetworkErrorAlert, content: {
Alert(
title: Text("Connection Error"),
message: Text("""
There is no internet connection at the moment. Please reconnect or try again later.
"""),
dismissButton: .default(Text("OK"), action: {
model.isPresentingNetworkErrorAlert = false
})
)
})
Spacer()
if model.isProcessingRequest {
ProgressView()
.padding(.vertical, 4)
.padding(.horizontal, 8)
} else {
Button(action: {
DispatchQueue.main.async {
model.fetchUserCollections()
model.fetchUserPosts()
}
}, label: {
Image(systemName: "arrow.clockwise")
.padding(.vertical, 4)
.padding(.horizontal, 8)
})
.accessibilityLabel(Text("Refresh Posts"))
.accessibilityHint(Text("Fetch changes from the server"))
.disabled(!model.account.isLoggedIn)
}
}
.padding(.top, 8)
.padding(.horizontal, 8)
Spacer()
}
.frame(height: frameHeight)
.background(Color(UIColor.systemGray5))
.overlay(Divider(), alignment: .top)
}
.onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
// We use this to invalidate and refresh the view, to make sure we show any new posts that were created
// in an extension, for example.
withAnimation {
self.filteredListViewId += 1
}
}
.ignoresSafeArea()
.onAppear {
model.selectedCollection = selectedCollection
model.showAllPosts = showAllPosts
}
#else
PostListFilteredView(
collection: selectedCollection,
showAllPosts: showAllPosts,
postCount: $postCount
)
.toolbar {
ToolbarItemGroup(placement: .primaryAction) {
if model.selectedPost != nil {
ActivePostToolbarView(activePost: model.selectedPost!)
.alert(isPresented: $model.isPresentingNetworkErrorAlert, content: {
Alert(
title: Text("Connection Error"),
message: Text("""
There is no internet connection at the moment. \
Please reconnect or try again later.
"""),
dismissButton: .default(Text("OK"), action: {
model.isPresentingNetworkErrorAlert = false
})
)
})
}
}
}
.navigationTitle(
showAllPosts ? "All Posts" : selectedCollection?.title ?? (
model.account.server == "https://write.as" ? "Anonymous" : "Drafts"
)
)
#endif
}
}
struct PostListView_Previews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let model = WriteFreelyModel()
return PostListView(showAllPosts: true)
.environment(\.managedObjectContext, context)
.environmentObject(model)
}
}

View File

@ -38,7 +38,7 @@ struct PostStatusBadgeView: View {
struct PostStatusBadge_LocalDraftPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let testPost = WFAPost(context: context)
testPost.status = PostStatus.local.rawValue
@ -49,7 +49,7 @@ struct PostStatusBadge_LocalDraftPreviews: PreviewProvider {
struct PostStatusBadge_EditedPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let testPost = WFAPost(context: context)
testPost.status = PostStatus.edited.rawValue
@ -60,7 +60,7 @@ struct PostStatusBadge_EditedPreviews: PreviewProvider {
struct PostStatusBadge_PublishedPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let testPost = WFAPost(context: context)
testPost.status = PostStatus.published.rawValue

View File

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

View File

@ -158,7 +158,7 @@ struct PostEditorView: View {
self.model.editor.clearLastDraft()
}
DispatchQueue.main.async {
LocalStorageManager().saveContext()
LocalStorageManager.standard.saveContext()
}
})
.onAppear(perform: {
@ -183,7 +183,7 @@ struct PostEditorView: View {
}
} else if post.status != PostStatus.published.rawValue {
DispatchQueue.main.async {
LocalStorageManager().saveContext()
LocalStorageManager.standard.saveContext()
}
}
})
@ -191,7 +191,7 @@ struct PostEditorView: View {
private func publishPost() {
DispatchQueue.main.async {
LocalStorageManager().saveContext()
LocalStorageManager.standard.saveContext()
model.publish(post: post)
}
#if os(iOS)
@ -236,7 +236,7 @@ struct PostEditorView: View {
struct PostEditorView_EmptyPostPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let testPost = WFAPost(context: context)
testPost.createdDate = Date()
testPost.appearance = "norm"
@ -251,7 +251,7 @@ struct PostEditorView_EmptyPostPreviews: PreviewProvider {
struct PostEditorView_ExistingPostPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let testPost = WFAPost(context: context)
testPost.title = "Test Post Title"
testPost.body = "Here's some cool sample body text."

View File

@ -129,7 +129,7 @@ struct ActivePostToolbarView: View {
return
}
DispatchQueue.main.async {
LocalStorageManager().saveContext()
LocalStorageManager.standard.saveContext()
model.publish(post: post)
}
}

View File

@ -35,7 +35,7 @@ struct PostEditorView: View {
self.model.editor.clearLastDraft()
}
DispatchQueue.main.async {
LocalStorageManager().saveContext()
LocalStorageManager.standard.saveContext()
}
})
.onDisappear(perform: {
@ -52,7 +52,7 @@ struct PostEditorView: View {
}
} else if post.status != PostStatus.published.rawValue {
DispatchQueue.main.async {
LocalStorageManager().saveContext()
LocalStorageManager.standard.saveContext()
}
}
})
@ -61,7 +61,7 @@ struct PostEditorView: View {
struct PostEditorView_EmptyPostPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let testPost = WFAPost(context: context)
testPost.createdDate = Date()
testPost.appearance = "norm"
@ -76,7 +76,7 @@ struct PostEditorView_EmptyPostPreviews: PreviewProvider {
struct PostEditorView_ExistingPostPreviews: PreviewProvider {
static var previews: some View {
let context = LocalStorageManager.persistentContainer.viewContext
let context = LocalStorageManager.standard.persistentContainer.viewContext
let testPost = WFAPost(context: context)
testPost.title = "Test Post Title"
testPost.body = "Here's some cool sample body text."

View File

@ -60,7 +60,7 @@ struct PostTextEditingView: View {
.onReceive(timer) { _ in
if !post.body.isEmpty && hasBeenEdited {
DispatchQueue.main.async {
LocalStorageManager().saveContext()
LocalStorageManager.standard.saveContext()
hasBeenEdited = false
}
}
@ -87,7 +87,7 @@ struct PostTextEditingView: View {
private func onCommit() {
if !post.body.isEmpty && hasBeenEdited {
DispatchQueue.main.async {
LocalStorageManager().saveContext()
LocalStorageManager.standard.saveContext()
}
}
hasBeenEdited = false