mirror of
https://github.com/metabolist/metatext.git
synced 2024-12-22 05:26:30 +00:00
View favorites
This commit is contained in:
parent
238d83a9f7
commit
02cc1e3533
12 changed files with 51 additions and 22 deletions
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ extension Timeline {
|
|||
excludeReplies: excludeReplies,
|
||||
onlyMedia: onlyMedia,
|
||||
pinned: false)
|
||||
case .favorites:
|
||||
return .favourites
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
|
||||
nextPageMaxIdSubject.send(maxId)
|
||||
if let maxId = $0.info.maxId {
|
||||
nextPageMaxIdSubject.send(maxId)
|
||||
}
|
||||
})
|
||||
.flatMap { contentDatabase.insert(statuses: $0.result, timeline: timeline) }
|
||||
.eraseToAnyPublisher()
|
||||
|
|
|
@ -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([], +)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue