Load user collections from cache on launch, wipe from database on logout

This commit is contained in:
Angelo Stavrow 2020-09-04 12:50:06 -04:00
parent 8e035a43cb
commit 0887638841
No known key found for this signature in database
GPG Key ID: 1A49C7064E060EEE
9 changed files with 195 additions and 112 deletions

View File

@ -10,7 +10,7 @@ enum PostStatus {
class Post: Identifiable, ObservableObject, Hashable {
@Published var wfPost: WFPost
@Published var status: PostStatus
@Published var collection: PostCollection?
@Published var collection: WFACollection?
@Published var hasNewerRemoteCopy: Bool = false
let id = UUID()
@ -20,14 +20,14 @@ class Post: Identifiable, ObservableObject, Hashable {
body: String = "Write your post here...",
createdDate: Date = Date(),
status: PostStatus = .draft,
collection: PostCollection? = nil
collection: WFACollection? = nil
) {
self.wfPost = WFPost(body: body, title: title, createdDate: createdDate)
self.status = status
self.collection = collection
}
convenience init(wfPost: WFPost, in collection: PostCollection? = nil) {
convenience init(wfPost: WFPost, in collection: WFACollection? = nil) {
self.init(
title: wfPost.title ?? "",
body: wfPost.body,
@ -48,65 +48,3 @@ extension Post {
hasher.combine(id)
}
}
#if DEBUG
let userCollection1 = PostCollection(title: "Collection 1")
let userCollection2 = PostCollection(title: "Collection 2")
let userCollection3 = PostCollection(title: "Collection 3")
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

View File

@ -42,8 +42,9 @@ 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()
if self.collections.userCollections.count == 0 {
self.fetchUserCollections()
}
self.fetchUserPosts()
}
}
@ -96,7 +97,7 @@ extension WriteFreelyModel {
} else {
// This is a new local draft.
loggedInClient.createPost(
post: post.wfPost, in: post.collection?.wfCollection?.alias, completion: publishHandler
post: post.wfPost, in: post.collection?.alias, completion: publishHandler
)
}
}
@ -164,8 +165,8 @@ private extension WriteFreelyModel {
try purgeTokenFromKeychain(username: account.user?.username, server: account.server)
client = nil
DispatchQueue.main.async {
self.account.logout()
self.collections.clearUserCollection()
self.account.logout()
self.store.purgeAllPosts()
}
} catch {
@ -186,14 +187,12 @@ private extension WriteFreelyModel {
}
func fetchUserCollectionsHandler(result: Result<[WFCollection], Error>) {
DispatchQueue.main.async {
self.collections.loadCachedUserCollections()
}
do {
let fetchedCollections = try result.get()
var fetchedCollectionsArray: [PostCollection] = []
for fetchedCollection in fetchedCollections {
let postCollection = PostCollection(title: fetchedCollection.title)
postCollection.wfCollection = fetchedCollection
fetchedCollectionsArray.append(postCollection)
DispatchQueue.main.async {
let localCollection = WFACollection(context: PersistenceManager.persistentContainer.viewContext)
localCollection.alias = fetchedCollection.alias
@ -206,7 +205,6 @@ private extension WriteFreelyModel {
}
}
DispatchQueue.main.async {
// self.collections = CollectionListModel(with: fetchedCollectionsArray)
PersistenceManager().saveContext()
}
} catch {
@ -221,10 +219,10 @@ private extension WriteFreelyModel {
for fetchedPost in fetchedPosts {
var post: Post
if let matchingAlias = fetchedPost.collectionAlias {
let postCollection = PostCollection(title: (
let matchingCachedCollection = (
collections.userCollections.filter { $0.alias == matchingAlias }
).first?.title ?? "NO TITLE")
post = Post(wfPost: fetchedPost, in: postCollection)
).first
post = Post(wfPost: fetchedPost, in: matchingCachedCollection)
} else {
post = Post(wfPost: fetchedPost)
}

View File

@ -7,7 +7,7 @@ struct ContentView: View {
NavigationView {
SidebarView()
PostListView(selectedCollection: CollectionListModel.allPostsCollection)
PostListView(selectedCollection: nil)
Text("Select a post, or create a new local draft.")
.foregroundColor(.secondary)

View File

@ -4,22 +4,34 @@ import CoreData
class CollectionListModel: ObservableObject {
@Published var userCollections = [WFACollection]()
static let allPostsCollection = PostCollection(title: "All Posts")
static let draftsCollection = PostCollection(title: "Drafts")
init() {
// let request = WFACollection.createFetchRequest()
// request.sortDescriptors = []
// do {
// userCollections = try PersistenceManager.persistentContainer.viewContext.fetch(request)
// } catch {
// print("Error: Failed to fetch user collections from local store")
// userCollections = []
// }
loadCachedUserCollections()
}
func loadCachedUserCollections() {
let request = WFACollection.createFetchRequest()
let sort = NSSortDescriptor(key: "title", ascending: true)
request.sortDescriptors = [sort]
userCollections = []
do {
let cachedCollections = try PersistenceManager.persistentContainer.viewContext.fetch(request)
userCollections.append(contentsOf: cachedCollections)
} catch {
print("Error: Failed to fetch cached user collections.")
}
}
func clearUserCollection() {
// Make sure the userCollections property is properly populated.
// FIXME: Without this, sometimes the userCollections array is empty.
loadCachedUserCollections()
for userCollection in userCollections {
PersistenceManager.persistentContainer.viewContext.delete(userCollection)
}
PersistenceManager().saveContext()
userCollections = []
// Clear collections from CoreData store.
}
}

View File

@ -4,20 +4,23 @@ struct CollectionListView: View {
@EnvironmentObject var model: WriteFreelyModel
@Environment(\.managedObjectContext) var moc
@FetchRequest(entity: WFACollection.entity(), sortDescriptors: []) var collections: FetchedResults<WFACollection>
@FetchRequest(
entity: WFACollection.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \WFACollection.title, ascending: true)]
) var collections: FetchedResults<WFACollection>
var body: some View {
List {
NavigationLink(destination: PostListView(selectedCollection: CollectionListModel.allPostsCollection)) {
Text(CollectionListModel.allPostsCollection.title)
}
NavigationLink(destination: PostListView(selectedCollection: CollectionListModel.draftsCollection)) {
Text(CollectionListModel.draftsCollection.title)
// NavigationLink(destination: PostListView(selectedCollection: CollectionListModel.allPostsCollection)) {
// Text(CollectionListModel.allPostsCollection.title)
// }
NavigationLink(destination: PostListView(selectedCollection: nil)) {
Text(model.account.server == "https://write.as" ? "Anonymous" : "Drafts")
}
Section(header: Text("Your Blogs")) {
ForEach(collections, id: \.alias) { collection in
NavigationLink(
destination: PostListView(selectedCollection: PostCollection(title: collection.title))
destination: PostListView(selectedCollection: collection)
) {
Text(collection.title)
}

View File

@ -61,6 +61,24 @@ struct PostEditorStatusToolbarView: View {
}
}
#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()
)
#endif
struct ToolbarView_LocalPreviews: PreviewProvider {
static var previews: some View {
let model = WriteFreelyModel()

View File

@ -30,6 +30,11 @@ struct PostCellView: View {
struct PostCell_Previews: PreviewProvider {
static var previews: some View {
PostCellView(post: testPost)
let testPost = Post(
title: "Test Post Title",
body: "Here's some cool sample body text.",
createdDate: Date()
)
return PostCellView(post: testPost)
}
}

View File

@ -2,7 +2,7 @@ import SwiftUI
struct PostListView: View {
@EnvironmentObject var model: WriteFreelyModel
@State var selectedCollection: PostCollection
@State var selectedCollection: WFACollection?
#if os(iOS)
@State private var isPresentingSettings = false
@ -23,7 +23,9 @@ struct PostListView: View {
}
}
.environmentObject(model)
.navigationTitle(selectedCollection.title)
.navigationTitle(
selectedCollection?.title ?? (model.account.server == "https://write.as" ? "Anonymous" : "Drafts")
)
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: {
@ -77,7 +79,9 @@ struct PostListView: View {
}
}
}
.navigationTitle(selectedCollection.title)
.navigationTitle(
selectedCollection?.title ?? (model.account.server == "https://write.as" ? "Anonymous" : "Drafts")
)
.navigationSubtitle(pluralizedPostCount(for: showPosts(for: selectedCollection)))
.toolbar {
Button(action: {
@ -104,15 +108,26 @@ struct PostListView: View {
}
}
private func showPosts(for collection: PostCollection) -> [Post] {
private func showPosts(for collection: WFACollection?) -> [Post] {
var posts: [Post]
if collection == CollectionListModel.allPostsCollection {
posts = model.store.posts
} else if collection == CollectionListModel.draftsCollection {
posts = model.store.posts.filter { $0.collection == nil }
if let selectedCollection = collection {
posts = model.store.posts.filter { $0.wfPost.collectionAlias == selectedCollection.alias }
} else {
posts = model.store.posts.filter { $0.collection?.title == collection.title }
posts = model.store.posts.filter { $0.wfPost.collectionAlias == nil }
}
// for post in model.store.posts {
// print("Post '\(post.wfPost.title ?? "Untitled")' in \(post.collection?.title ?? "No collection")")
// }
// if collection == CollectionListModel.allPostsCollection {
// posts = model.store.posts
// } else if collection == CollectionListModel.draftsCollection {
// posts = model.store.posts.filter { $0.collection == nil }
// } else {
// posts = model.store.posts.filter { $0.collection == collection }
// }
return posts
}
@ -127,12 +142,60 @@ struct PostListView: View {
struct PostList_Previews: PreviewProvider {
static var previews: some View {
let userCollection1 = WFACollection(context: PersistenceManager.persistentContainer.viewContext)
let userCollection2 = WFACollection(context: PersistenceManager.persistentContainer.viewContext)
let userCollection3 = WFACollection(context: PersistenceManager.persistentContainer.viewContext)
userCollection1.title = "Collection 1"
userCollection2.title = "Collection 2"
userCollection3.title = "Collection 3"
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
)
]
let model = WriteFreelyModel()
for post in testPostData {
model.store.add(post)
}
return Group {
PostListView(selectedCollection: CollectionListModel.allPostsCollection)
PostListView(selectedCollection: userCollection1)
.environmentObject(model)
}
}

View File

@ -36,17 +36,63 @@ struct PostStatusBadgeView: View {
}
}
#if DEBUG
let userCollection1 = WFACollection(context: PersistenceManager.persistentContainer.viewContext)
let userCollection2 = WFACollection(context: PersistenceManager.persistentContainer.viewContext)
let userCollection3 = WFACollection(context: PersistenceManager.persistentContainer.viewContext)
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
struct PostStatusBadge_LocalDraftPreviews: PreviewProvider {
static var previews: some View {
userCollection1.title = "Collection 1"
PostStatusBadgeView(post: testPostData[2])
}
}
struct PostStatusBadge_EditedPreviews: PreviewProvider {
static var previews: some View {
Group {
PostStatusBadgeView(post: testPostData[1])
}
userCollection1.title = "Collection 1"
return PostStatusBadgeView(post: testPostData[1])
}
}