Skip to content

Commit

Permalink
Merge pull request #1337 from planetary-social/relay-filter-on-home-f…
Browse files Browse the repository at this point in the history
…eed-again

Relay filter on home feed again
  • Loading branch information
mplorentz authored Jul 25, 2024
2 parents 1b7f9b6 + 715f012 commit 1a7aeab
Show file tree
Hide file tree
Showing 13 changed files with 363 additions and 134 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix a bug where multiple connections could be opened with the same relay.
- Fixed an issue where Profile views would sometimes not display any notes.
- Add impersonation flag category and better NIP-56 mapping.
- Added a filter button to the Home tab that lets you browse all notes on a specific relay.
- Add a Tap to Refresh button in empty profiles.
- Support nostr:naddr links to text and long-form content notes.
- Update the reply count shown below each note in a Feed.
Expand Down
47 changes: 35 additions & 12 deletions Nos/Assets/Localization/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,17 @@
}
}
},
"accountsIFollow" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Accounts I Follow"
}
}
}
},
"activity" : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -4316,6 +4327,18 @@
}
}
},
"filter" : {
"comment" : "verb for filtering your feed in various ways",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "filter"
}
}
}
},
"flagUser" : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -7151,73 +7174,73 @@
"localizations" : {
"ar" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "لا يوجد ملاحظات (بعد)! اطلع على علامة التصفح واتبع بعض الأشخاص للبدء."
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "(Noch) keine Notizen! Durchsuche das Tab \\\"Entdecken\\\" und folge einigen Leuten, um loszulegen."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "No notes (yet)! Browse the Discover tab and follow some people to get started."
"value" : "No notes (yet), but we'll keep looking!"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "¡No hay notas (aún)! Navega a la pestaña de Descubrir y sigue a algunas personas para empezar."
}
},
"fa" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "(هنوز) هیچ یادداشتی نیست! برای شروع سربرگ یافتن را بگردید و چند نفر را دنبال کنید."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Aucune note (pour l'instant) ! Allez sur l'onglet \\\"Découvrir\\\" et suivez des gens pour commencer."
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "メモはまだありません!ディスカバー / 検索 タブをタップして、何人かのユーザーをフォローして始めましょう。"
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Nog geen notes! Blader de Ontdek tab en volg enkele accounts om te beginnen."
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Sem notas (ainda)! Navegue na guia \"Descobrir\" e siga algumas pessoas para começar."
}
},
"sv" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Inga anteckningar (ännu)! Bläddra i fliken Upptäck och följ några personer för att komma igång."
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "(还)没有笔记!浏览发现选项卡并关注一些帐户以开始操作。"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "(還)沒有筆記!瀏覽發現選項卡並關注一些帳戶以開始操作。"
}
}
Expand Down
43 changes: 37 additions & 6 deletions Nos/Controller/PagedNoteDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ class PagedNoteDataSource<Header: View, EmptyPlaceholder: View>: NSObject, UICol
var collectionView: UICollectionView

@Dependency(\.relayService) private var relayService: RelayService
private(set) var databaseFilter: NSFetchRequest<Event>
private(set) var relayFilter: Filter
private(set) var relay: Relay?
private var pager: PagedRelaySubscription?
private var context: NSManagedObjectContext
private var header: () -> Header
Expand All @@ -26,12 +29,14 @@ class PagedNoteDataSource<Header: View, EmptyPlaceholder: View>: NSObject, UICol
init(
databaseFilter: NSFetchRequest<Event>,
relayFilter: Filter,
relay: Relay?,
collectionView: UICollectionView,
context: NSManagedObjectContext,
@ViewBuilder header: @escaping () -> Header,
@ViewBuilder emptyPlaceholder: @escaping (@escaping () -> Void) -> EmptyPlaceholder,
onRefresh: @escaping () -> NSFetchRequest<Event>
) {
self.databaseFilter = databaseFilter
self.fetchedResultsController = NSFetchedResultsController<Event>(
fetchRequest: databaseFilter,
managedObjectContext: context,
Expand All @@ -40,6 +45,8 @@ class PagedNoteDataSource<Header: View, EmptyPlaceholder: View>: NSObject, UICol
)
self.collectionView = collectionView
self.context = context
self.relayFilter = relayFilter
self.relay = relay
self.header = header
self.emptyPlaceholder = emptyPlaceholder
self.onRefresh = onRefresh
Expand Down Expand Up @@ -67,15 +74,23 @@ class PagedNoteDataSource<Header: View, EmptyPlaceholder: View>: NSObject, UICol
Log.error(error)
}

Task {
var limitedFilter = relayFilter
subscribeToEvents(matching: relayFilter, from: relay)
}

