Skip to content

Commit

Permalink
Merge pull request #1716 from planetary-social/bdm/remember-feed
Browse files Browse the repository at this point in the history
added remembering which feed source is selected
  • Loading branch information
bryanmontz authored Dec 31, 2024
2 parents 1f2a210 + b90ea45 commit c2fe8cd
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 40 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add empty state for lists/relays drop-down.
- Added support for decrypting private tags in kind 30000 lists.
- Added pop-up tip for feed customization. [#101](https://github.com/verse-pbc/issues/issues/101)
- Added remembering which feed source is selected.

### Internal Changes
- Upgraded to Xcode 16. [#1570](https://github.com/planetary-social/nos/issues/1570)
Expand Down
88 changes: 70 additions & 18 deletions Nos/Controller/FeedController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Dependencies
import SwiftUI

/// The source to be used for a feed of notes.
enum FeedSource: Hashable, Equatable {
enum FeedSource: RawRepresentable, Hashable, Equatable {
case following
case relay(String, String?)
case list(String, String?)
Expand All @@ -31,6 +31,44 @@ enum FeedSource: Hashable, Equatable {
default: false
}
}

// Note: RawRepresentable conformance is required for use of @AppStorage for persistence.
var rawValue: String {
switch self {
case .following:
"following"
case .relay(let host, let description):
"relay:|\(host):|\(description ?? "")"
case .list(let name, let description):
"list:|\(name):|\(description ?? "")"
}
}

init?(rawValue: String) {
let components = rawValue.split(separator: ":|").map { String($0) }
guard let caseName = components.first else {
return nil
}

switch caseName {
case "following":
self = .following
case "relay":
guard components.count >= 2 else {
return nil
}
let description = components.count >= 3 ? components[2] : ""
self = .relay(components[1], description)
case "list":
guard components.count >= 2 else {
return nil
}
let description = components.count >= 3 ? components[2] : ""
self = .list(components[1], description)
default:
return nil
}
}
}

@Observable @MainActor final class FeedController {
Expand All @@ -42,24 +80,13 @@ enum FeedSource: Hashable, Equatable {

private(set) var selectedList: AuthorList?
private(set) var selectedRelay: Relay?
var selectedSource: FeedSource = .following {

@ObservationIgnored @AppStorage("selectedFeedSource") private var persistedSelectedSource = FeedSource.following

var selectedSource = FeedSource.following {
didSet {
switch selectedSource {
case .relay(let address, _):
if let relay = relays.first(where: { $0.host == address }) {
selectedRelay = relay
selectedList = nil
}
case .list(let title, _):
// TODO: Needs to use replaceableID instead of title
if let list = lists.first(where: { $0.title == title }) {
selectedList = list
selectedRelay = nil
}
default:
selectedList = nil
selectedRelay = nil
}
updateSelectedListOrRelay()
persistedSelectedSource = selectedSource
}
}

Expand All @@ -82,6 +109,12 @@ enum FeedSource: Hashable, Equatable {
init() {
observeLists()
observeRelays()

// The delay here is an unfortunate workaround. Without it, the feed always resumes to
// the default value of FeedSource.following.
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
self.selectedSource = self.persistedSelectedSource
}
}

private func observeLists() {
Expand Down Expand Up @@ -136,6 +169,25 @@ enum FeedSource: Hashable, Equatable {
.store(in: &cancellables)
}

private func updateSelectedListOrRelay() {
switch selectedSource {
case .relay(let address, _):
if let relay = relays.first(where: { $0.host == address }) {
selectedRelay = relay
selectedList = nil
}
case .list(let title, _):
// TODO: Needs to use replaceableID instead of title
if let list = lists.first(where: { $0.title == title }) {
selectedList = list
selectedRelay = nil
}
default:
selectedList = nil
selectedRelay = nil
}
}

private func updateEnabledSources() {
var enabledSources = [FeedSource]()
enabledSources.append(.following)
Expand Down
5 changes: 3 additions & 2 deletions Nos/Models/CoreData/Event+Fetching.swift
Original file line number Diff line number Diff line change
Expand Up @@ -392,11 +392,12 @@ extension Event {
@nonobjc public class func homeFeed(
for user: Author,
after: Date,
seenOn relay: Relay? = nil
seenOn relay: Relay? = nil,
from authors: Set<Author>? = nil
) -> NSFetchRequest<Event> {
let fetchRequest = NSFetchRequest<Event>(entityName: "Event")
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Event.createdAt, ascending: false)]
fetchRequest.predicate = homeFeedPredicate(for: user, after: after, seenOn: relay)
fetchRequest.predicate = homeFeedPredicate(for: user, after: after, seenOn: relay, from: authors)
return fetchRequest
}

Expand Down
45 changes: 26 additions & 19 deletions Nos/Views/Home/FeedPicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,35 @@ struct FeedPicker: View {

var body: some View {
BeveledContainerView {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
ForEach(feedController.enabledSources, id: \.self) { source in
Button(action: {
withAnimation(nil) {
feedController.selectedSource = source
}
}, label: {
let isSelected = feedController.selectedSource == source
Text(source.displayName)
.font(.system(size: 16, weight: isSelected ? .medium : .regular))
.padding(.horizontal, 10)
.padding(.vertical, 4)
.background(isSelected ? Color.pickerBackgroundSelected : Color.clear)
.foregroundStyle(isSelected ? Color.white : Color.secondaryTxt)
.clipShape(Capsule())
})
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
ForEach(feedController.enabledSources, id: \.self) { source in
Button(action: {
withAnimation(nil) {
feedController.selectedSource = source
}
}, label: {
let isSelected = feedController.selectedSource == source
Text(source.displayName)
.font(.system(size: 16, weight: isSelected ? .medium : .regular))
.padding(.horizontal, 10)
.padding(.vertical, 4)
.background(isSelected ? Color.pickerBackgroundSelected : Color.clear)
.foregroundStyle(isSelected ? Color.white : Color.secondaryTxt)
.clipShape(Capsule())
})
}
}
.padding(.horizontal, 8)
.onChange(of: feedController.selectedSource) {
withAnimation {
proxy.scrollTo(feedController.selectedSource)
}
}
}
.padding(.horizontal, 8)
.frame(height: 40)
}
.frame(height: 40)
}
.background(Color.cardBgTop)
}
Expand Down
3 changes: 2 additions & 1 deletion Nos/Views/Home/HomeFeedView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ struct HomeFeedView: View {
Event.homeFeed(
for: user,
after: refreshController.lastRefreshDate,
seenOn: feedController.selectedRelay
seenOn: feedController.selectedRelay,
from: feedController.selectedList?.allAuthors
)
}

Expand Down

0 comments on commit c2fe8cd

Please sign in to comment.