Skip to content

Commit

Permalink
Merge pull request #1389 from planetary-social/refactor-refresh
Browse files Browse the repository at this point in the history
Refactor the New Notes Notification code
  • Loading branch information
joshuatbrown authored Aug 8, 2024
2 parents e533393 + e74b3a2 commit f82ddd0
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 124 deletions.
6 changes: 0 additions & 6 deletions Nos.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@

/* Begin PBXBuildFile section */
030036852C5D39DD002C71F5 /* RefreshController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030036842C5D39DD002C71F5 /* RefreshController.swift */; };
0300368F2C5D3AB4002C71F5 /* MockRefreshController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0300368E2C5D3AB4002C71F5 /* MockRefreshController.swift */; };
030036942C5D3AD3002C71F5 /* RefreshController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030036842C5D39DD002C71F5 /* RefreshController.swift */; };
030036952C5D3AD9002C71F5 /* MockRefreshController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0300368E2C5D3AB4002C71F5 /* MockRefreshController.swift */; };
030036AB2C5D872B002C71F5 /* NewNotesButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030036AA2C5D872B002C71F5 /* NewNotesButton.swift */; };
030AE4292BE3D63C004DEE02 /* FeaturedAuthor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030AE4282BE3D63C004DEE02 /* FeaturedAuthor.swift */; };
0320C0FB2BFE43A600C4C080 /* RelayServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0320C0FA2BFE43A600C4C080 /* RelayServiceTests.swift */; };
Expand Down Expand Up @@ -519,7 +517,6 @@

/* Begin PBXFileReference section */
030036842C5D39DD002C71F5 /* RefreshController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshController.swift; sourceTree = "<group>"; };
0300368E2C5D3AB4002C71F5 /* MockRefreshController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRefreshController.swift; sourceTree = "<group>"; };
030036AA2C5D872B002C71F5 /* NewNotesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNotesButton.swift; sourceTree = "<group>"; };
030AE4282BE3D63C004DEE02 /* FeaturedAuthor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedAuthor.swift; sourceTree = "<group>"; };
0320C0E42BFBB27E00C4C080 /* PerformanceTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = PerformanceTests.xctestplan; sourceTree = "<group>"; };
Expand Down Expand Up @@ -992,7 +989,6 @@
0357299C2BE41653005FEE85 /* ContentWarningControllerTests.swift */,
0357299D2BE41653005FEE85 /* SocialGraphTests.swift */,
C99314932C5BE13600224BA6 /* NoteEditorControllerTests.swift */,
0300368E2C5D3AB4002C71F5 /* MockRefreshController.swift */,
);
path = Controller;
sourceTree = "<group>";
Expand Down Expand Up @@ -1962,7 +1958,6 @@
5BC0D9CC2B867B9D005D6980 /* NamesAPI.swift in Sources */,
C987F81D29BA6D9A00B44E7A /* ProfileTab.swift in Sources */,
C9ADB14129951CB10075E7F8 /* NSManagedObject+Nos.swift in Sources */,
030036952C5D3AD9002C71F5 /* MockRefreshController.swift in Sources */,
C9F84C21298DC36800C6714D /* AppView.swift in Sources */,
C9CE5B142A0172CF008E198C /* WebView.swift in Sources */,
CD4908D429B92941007443DB /* ReportABugMailView.swift in Sources */,
Expand Down Expand Up @@ -2321,7 +2316,6 @@
C9ADB14229951CB10075E7F8 /* NSManagedObject+Nos.swift in Sources */,
035729AB2BE4167E005FEE85 /* AuthorTests.swift in Sources */,
03B4E6AC2C125D13006E5F59 /* FileStorageUploadResponseJSONTests.swift in Sources */,
0300368F2C5D3AB4002C71F5 /* MockRefreshController.swift in Sources */,
C92DF80629C25DE900400561 /* URL+Extensions.swift in Sources */,
C9BAB09C2996FBA10003A84E /* EventProcessor.swift in Sources */,
C992B32B2B3613CC00704A9C /* SubscriptionCancellable.swift in Sources */,
Expand Down
6 changes: 1 addition & 5 deletions Nos/Controller/PagedNoteDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ class PagedNoteDataSource<Header: View, EmptyPlaceholder: View>: NSObject, UICol
private var managedObjectContext: NSManagedObjectContext
private var header: () -> Header
private var emptyPlaceholder: () -> EmptyPlaceholder
/// An action to perform when the data source is refreshed.
private var onRefresh: () -> Void
let pageSize = 20

