Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added feed source customizer drop-down view #102 #1711

Merged
merged 2 commits into from
Dec 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading