Skip to content

Commit

Permalink
Merge pull request #1711 from planetary-social/bdm/102-feeds-filter
Browse files Browse the repository at this point in the history
added feed source customizer drop-down view #102
  • Loading branch information
bryanmontz authored Dec 28, 2024
2 parents c1cd326 + 69479ff commit 136c777
Show file tree
Hide file tree
Showing 16 changed files with 637 additions and 68 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Nos now publishes the hashtags it finds in your note when you post. This means it works the way you’ve always expected it to work. [#44](https://github.com/verse-pbc/issues/issues/44)
- Fixed crash related to tracking delete events. [#96](https://github.com/verse-pbc/issues/issues/96)
- Added feed picker view (UI only). [#103](https://github.com/verse-pbc/issues/issues/103)
- Added feed source customizer drop-down view. [#102](https://github.com/verse-pbc/issues/issues/102)

### Internal Changes
- Upgraded to Xcode 16. [#1570](https://github.com/planetary-social/nos/issues/1570)
Expand Down
26 changes: 24 additions & 2 deletions Nos.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@
501728B42D16EFB000CF2A07 /* FeedPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501728B32D16EFAC00CF2A07 /* FeedPicker.swift */; };
502B6C3D2C9462A400446316 /* PushNotificationRegistrar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 502B6C3C2C9462A400446316 /* PushNotificationRegistrar.swift */; };
503CA9532D19ACCC00805EF8 /* HorizontalLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503CA9522D19ACC800805EF8 /* HorizontalLine.swift */; };
503CA9792D19C39F00805EF8 /* FeedCustomizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503CA9782D19C39800805EF8 /* FeedCustomizerView.swift */; };
503CAAF12D1AFF8900805EF8 /* BeveledContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503CAAF02D1AFF8400805EF8 /* BeveledContainerView.swift */; };
503CAB502D1D8FB300805EF8 /* FeedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503CAB4E2D1D8FAF00805EF8 /* FeedController.swift */; };
503CAB6E2D1DA17400805EF8 /* FeedToggleRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503CAB6D2D1DA17100805EF8 /* FeedToggleRow.swift */; };
503CAC612D1EF71B00805EF8 /* FeedSourceToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503CAC602D1EF71700805EF8 /* FeedSourceToggleView.swift */; };
5044546E2C90726A00251A7E /* Event+Fetching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044546D2C90726A00251A7E /* Event+Fetching.swift */; };
504454702C90728500251A7E /* Event+Hydration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044546F2C90728500251A7E /* Event+Hydration.swift */; };
504454712C90728E00251A7E /* Event+Fetching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5044546D2C90726A00251A7E /* Event+Fetching.swift */; };
Expand Down Expand Up @@ -616,8 +621,8 @@
030024182CC00DF70073ED56 /* SplashScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenView.swift; sourceTree = "<group>"; };
030036842C5D39DD002C71F5 /* RefreshController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshController.swift; sourceTree = "<group>"; };
030036AA2C5D872B002C71F5 /* NewNotesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewNotesButton.swift; sourceTree = "<group>"; };
0303B11E2D0257D400077929 /* Nos 21.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Nos 21.xcdatamodel"; sourceTree = "<group>"; };
0301495B2CFFA8B7000A0152 /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = "<group>"; };
0303B11E2D0257D400077929 /* Nos 21.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Nos 21.xcdatamodel"; sourceTree = "<group>"; };
0303B13E2D025BDD00077929 /* AuthorList+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AuthorList+CoreDataProperties.swift"; sourceTree = "<group>"; };
0304D0A62C9B4BF2001D16C7 /* OpenGraphMetatdata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGraphMetatdata.swift; sourceTree = "<group>"; };
0304D0B12C9B731F001D16C7 /* MockOpenGraphService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockOpenGraphService.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -760,6 +765,12 @@
501728B32D16EFAC00CF2A07 /* FeedPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedPicker.swift; sourceTree = "<group>"; };
502B6C3C2C9462A400446316 /* PushNotificationRegistrar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationRegistrar.swift; sourceTree = "<group>"; };
503CA9522D19ACC800805EF8 /* HorizontalLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalLine.swift; sourceTree = "<group>"; };
503CA9782D19C39800805EF8 /* FeedCustomizerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedCustomizerView.swift; sourceTree = "<group>"; };
503CAAF02D1AFF8400805EF8 /* BeveledContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeveledContainerView.swift; sourceTree = "<group>"; };
503CAB4E2D1D8FAF00805EF8 /* FeedController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedController.swift; sourceTree = "<group>"; };
503CAB6D2D1DA17100805EF8 /* FeedToggleRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedToggleRow.swift; sourceTree = "<group>"; };
503CAB7B2D1DA6DB00805EF8 /* Nos 22.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Nos 22.xcdatamodel"; sourceTree = "<group>"; };
503CAC602D1EF71700805EF8 /* FeedSourceToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedSourceToggleView.swift; sourceTree = "<group>"; };
5044546D2C90726A00251A7E /* Event+Fetching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Event+Fetching.swift"; sourceTree = "<group>"; };
5044546F2C90728500251A7E /* Event+Hydration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Event+Hydration.swift"; sourceTree = "<group>"; };
5045540C2C81E10C0044ECAE /* EditableAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableAvatarView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1596,6 +1607,7 @@
C98DC9BA2A795CAD004E5F0F /* ActionBanner.swift */,
C9A0DAE929C6A34200466635 /* ActivityView.swift */,
3FFB1D88299FF37C002A755D /* AvatarView.swift */,
503CAAF02D1AFF8400805EF8 /* BeveledContainerView.swift */,
C95D68A0299E6D3E00429F86 /* BioView.swift */,
C9DFA968299BEC33006929C1 /* CardStyle.swift */,
0496D6302C975E6900D29375 /* FlagOptionPicker.swift */,
Expand Down Expand Up @@ -1700,7 +1712,10 @@
C96877B32B4EDCCF0051ED2F /* Home */ = {
isa = PBXGroup;
children = (
503CA9782D19C39800805EF8 /* FeedCustomizerView.swift */,
501728B32D16EFAC00CF2A07 /* FeedPicker.swift */,
503CAC602D1EF71700805EF8 /* FeedSourceToggleView.swift */,
503CAB6D2D1DA17100805EF8 /* FeedToggleRow.swift */,
C9DEBFD8298941000078B43A /* HomeFeedView.swift */,
5BE281C92AE2CCEB00880466 /* HomeTab.swift */,
03C7E7912CB9C0AF0054624C /* WelcomeToFeedTip.swift */,
Expand Down Expand Up @@ -2002,6 +2017,7 @@
isa = PBXGroup;
children = (
0357299A2BE415E5005FEE85 /* ContentWarningController.swift */,
503CAB4E2D1D8FAF00805EF8 /* FeedController.swift */,
C913DA0B2AEB2EBF003BDD6D /* FetchRequestPublisher.swift */,
C993148C2C5BD8FC00224BA6 /* NoteEditorController.swift */,
C913DA092AEAF52B003BDD6D /* NoteWarningController.swift */,
Expand Down Expand Up @@ -2381,9 +2397,11 @@
C9E8C1152B081EBE002D46B0 /* NIP05View.swift in Sources */,
50E2EB722C86175900D4B360 /* NSRegularExpression+Replacement.swift in Sources */,
C92E7F6A2C4EFF7200B80638 /* WebSocketConnection.swift in Sources */,
503CAB6E2D1DA17400805EF8 /* FeedToggleRow.swift in Sources */,
5BC0D9CC2B867B9D005D6980 /* NamesAPI.swift in Sources */,
C987F81D29BA6D9A00B44E7A /* ProfileTab.swift in Sources */,
C9ADB14129951CB10075E7F8 /* NSManagedObject+Nos.swift in Sources */,
503CA9792D19C39F00805EF8 /* FeedCustomizerView.swift in Sources */,
C9F84C21298DC36800C6714D /* AppView.swift in Sources */,
C9CE5B142A0172CF008E198C /* WebView.swift in Sources */,
CD4908D429B92941007443DB /* ReportABugMailView.swift in Sources */,
Expand All @@ -2395,6 +2413,7 @@
A34E439929A522F20057AFCB /* CurrentUser.swift in Sources */,
045EDCF32CAAF47600B67964 /* FlagSuccessView.swift in Sources */,
03E1812F2C753C9B00886CC6 /* ImageButton.swift in Sources */,
503CAC612D1EF71B00805EF8 /* FeedSourceToggleView.swift in Sources */,
C9A0DADD29C689C900466635 /* NosNavigationBar.swift in Sources */,
3F30020529C1FDD9003D4F8B /* OnboardingStartView.swift in Sources */,
C936B4592A4C7B7C00DF1EB9 /* Nos.xcdatamodeld in Sources */,
Expand Down Expand Up @@ -2554,6 +2573,7 @@
C97465312A3B89140031226F /* AuthorLabel.swift in Sources */,
C9C547592A4F1D8C006B0741 /* NosNotification+CoreDataClass.swift in Sources */,
030AE4292BE3D63C004DEE02 /* FeaturedAuthor.swift in Sources */,
503CAAF12D1AFF8900805EF8 /* BeveledContainerView.swift in Sources */,
C9B678E729F01A8500303F33 /* FullscreenProgressView.swift in Sources */,
C9F0BB6929A5039D000547FC /* Int+Bool.swift in Sources */,
03E181472C754BA300886CC6 /* LinkView.swift in Sources */,
Expand Down Expand Up @@ -2583,6 +2603,7 @@
C9BAB09B2996FBA10003A84E /* EventProcessor.swift in Sources */,
C9B5C78E2C24AF650070445B /* MockRelaySubscriptionManager.swift in Sources */,
C960C57129F3236200929990 /* LikeButton.swift in Sources */,
503CAB502D1D8FB300805EF8 /* FeedController.swift in Sources */,
C97797B9298AA19A0046BD25 /* RelayService.swift in Sources */,
04368D2B2C99A2C400DEAA2E /* FlagOption.swift in Sources */,
C99721CB2AEBED26004EBEAB /* String+Empty.swift in Sources */,
Expand Down Expand Up @@ -3896,6 +3917,7 @@
C936B4572A4C7B7C00DF1EB9 /* Nos.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
503CAB7B2D1DA6DB00805EF8 /* Nos 22.xcdatamodel */,
0303B11E2D0257D400077929 /* Nos 21.xcdatamodel */,
2D3C71A52CEE6F7100625BCB /* Nos 20.xcdatamodel */,
C95057C62CC69FD70024EC9C /* Nos 19.xcdatamodel */,
Expand All @@ -3910,7 +3932,7 @@
C9C547562A4F1D1A006B0741 /* Nos 9.xcdatamodel */,
5BFF66AF2A4B55FC00AA79DD /* Nos 10.xcdatamodel */,
);
currentVersion = 0303B11E2D0257D400077929 /* Nos 21.xcdatamodel */;
currentVersion = 503CAB7B2D1DA6DB00805EF8 /* Nos 22.xcdatamodel */;
path = Nos.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
Expand Down
176 changes: 176 additions & 0 deletions Nos/Controller/FeedController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import Combine
import CoreData
import Dependencies
import SwiftUI

/// The source to be used for a feed of notes.
enum FeedSource: Hashable, Equatable {
case following
case relay(String, String?)
case list(String, String?)

var displayName: String {
switch self {
case .following: String(localized: "following")
case .relay(let name, _), .list(let name, _): name
}
}

var description: String? {
switch self {
case .following: nil
case .relay(_, let description), .list(_, let description): description
}
}

static func == (lhs: FeedSource, rhs: FeedSource) -> Bool {
switch (lhs, rhs) {
case (.following, .following): true
case (.relay(let name1, _), .relay(let name2, _)): name1 == name2
case (.list(let name1, _), .list(let name2, _)): name1 == name2
default: false
}
}
}

@Observable @MainActor final class FeedController {

@ObservationIgnored @Dependency(\.persistenceController) private var persistenceController
@ObservationIgnored @Dependency(\.currentUser) private var currentUser

var enabledSources: [FeedSource] = [.following]
var selectedSource: FeedSource = .following

private(set) var listRowItems: [FeedToggleRow.Item] = []
private(set) var relayRowItems: [FeedToggleRow.Item] = []

private var lists: [AuthorList] = [] {
didSet {
updateEnabledSources()
}
}
private var relays: [Relay] = [] {
didSet {
updateEnabledSources()
}
}

private var cancellables = Set<AnyCancellable>()

init() {
observeLists()
observeRelays()
}

private func observeLists() {
guard let author = currentUser.author else {
return
}

let request = NSFetchRequest<AuthorList>(entityName: "AuthorList")
request.sortDescriptors = [NSSortDescriptor(keyPath: \Event.createdAt, ascending: false)]
request.predicate = NSPredicate(
format: "kind = %i AND author = %@ AND title != nil",
EventKind.followSet.rawValue,
author
)

let listWatcher = NSFetchedResultsController(
fetchRequest: request,
managedObjectContext: persistenceController.viewContext,
sectionNameKeyPath: nil,
cacheName: "FeedController.listWatcher"
)

FetchedResultsControllerPublisher(fetchedResultsController: listWatcher)
.publisher
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] lists in
self?.lists = lists
})
.store(in: &cancellables)
}