// We intentionally generate unique IDs for cell reuse to get around
Expand All @@ -34,8 +32,7 @@ class PagedNoteDataSource<Header: View, EmptyPlaceholder: View>: NSObject, UICol
collectionView: UICollectionView,
managedObjectContext: NSManagedObjectContext,
@ViewBuilder header: @escaping () -> Header,
@ViewBuilder emptyPlaceholder: @escaping () -> EmptyPlaceholder,
onRefresh: @escaping () -> Void
@ViewBuilder emptyPlaceholder: @escaping () -> EmptyPlaceholder
) {
self.databaseFilter = databaseFilter
self.fetchedResultsController = NSFetchedResultsController<Event>(
Expand All @@ -50,7 +47,6 @@ class PagedNoteDataSource<Header: View, EmptyPlaceholder: View>: NSObject, UICol
self.relay = relay
self.header = header
self.emptyPlaceholder = emptyPlaceholder
self.onRefresh = onRefresh

super.init()

Expand Down
37 changes: 10 additions & 27 deletions Nos/Controller/RefreshController.swift
Original file line number Diff line number Diff line change
@@ -1,36 +1,19 @@
import Foundation

/// Defines a common interface for refreshing.
@MainActor protocol RefreshController: Observable {
/// Whether a refresh should begin or not.
var shouldRefresh: Bool { get }
@Observable @MainActor class RefreshController {
/// Whether a refresh should start or not. When this is `true`, the view and data source will begin refreshing.
var startRefresh: Bool

/// The last time the view was refreshed.
var lastRefreshDate: Date { get }

/// Update the state of `shouldRefresh` to the given value.
func setShouldRefresh(_: Bool)

/// Updates the last refresh date to the given value.
func setLastRefreshDate(_: Date)
}

/// The default implementation of `RefreshController`.
@Observable @MainActor class DefaultRefreshController: RefreshController {
var shouldRefresh: Bool

var lastRefreshDate: Date

init(shouldRefresh: Bool = false, lastRefreshDate: Date = .now) {
self.shouldRefresh = shouldRefresh
self.lastRefreshDate = lastRefreshDate
}

func setShouldRefresh(_ shouldRefresh: Bool) {
self.shouldRefresh = shouldRefresh
}

func setLastRefreshDate(_ lastRefreshDate: Date) {

/// Initializes a refresh controller with the given parameters.
/// - Parameters:
/// - startRefresh: Whether a refresh should start or not. Defaults to `false`.
/// - lastRefreshDate: The last time the view was refreshed. Defaults to `.now`.
init(startRefresh: Bool = false, lastRefreshDate: Date = .now) {
self.startRefresh = startRefresh
self.lastRefreshDate = lastRefreshDate
}
}
78 changes: 49 additions & 29 deletions Nos/Models/CoreData/Event+CoreDataClass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -338,42 +338,62 @@ public class Event: NosManagedObject, VerifiableEvent {
return fetchRequest
}

@nonobjc public class func homeFeedPredicate(
for user: Author,
before: Date,
/// Returns a predicate that can be used to fetch the given user's home feed.
/// - Parameters:
/// - user: The user whose home feed should appear.
/// - before: Only fetch events that were created before this date. Defaults to `nil`.
/// - after: Only fetch events that were created after this date. Defaults to `nil`.
/// - relay: Only fetch events on this relay. Defaults to `nil`, which uses all the user's relays.
/// - Returns: A predicate matching the given parameters that can be used to fetch the user's home feed.
@nonobjc private class func homeFeedPredicate(
for user: Author,
before: Date? = nil,
after: Date? = nil,
seenOn relay: Relay? = nil
) -> NSPredicate {
// swiftlint:disable:next line_length
var queryString = "((kind = 1 AND SUBQUERY(eventReferences, $reference, $reference.marker = 'root' OR $reference.marker = 'reply' OR $reference.marker = nil).@count = 0) OR kind = 6 OR kind = 30023) AND author.muted = 0 AND createdAt <= %@ AND deletedOn.@count = 0"
var arguments: [CVarArg] = [before as CVarArg]
if let relay {
queryString.append(" AND ANY seenOnRelays = %@")
arguments.append(relay)
} else {
queryString.append(" AND (ANY author.followers.source = %@ OR author = %@)")
arguments += [user, user]
let kind1Predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
NSPredicate(format: "kind = 1"),
NSPredicate(
format: "SUBQUERY(" +
"eventReferences, $reference, $reference.marker = 'root'" +
" OR $reference.marker = 'reply'" +
" OR $reference.marker = nil" +
").@count = 0"
)
])
let kind6Predicate = NSPredicate(format: "kind = 6")
let kind30023Predicate = NSPredicate(format: "kind = 30023")

let kindsPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [
kind1Predicate,
kind6Predicate,
kind30023Predicate
])

let notMutedPredicate = NSPredicate(format: "author.muted = 0")
let notDeletedPredicate = NSPredicate(format: "deletedOn.@count = 0")

var andPredicates = [kindsPredicate, notMutedPredicate, notDeletedPredicate]

if let before {
andPredicates.append(NSPredicate(format: "createdAt <= %@", before as CVarArg))
}
return NSPredicate(format: queryString, argumentArray: arguments)
}

@nonobjc public class func homeFeedPredicate(
for user: Author,
after: Date,
seenOn relay: Relay? = nil
) -> NSPredicate {
// swiftlint:disable:next line_length
var queryString = "((kind = 1 AND SUBQUERY(eventReferences, $reference, $reference.marker = 'root' OR $reference.marker = 'reply' OR $reference.marker = nil).@count = 0) OR kind = 6 OR kind = 30023) AND author.muted = 0 AND createdAt > %@ AND deletedOn.@count = 0"
var arguments: [CVarArg] = [after as CVarArg]

if let after {
andPredicates.append(NSPredicate(format: "createdAt > %@", after as CVarArg))
}

if let relay {
queryString.append(" AND ANY seenOnRelays = %@")
arguments.append(relay)
andPredicates.append(NSPredicate(format: "ANY seenOnRelays = %@", relay as CVarArg))
} else {
queryString.append(" AND (ANY author.followers.source = %@ OR author = %@)")
arguments += [user, user]
andPredicates.append(
NSPredicate(format: "(ANY author.followers.source = %@ OR author = %@)", user, user)
)
}
return NSPredicate(format: queryString, argumentArray: arguments)

return NSCompoundPredicate(andPredicateWithSubpredicates: andPredicates)
}

@nonobjc public class func homeFeed(
for user: Author,
before: Date,
Expand Down
13 changes: 4 additions & 9 deletions Nos/Views/Home/HomeFeedView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ struct HomeFeedView: View {
@Environment(CurrentUser.self) var currentUser
@ObservationIgnored @Dependency(\.analytics) private var analytics

@State private var refreshController: RefreshController = DefaultRefreshController(
lastRefreshDate: .now + Self.staticLoadTime
)
@State private var refreshController = RefreshController(lastRefreshDate: Date.now + Self.staticLoadTime)
@State private var isVisible = false
@State private var relaySubscriptions = [SubscriptionCancellable]()
@State private var isShowingRelayList = false
Expand Down Expand Up @@ -70,12 +68,12 @@ struct HomeFeedView: View {
var body: some View {
ZStack {
PagedNoteListView(
refreshController: $refreshController,
databaseFilter: homeFeedFetchRequest,
relayFilter: homeFeedFilter,
relay: selectedRelay,
managedObjectContext: viewContext,
tab: .home,
refreshController: refreshController,
header: {
EmptyView()
},
Expand All @@ -85,15 +83,12 @@ struct HomeFeedView: View {
.padding()
}
.frame(minHeight: 300)
},
onRefresh: {
refreshController.setLastRefreshDate(.now)
}
)
.padding(0)

NewNotesButton(fetchRequest: FetchRequest(fetchRequest: newNotesRequest)) {
refreshController.setShouldRefresh(true)
refreshController.startRefresh = true
}

if showTimedLoadingIndicator {
Expand All @@ -112,7 +107,7 @@ struct HomeFeedView: View {
)
.onChange(of: selectedRelay) { _, _ in
showTimedLoadingIndicator = true
refreshController.setLastRefreshDate(.now + Self.staticLoadTime)
refreshController.lastRefreshDate = .now + Self.staticLoadTime
Task {
withAnimation {
showRelayPicker = false
Expand Down
Loading

0 comments on commit f82ddd0

Please sign in to comment.