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 remembering which feed source is selected #1716

Merged
merged 1 commit into from
Dec 31, 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 @@ -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
Loading