private func observeRelays() {
guard let author = currentUser.author else {
return
}

let request = Relay.relays(for: author)

let relayWatcher = NSFetchedResultsController(
fetchRequest: request,
managedObjectContext: persistenceController.viewContext,
sectionNameKeyPath: nil,
cacheName: "FeedController.relayWatcher"
)

FetchedResultsControllerPublisher(fetchedResultsController: relayWatcher)
.publisher
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] relays in
self?.relays = relays
})
.store(in: &cancellables)
}

private func updateEnabledSources() {
var enabledSources = [FeedSource]()
enabledSources.append(.following)

var listItems = [FeedToggleRow.Item]()
var relayItems = [FeedToggleRow.Item]()

for list in lists {
let source = FeedSource.list(list.title ?? "??", nil)

if list.isFeedEnabled {
enabledSources.append(source)
}

listItems.append(FeedToggleRow.Item(source: source, isOn: list.isFeedEnabled))
}

for relay in relays {
let source = FeedSource.relay(relay.host ?? "", relay.relayDescription)

if relay.isFeedEnabled {
enabledSources.append(source)
}

relayItems.append(FeedToggleRow.Item(source: source, isOn: relay.isFeedEnabled))
}

self.enabledSources = enabledSources
self.listRowItems = listItems
self.relayRowItems = relayItems
}