func subscribeToEvents(matching filter: Filter, from relay: Relay?) {
self.relayFilter = filter
self.relay = relay

Task {
var limitedFilter = filter
limitedFilter.limit = pageSize
self.pager = await relayService.subscribeToPagedEvents(matching: limitedFilter)
self.pager = await relayService.subscribeToPagedEvents(matching: limitedFilter, from: relay?.addressURL)
loadMoreIfNeeded(for: IndexPath(row: 0, section: 0))
}
}

func updateFetchRequest(_ fetchRequest: NSFetchRequest<Event>) {
self.databaseFilter = fetchRequest
self.fetchedResultsController = NSFetchedResultsController<Event>(
fetchRequest: fetchRequest,
managedObjectContext: context,
Expand All @@ -86,6 +101,7 @@ class PagedNoteDataSource<Header: View, EmptyPlaceholder: View>: NSObject, UICol
try? self.fetchedResultsController.performFetch()
loadMoreIfNeeded(for: IndexPath(row: 0, section: 0))
collectionView.reloadData()
collectionView.setContentOffset(.zero, animated: false)
}

// MARK: - UICollectionViewDataSource
Expand Down Expand Up @@ -208,7 +224,10 @@ class PagedNoteDataSource<Header: View, EmptyPlaceholder: View>: NSObject, UICol
startAggressivePaging()
return
} else if indexPath.row.isMultiple(of: pageSize / 2) {
Task { await pager?.loadMore() }
let displayedDate = displayedDate(for: indexPath.row)
Task {
await pager?.loadMore(displayingContentAt: displayedDate)
}
}
}

Expand All @@ -231,7 +250,8 @@ class PagedNoteDataSource<Header: View, EmptyPlaceholder: View>: NSObject, UICol
/// `loadMoreIfNeeded(for:)` won't be called which means we'll never ask for the next page. So we need the timer.
private func startAggressivePaging() {
if aggressivePagingTimer == nil {
aggressivePagingTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { [weak self] timer in

aggressivePagingTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
guard let self else {
timer.invalidate()
return
Expand All @@ -241,12 +261,18 @@ class PagedNoteDataSource<Header: View, EmptyPlaceholder: View>: NSObject, UICol

if self.largestLoadedRowIndex > lastPageStartIndex {
// we are still on the last page of results, keep loading
Task { await self.pager?.loadMore() }
let displayedDate = self.displayedDate(for: largestLoadedRowIndex)
Task {
await self.pager?.loadMore(displayingContentAt: displayedDate)
}
} else {
// we've loaded enough, go back to normal paging
self.stopAggressivePaging()
}
}

// Fire manually once because the timer doesn't fire immediately
aggressivePagingTimer?.fire()
}
}

Expand All @@ -258,6 +284,11 @@ class PagedNoteDataSource<Header: View, EmptyPlaceholder: View>: NSObject, UICol
}
}

/// Returns the `created_at` date of the event at the given index, if one exists.
private func displayedDate(for index: Int) -> Date? {
fetchedResultsController.fetchedObjects?[safe: index]?.createdAt
}

// MARK: - NSFetchedResultsControllerDelegate

private var insertedIndexes = [IndexPath]()
Expand Down
33 changes: 22 additions & 11 deletions Nos/Models/CoreData/Event+CoreDataClass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -325,21 +325,32 @@ public class Event: NosManagedObject, VerifiableEvent {
return fetchRequest
}

@nonobjc public class func homeFeedPredicate(for user: Author, before: Date) -> NSPredicate {
NSPredicate(
// swiftlint:disable line_length
format: "((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 (ANY author.followers.source = %@ OR author = %@) AND author.muted = 0 AND createdAt <= %@ AND deletedOn.@count = 0",
// swiftlint:enable line_length
user,
user,
before as CVarArg
)
@nonobjc public class func homeFeedPredicate(
for user: Author,
before: 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] = [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]
}
return NSPredicate(format: queryString, argumentArray: arguments)
}

@nonobjc public class func homeFeed(for user: Author, before: Date) -> NSFetchRequest<Event> {
@nonobjc public class func homeFeed(
for user: Author,
before: Date,
seenOn relay: Relay? = nil
) -> NSFetchRequest<Event> {
let fetchRequest = NSFetchRequest<Event>(entityName: "Event")
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Event.createdAt, ascending: false)]
fetchRequest.predicate = homeFeedPredicate(for: user, before: before)
fetchRequest.predicate = homeFeedPredicate(for: user, before: before, seenOn: relay)
return fetchRequest
}

Expand Down
2 changes: 1 addition & 1 deletion Nos/Service/Relay/Filter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Foundation
struct Filter: Hashable, Identifiable {

/// List of author identifiers the Filter should be constrained to.
let authorKeys: [RawAuthorID]
var authorKeys: [RawAuthorID]

/// List of event identifiers the Filter should be constrained to.
let eventIDs: [RawEventID]
Expand Down
Loading

0 comments on commit 1a7aeab

Please sign in to comment.