View favorites

This commit is contained in:
Justin Mazzocchi 2020-11-30 19:07:38 -08:00
parent 238d83a9f7
commit 02cc1e3533
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
12 changed files with 51 additions and 22 deletions

View file

@ -44,7 +44,7 @@ extension TimelineRecord {
id = timeline.id
switch timeline {
case .home, .local, .federated:
case .home, .local, .federated, .favorites:
listId = nil
listTitle = nil
tag = nil

View file

@ -10,6 +10,7 @@ public enum Timeline: Hashable {
case list(List)
case tag(String)
case profile(accountId: Account.Id, profileCollection: ProfileCollection)
case favorites
}
public extension Timeline {
@ -18,7 +19,7 @@ public extension Timeline {
static let unauthenticatedDefaults: [Timeline] = [.local, .federated]
static let authenticatedDefaults: [Timeline] = [.home, .local, .federated]
var filterContext: Filter.Context {
var filterContext: Filter.Context? {
switch self {
case .home, .list:
return .home
@ -26,6 +27,8 @@ public extension Timeline {
return .public
case .profile:
return .account
default:
return nil
}
}
}
@ -45,6 +48,8 @@ extension Timeline: Identifiable {
return "tag-".appending(tag).lowercased()
case let .profile(accountId, profileCollection):
return "profile-\(accountId)-\(profileCollection)"
case .favorites:
return "favorites"
}
}
}

View file

@ -24,6 +24,8 @@ extension Timeline {
self = .tag(tag)
case (_, _, _, _, .some(let accountId), .some(let profileCollection)):
self = .profile(accountId: accountId, profileCollection: profileCollection)
case (Timeline.favorites.id, _, _, _, _, _):
self = .favorites
default:
return nil
}

View file

@ -25,6 +25,7 @@
"attachment.sensitive-content" = "Sensitive content";
"attachment.media-hidden" = "Media hidden";
"cancel" = "Cancel";
"favorites" = "Favorites";
"registration.review-terms-of-use-and-privacy-policy-%@" = "Please review %@'s Terms of Use and Privacy Policy to continue";
"registration.username" = "Username";
"registration.email" = "Email";

View file

@ -38,7 +38,9 @@ extension Array where Element == Filter {
// swiftlint:disable line_length
// Adapted from https://github.com/tootsuite/mastodon/blob/bf477cee9f31036ebf3d164ddec1cebef5375513/app/javascript/mastodon/selectors/index.js#L43
// swiftlint:enable line_length
public func regularExpression(context: Filter.Context) -> String? {
public func regularExpression(context: Filter.Context?) -> String? {
guard let context = context else { return nil }
let inContext = filter { $0.context.contains(context) }
guard !inContext.isEmpty else { return nil }

View file

@ -10,6 +10,7 @@ public enum StatusesEndpoint {
case timelinesHome
case timelinesList(id: List.Id)
case accountsStatuses(id: Account.Id, excludeReplies: Bool, onlyMedia: Bool, pinned: Bool)
case favourites
}
extension StatusesEndpoint: Endpoint {
@ -21,6 +22,8 @@ extension StatusesEndpoint: Endpoint {
return defaultContext + ["timelines"]
case .accountsStatuses:
return defaultContext + ["accounts"]
case .favourites:
return defaultContext
}
}
@ -36,6 +39,8 @@ extension StatusesEndpoint: Endpoint {
return ["list", id]
case let .accountsStatuses(id, _, _, _):
return [id, "statuses"]
case .favourites:
return ["favourites"]
}
}

View file

@ -39,6 +39,8 @@ extension Timeline {
excludeReplies: excludeReplies,
onlyMedia: onlyMedia,
pinned: false)
case .favorites:
return .favourites
}
}
}

View file

@ -15,27 +15,15 @@ public struct TimelineService {
private let timeline: Timeline
private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase
private let nextPageMaxIdSubject: CurrentValueSubject<String, Never>
private let nextPageMaxIdSubject = PassthroughSubject<String, Never>()
init(timeline: Timeline, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
self.timeline = timeline
self.mastodonAPIClient = mastodonAPIClient
self.contentDatabase = contentDatabase
let nextPageMaxIdSubject = CurrentValueSubject<String, Never>(String(Int.max))
self.nextPageMaxIdSubject = nextPageMaxIdSubject
sections = contentDatabase.timelinePublisher(timeline)
.handleEvents(receiveOutput: {
guard case let .status(status, _) = $0.last?.last,
status.id < nextPageMaxIdSubject.value
else { return }
nextPageMaxIdSubject.send(status.id)
})
.eraseToAnyPublisher()
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
nextPageMaxId = nextPageMaxIdSubject.dropFirst().eraseToAnyPublisher()
nextPageMaxId = nextPageMaxIdSubject.eraseToAnyPublisher()
if case let .tag(tag) = timeline {
title = Just("#".appending(tag)).eraseToAnyPublisher()
@ -58,9 +46,9 @@ extension TimelineService: CollectionService {
public func request(maxId: String?, minId: String?) -> AnyPublisher<Never, Error> {
mastodonAPIClient.pagedRequest(timeline.endpoint, maxId: maxId, minId: minId)
.handleEvents(receiveOutput: {
guard let maxId = $0.info.maxId, maxId < nextPageMaxIdSubject.value else { return }
if let maxId = $0.info.maxId {
nextPageMaxIdSubject.send(maxId)
}
})
.flatMap { contentDatabase.insert(statuses: $0.result, timeline: timeline) }
.eraseToAnyPublisher()

View file

@ -98,7 +98,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
.eraseToAnyPublisher()
self.hasRequestedUsingMarker = true
} else {
publisher = collectionService.request(maxId: maxId, minId: minId)
publisher = collectionService.request(maxId: realMaxId(maxId: maxId), minId: minId)
}
publisher
@ -294,6 +294,17 @@ private extension CollectionItemsViewModel {
viewModelCache = viewModelCache.filter { itemsSet.contains($0.key) }
}
func realMaxId(maxId: String?) -> String? {
guard let maxId = maxId else { return nil }
guard let markerTimeline = collectionService.markerTimeline,
identification.appPreferences.positionBehavior(markerTimeline: markerTimeline) == .rememberPosition,
let lastItemId = items.value.last?.last?.itemId
else { return maxId }
return min(maxId, lastItemId)
}
func idForScrollPositionMaintenance(newItems: [[CollectionItem]]) -> CollectionItem.Id? {
let flatItems = items.value.reduce([], +)
let flatNewItems = newItems.reduce([], +)

View file

@ -95,7 +95,7 @@ public extension NavigationViewModel {
switch timeline {
case .home, .list:
return identification.identity.handle
case .local, .federated, .tag, .profile:
case .local, .federated, .tag, .profile, .favorites:
return identification.identity.instance?.uri ?? ""
}
}
@ -140,6 +140,12 @@ public extension NavigationViewModel {
case notifications
case messages
}
func favoritesViewModel() -> CollectionViewModel {
CollectionItemsViewModel(
collectionService: identification.service.service(timeline: .favorites),
identification: identification)
}
}
extension NavigationViewModel.Tab: Identifiable {

View file

@ -56,6 +56,10 @@ struct SecondaryNavigationView: View {
NavigationLink(destination: ListsView(viewModel: .init(identification: viewModel.identification))) {
Label("secondary-navigation.lists", systemImage: "scroll")
}
NavigationLink(destination: TableView(viewModel: viewModel.favoritesViewModel())
.navigationTitle(Text("favorites"))) {
Label("favorites", systemImage: "star.fill")
}
}
Section {
NavigationLink(

View file

@ -159,6 +159,8 @@ private extension Timeline {
return "#" + tag
case .profile:
return ""
case .favorites:
return NSLocalizedString("favorites", comment: "")
}
}
@ -170,6 +172,7 @@ private extension Timeline {
case .list: return "scroll"
case .tag: return "number"
case .profile: return "person"
case .favorites: return "star.fill"
}
}
}