func toggleSourceEnabled(_ source: FeedSource) {
do {
switch source {
case .relay(let address, _):
if let relay = relays.first(where: { $0.host == address }) {
relay.isFeedEnabled.toggle()
try relay.managedObjectContext?.save()
updateEnabledSources()
}
case .list(let title, _):
// TODO: Needs to use replaceableID instead of title
if let list = lists.first(where: { $0.title == title }) {
list.isFeedEnabled.toggle()
try list.managedObjectContext?.save()
updateEnabledSources()
}
default:
break
}
} catch {
print("FeedController: error updating source: \(source), error: \(error)")
}
}

func isSourceEnabled(_ source: FeedSource) -> Bool {
enabledSources.contains(source)
}
}
2 changes: 1 addition & 1 deletion Nos/Controller/FetchRequestPublisher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Combine
import CoreData

/// Create by passing in a FetchedResultsController
/// This will perform the fetch request on the correct queue and publish the resutls on the
/// This will perform the fetch request on the correct queue and publish the results on the
/// publishers.
/// source: https://gist.github.com/josephlord/0d6a9d0871bd2e1b3a3bdbf20c184f88
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ extension AuthorList {

/// The set of unique authors in this list.
@NSManaged public var authors: Set<Author>

/// Whether or not this list should be visible in the ``FeedPicker``.
@NSManaged public var isFeedEnabled: Bool
}

// MARK: Generated accessors for authors
Expand Down
2 changes: 2 additions & 0 deletions Nos/Models/CoreData/Generated/Relay+CoreDataProperties.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ extension Relay {
@NSManaged public var events: Set<Event>
@NSManaged public var publishedEvents: Set<Event>
@NSManaged public var shouldBePublishedEvents: Set<Event>
/// Whether or not this relay should be visible in the ``FeedPicker``.
@NSManaged public var isFeedEnabled: Bool

// Metadata
@NSManaged public var name: String?
Expand Down
2 changes: 1 addition & 1 deletion Nos/Models/CoreData/Nos.xcdatamodeld/.xccurrentversion
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Nos 21.xcdatamodel</string>
<string>Nos 22.xcdatamodel</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Nos.xcdatamodel</string>
</dict>
</plist>
Loading

0 comments on commit 136c777

Please sign in to comment.