Timeline: Basic timeline sync using the marker API
This commit is contained in:
parent
590299d102
commit
962c7c0295
|
@ -125,6 +125,16 @@ struct TimelineTab: View {
|
||||||
} label: {
|
} label: {
|
||||||
Label(TimelineFilter.latest.localizedTitle(), systemImage: TimelineFilter.latest.iconName() ?? "")
|
Label(TimelineFilter.latest.localizedTitle(), systemImage: TimelineFilter.latest.iconName() ?? "")
|
||||||
}
|
}
|
||||||
|
if timeline == .home {
|
||||||
|
Button {
|
||||||
|
timeline = .resume
|
||||||
|
} label: {
|
||||||
|
VStack {
|
||||||
|
Label(TimelineFilter.resume.localizedTitle(),
|
||||||
|
systemImage: TimelineFilter.resume.iconName() ?? "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
ForEach(TimelineFilter.availableTimeline(client: client), id: \.self) { timeline in
|
ForEach(TimelineFilter.availableTimeline(client: client), id: \.self) { timeline in
|
||||||
|
|
|
@ -10983,7 +10983,7 @@
|
||||||
},
|
},
|
||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "Konto hinzufügen"
|
"value" : "Konto hinzufügen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -11013,13 +11013,13 @@
|
||||||
},
|
},
|
||||||
"fr" : {
|
"fr" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "Ajouter un compte"
|
"value" : "Ajouter un compte"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"it" : {
|
"it" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "Aggiungi account"
|
"value" : "Aggiungi account"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -11073,13 +11073,13 @@
|
||||||
},
|
},
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "添加账户"
|
"value" : "添加账户"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-Hant" : {
|
"zh-Hant" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "新增帳戶"
|
"value" : "新增帳戶"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38368,7 +38368,7 @@
|
||||||
},
|
},
|
||||||
"de" : {
|
"de" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "Konto hinzufügen"
|
"value" : "Konto hinzufügen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -38398,13 +38398,13 @@
|
||||||
},
|
},
|
||||||
"fr" : {
|
"fr" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "Ajouter un compte"
|
"value" : "Ajouter un compte"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"it" : {
|
"it" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "Aggiungi un account"
|
"value" : "Aggiungi un account"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -38458,13 +38458,13 @@
|
||||||
},
|
},
|
||||||
"zh-Hans" : {
|
"zh-Hans" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "添加账户"
|
"value" : "添加账户"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zh-Hant" : {
|
"zh-Hant" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "新增帳戶"
|
"value" : "新增帳戶"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44190,8 +44190,8 @@
|
||||||
},
|
},
|
||||||
"fr" : {
|
"fr" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "needs_review",
|
"state" : "translated",
|
||||||
"value" : "Show Account on Hover"
|
"value" : "Afficher le popover du compte"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"it" : {
|
"it" : {
|
||||||
|
@ -72324,6 +72324,125 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"timeline.resume" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"be" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resime"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ca" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"de" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"en-GB" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"es" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eu" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Reprendre"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"it" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ja" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ko" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nb" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pl" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pt-BR" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tr" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uk" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hans" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zh-Hant" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "needs_review",
|
||||||
|
"value" : "Resume"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"timeline.trending" : {
|
"timeline.trending" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
|
@ -11,12 +11,13 @@ public enum Markers: Endpoint {
|
||||||
|
|
||||||
public func queryItems() -> [URLQueryItem]? {
|
public func queryItems() -> [URLQueryItem]? {
|
||||||
switch self {
|
switch self {
|
||||||
|
case .markers:
|
||||||
|
[URLQueryItem(name: "timeline[]", value: "home"),
|
||||||
|
URLQueryItem(name: "timeline[]", value: "notifications")]
|
||||||
case let .markNotifications(lastReadId):
|
case let .markNotifications(lastReadId):
|
||||||
[URLQueryItem(name: "notifications[last_read_id]", value: lastReadId)]
|
[URLQueryItem(name: "notifications[last_read_id]", value: lastReadId)]
|
||||||
case let .markHome(lastReadId):
|
case let .markHome(lastReadId):
|
||||||
[URLQueryItem(name: "home[last_read_id]", value: lastReadId)]
|
[URLQueryItem(name: "home[last_read_id]", value: lastReadId)]
|
||||||
default:
|
|
||||||
nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ public enum TimelineFilter: Hashable, Equatable {
|
||||||
case list(list: Models.List)
|
case list(list: Models.List)
|
||||||
case remoteLocal(server: String, filter: RemoteTimelineFilter)
|
case remoteLocal(server: String, filter: RemoteTimelineFilter)
|
||||||
case latest
|
case latest
|
||||||
|
case resume
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
hasher.combine(title)
|
hasher.combine(title)
|
||||||
|
@ -63,6 +64,8 @@ public enum TimelineFilter: Hashable, Equatable {
|
||||||
switch self {
|
switch self {
|
||||||
case .latest:
|
case .latest:
|
||||||
"Latest"
|
"Latest"
|
||||||
|
case .resume:
|
||||||
|
"Resume"
|
||||||
case .federated:
|
case .federated:
|
||||||
"Federated"
|
"Federated"
|
||||||
case .local:
|
case .local:
|
||||||
|
@ -86,6 +89,8 @@ public enum TimelineFilter: Hashable, Equatable {
|
||||||
switch self {
|
switch self {
|
||||||
case .latest:
|
case .latest:
|
||||||
"timeline.latest"
|
"timeline.latest"
|
||||||
|
case .resume:
|
||||||
|
"timeline.resume"
|
||||||
case .federated:
|
case .federated:
|
||||||
"timeline.federated"
|
"timeline.federated"
|
||||||
case .local:
|
case .local:
|
||||||
|
@ -109,6 +114,8 @@ public enum TimelineFilter: Hashable, Equatable {
|
||||||
switch self {
|
switch self {
|
||||||
case .latest:
|
case .latest:
|
||||||
"arrow.counterclockwise"
|
"arrow.counterclockwise"
|
||||||
|
case .resume:
|
||||||
|
"clock.arrow.2.circlepath"
|
||||||
case .federated:
|
case .federated:
|
||||||
"globe.americas"
|
"globe.americas"
|
||||||
case .local:
|
case .local:
|
||||||
|
@ -140,6 +147,7 @@ public enum TimelineFilter: Hashable, Equatable {
|
||||||
return Trends.statuses(offset: offset)
|
return Trends.statuses(offset: offset)
|
||||||
}
|
}
|
||||||
case .latest: return Timelines.home(sinceId: nil, maxId: nil, minId: nil)
|
case .latest: return Timelines.home(sinceId: nil, maxId: nil, minId: nil)
|
||||||
|
case .resume: return Timelines.home(sinceId: nil, maxId: nil, minId: nil)
|
||||||
case .home: return Timelines.home(sinceId: sinceId, maxId: maxId, minId: minId)
|
case .home: return Timelines.home(sinceId: sinceId, maxId: maxId, minId: minId)
|
||||||
case .trending: return Trends.statuses(offset: offset)
|
case .trending: return Trends.statuses(offset: offset)
|
||||||
case let .list(list): return Timelines.list(listId: list.id, sinceId: sinceId, maxId: maxId, minId: minId)
|
case let .list(list): return Timelines.list(listId: list.id, sinceId: sinceId, maxId: maxId, minId: minId)
|
||||||
|
@ -172,6 +180,7 @@ extension TimelineFilter: Codable {
|
||||||
case list
|
case list
|
||||||
case remoteLocal
|
case remoteLocal
|
||||||
case latest
|
case latest
|
||||||
|
case resume
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
|
@ -255,6 +264,8 @@ extension TimelineFilter: Codable {
|
||||||
try nestedContainer.encode(filter)
|
try nestedContainer.encode(filter)
|
||||||
case .latest:
|
case .latest:
|
||||||
try container.encode(CodingKeys.latest.rawValue, forKey: .latest)
|
try container.encode(CodingKeys.latest.rawValue, forKey: .latest)
|
||||||
|
case .resume:
|
||||||
|
try container.encode(CodingKeys.resume.rawValue, forKey: .latest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,8 @@ public struct TimelineView: View {
|
||||||
|
|
||||||
public init(timeline: Binding<TimelineFilter>,
|
public init(timeline: Binding<TimelineFilter>,
|
||||||
selectedTagGroup: Binding<TagGroup?>,
|
selectedTagGroup: Binding<TagGroup?>,
|
||||||
scrollToTopSignal: Binding<Int>, canFilterTimeline: Bool)
|
scrollToTopSignal: Binding<Int>,
|
||||||
|
canFilterTimeline: Bool)
|
||||||
{
|
{
|
||||||
_timeline = timeline
|
_timeline = timeline
|
||||||
_selectedTagGroup = selectedTagGroup
|
_selectedTagGroup = selectedTagGroup
|
||||||
|
@ -111,6 +112,7 @@ public struct TimelineView: View {
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
viewModel.isTimelineVisible = false
|
viewModel.isTimelineVisible = false
|
||||||
|
viewModel.saveMarker()
|
||||||
}
|
}
|
||||||
.refreshable {
|
.refreshable {
|
||||||
SoundEffectManager.shared.playSound(.pull)
|
SoundEffectManager.shared.playSound(.pull)
|
||||||
|
@ -145,7 +147,8 @@ public struct TimelineView: View {
|
||||||
}
|
}
|
||||||
case .background:
|
case .background:
|
||||||
wasBackgrounded = true
|
wasBackgrounded = true
|
||||||
|
viewModel.saveMarker()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -243,6 +246,9 @@ public struct TimelineView: View {
|
||||||
Text(timeline.localizedTitle())
|
Text(timeline.localizedTitle())
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
|
case .home:
|
||||||
|
Text(timeline.localizedTitle())
|
||||||
|
.font(.headline)
|
||||||
default:
|
default:
|
||||||
Text(timeline.localizedTitle())
|
Text(timeline.localizedTitle())
|
||||||
.font(.headline)
|
.font(.headline)
|
||||||
|
|
|
@ -10,23 +10,34 @@ import SwiftUI
|
||||||
var scrollToIndex: Int?
|
var scrollToIndex: Int?
|
||||||
var statusesState: StatusesState = .loading
|
var statusesState: StatusesState = .loading
|
||||||
var timeline: TimelineFilter = .federated {
|
var timeline: TimelineFilter = .federated {
|
||||||
|
willSet {
|
||||||
|
if timeline == .home && newValue != .resume {
|
||||||
|
saveMarker()
|
||||||
|
}
|
||||||
|
}
|
||||||
didSet {
|
didSet {
|
||||||
timelineTask?.cancel()
|
timelineTask?.cancel()
|
||||||
timelineTask = Task {
|
timelineTask = Task {
|
||||||
if timeline == .latest {
|
if timeline == .latest || timeline == .resume {
|
||||||
if oldValue == .home {
|
if oldValue == .home {
|
||||||
await clearHomeCache()
|
await clearHomeCache()
|
||||||
}
|
}
|
||||||
|
if timeline == .resume, let marker = await fetchMarker() {
|
||||||
|
self.marker = marker
|
||||||
|
}
|
||||||
timeline = oldValue
|
timeline = oldValue
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldValue != timeline {
|
if oldValue != timeline {
|
||||||
await reset()
|
await reset()
|
||||||
pendingStatusesObserver.pendingStatuses = []
|
pendingStatusesObserver.pendingStatuses = []
|
||||||
tag = nil
|
tag = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
guard !Task.isCancelled else {
|
guard !Task.isCancelled else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await fetchNewestStatuses()
|
await fetchNewestStatuses()
|
||||||
switch timeline {
|
switch timeline {
|
||||||
case let .hashtag(tag, _):
|
case let .hashtag(tag, _):
|
||||||
|
@ -77,7 +88,8 @@ import SwiftUI
|
||||||
var isTimelineVisible: Bool = false
|
var isTimelineVisible: Bool = false
|
||||||
let pendingStatusesObserver: PendingStatusesObserver = .init()
|
let pendingStatusesObserver: PendingStatusesObserver = .init()
|
||||||
var scrollToIndexAnimated: Bool = false
|
var scrollToIndexAnimated: Bool = false
|
||||||
|
var marker: Marker.Content?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
pendingStatusesObserver.scrollToIndex = { [weak self] index in
|
pendingStatusesObserver.scrollToIndex = { [weak self] index in
|
||||||
self?.scrollToIndexAnimated = true
|
self?.scrollToIndexAnimated = true
|
||||||
|
@ -174,19 +186,42 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
await fetchNewestStatuses()
|
await fetchNewestStatuses()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchStatuses(from: Marker.Content) async throws {
|
||||||
|
guard let client else { return }
|
||||||
|
statusesState = .loading
|
||||||
|
var statuses: [Status] = try await client.get(endpoint: timeline.endpoint(sinceId: nil,
|
||||||
|
maxId: from.lastReadId,
|
||||||
|
minId: nil,
|
||||||
|
offset: 0))
|
||||||
|
|
||||||
|
ReblogCache.shared.removeDuplicateReblogs(&statuses)
|
||||||
|
StatusDataControllerProvider.shared.updateDataControllers(for: statuses, client: client)
|
||||||
|
|
||||||
|
await datasource.set(statuses)
|
||||||
|
await cacheHome()
|
||||||
|
marker = nil
|
||||||
|
|
||||||
|
withAnimation {
|
||||||
|
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetchNewestStatuses()
|
||||||
|
}
|
||||||
|
|
||||||
func fetchNewestStatuses() async {
|
func fetchNewestStatuses() async {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
do {
|
do {
|
||||||
if await datasource.isEmpty {
|
if let marker {
|
||||||
|
try await fetchStatuses(from: marker)
|
||||||
|
} else if await datasource.isEmpty {
|
||||||
try await fetchFirstPage(client: client)
|
try await fetchFirstPage(client: client)
|
||||||
} else if let latest = await datasource.get().first, timeline.supportNewestPagination {
|
} else if let latest = await datasource.get().first, timeline.supportNewestPagination {
|
||||||
try await fetchNewPagesFrom(latestStatus: latest, client: client)
|
try await fetchNewPagesFrom(latestStatus: latest.id, client: client)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
statusesState = .error(error: error)
|
statusesState = .error(error: error)
|
||||||
canStreamEvents = true
|
canStreamEvents = true
|
||||||
print("timeline parse error: \(error)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,10 +276,10 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch pages from the top most status of the tomeline.
|
// Fetch pages from the top most status of the tomeline.
|
||||||
private func fetchNewPagesFrom(latestStatus: Status, client: Client) async throws {
|
private func fetchNewPagesFrom(latestStatus: String, client: Client) async throws {
|
||||||
canStreamEvents = false
|
canStreamEvents = false
|
||||||
let initialTimeline = timeline
|
let initialTimeline = timeline
|
||||||
var newStatuses: [Status] = await fetchNewPages(minId: latestStatus.id, maxPages: 10)
|
var newStatuses: [Status] = await fetchNewPages(minId: latestStatus, maxPages: 10)
|
||||||
|
|
||||||
// Dedup statuses, a status with the same id could have been streamed in.
|
// Dedup statuses, a status with the same id could have been streamed in.
|
||||||
let ids = await datasource.get().map(\.id)
|
let ids = await datasource.get().map(\.id)
|
||||||
|
@ -321,7 +356,7 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
!Task.isCancelled,
|
!Task.isCancelled,
|
||||||
let latest = await datasource.get().first
|
let latest = await datasource.get().first
|
||||||
{
|
{
|
||||||
try await fetchNewPagesFrom(latestStatus: latest, client: client)
|
try await fetchNewPagesFrom(latestStatus: latest.id, client: client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,3 +427,28 @@ extension TimelineViewModel: StatusesFetcher {
|
||||||
visibileStatusesIds.remove(status.id)
|
visibileStatusesIds.remove(status.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - MARKER
|
||||||
|
extension TimelineViewModel {
|
||||||
|
func fetchMarker() async -> Marker.Content? {
|
||||||
|
guard let client else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
let data: Marker = try await client.get(endpoint: Markers.markers)
|
||||||
|
return data.home
|
||||||
|
} catch {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveMarker() {
|
||||||
|
guard timeline == .home, let client else { return }
|
||||||
|
Task {
|
||||||
|
guard let id = await cache.getLatestSeenStatus(for: client)?.first else { return }
|
||||||
|
do {
|
||||||
|
let _: Marker = try await client.post(endpoint: Markers.markHome(lastReadId: id))
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue