2023-01-17 10:36:01 +00:00
|
|
|
import Env
|
2022-11-29 08:28:17 +00:00
|
|
|
import Models
|
2023-01-17 10:36:01 +00:00
|
|
|
import Network
|
2022-12-19 06:17:01 +00:00
|
|
|
import Status
|
2023-01-17 10:36:01 +00:00
|
|
|
import SwiftUI
|
2022-11-25 11:00:01 +00:00
|
|
|
|
|
|
|
@MainActor
|
2022-12-19 06:17:01 +00:00
|
|
|
class TimelineViewModel: ObservableObject, StatusesFetcher {
|
2022-12-30 07:36:22 +00:00
|
|
|
var client: Client? {
|
|
|
|
didSet {
|
|
|
|
if oldValue != client {
|
|
|
|
statuses = []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-26 06:36:54 +00:00
|
|
|
// Internal source of truth for a timeline.
|
2022-11-25 11:00:01 +00:00
|
|
|
private var statuses: [Status] = []
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-19 06:17:01 +00:00
|
|
|
@Published var statusesState: StatusesState = .loading
|
2023-01-06 11:14:05 +00:00
|
|
|
@Published var timeline: TimelineFilter = .federated {
|
2022-12-01 08:05:26 +00:00
|
|
|
didSet {
|
2022-12-25 07:17:16 +00:00
|
|
|
Task {
|
2022-12-25 11:46:42 +00:00
|
|
|
if oldValue != timeline {
|
|
|
|
statuses = []
|
2022-12-26 07:24:55 +00:00
|
|
|
pendingStatuses = []
|
2023-01-04 17:37:58 +00:00
|
|
|
tag = nil
|
2022-12-25 11:46:42 +00:00
|
|
|
}
|
2022-12-26 06:36:54 +00:00
|
|
|
await fetchStatuses(userIntent: false)
|
2022-12-25 07:17:16 +00:00
|
|
|
switch timeline {
|
|
|
|
case let .hashtag(tag, _):
|
|
|
|
await fetchTag(id: tag)
|
|
|
|
default:
|
|
|
|
break
|
2022-12-01 08:05:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-21 11:39:29 +00:00
|
|
|
@Published var tag: Tag?
|
2022-12-25 17:43:15 +00:00
|
|
|
@Published var pendingStatuses: [Status] = []
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2023-01-19 17:14:08 +00:00
|
|
|
var pendingStatusesButtonTitle: LocalizedStringKey {
|
2023-01-30 20:41:42 +00:00
|
|
|
"\(pendingStatuses.count)"
|
2022-12-26 06:36:54 +00:00
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-26 06:36:54 +00:00
|
|
|
var pendingStatusesEnabled: Bool {
|
|
|
|
timeline == .home
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-11-25 11:00:01 +00:00
|
|
|
var serverName: String {
|
2022-12-19 11:28:55 +00:00
|
|
|
client?.server ?? "Error"
|
2022-11-25 11:00:01 +00:00
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-19 06:17:01 +00:00
|
|
|
func fetchStatuses() async {
|
2022-12-26 06:36:54 +00:00
|
|
|
await fetchStatuses(userIntent: false)
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-26 06:36:54 +00:00
|
|
|
func fetchStatuses(userIntent: Bool) async {
|
2022-12-19 11:28:55 +00:00
|
|
|
guard let client else { return }
|
2022-11-25 11:00:01 +00:00
|
|
|
do {
|
2022-12-24 11:20:42 +00:00
|
|
|
if statuses.isEmpty {
|
2022-12-26 07:24:55 +00:00
|
|
|
pendingStatuses = []
|
2022-12-24 11:20:42 +00:00
|
|
|
statusesState = .loading
|
2023-01-01 13:28:15 +00:00
|
|
|
statuses = try await client.get(endpoint: timeline.endpoint(sinceId: nil,
|
|
|
|
maxId: nil,
|
|
|
|
minId: nil,
|
|
|
|
offset: statuses.count))
|
2023-01-12 18:12:23 +00:00
|
|
|
withAnimation {
|
|
|
|
statusesState = .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage)
|
|
|
|
}
|
2023-01-05 05:39:23 +00:00
|
|
|
} else if let first = pendingStatuses.first ?? statuses.first {
|
|
|
|
var newStatuses: [Status] = await fetchNewPages(minId: first.id, maxPages: 20)
|
2022-12-26 06:36:54 +00:00
|
|
|
if userIntent || !pendingStatusesEnabled {
|
2023-01-05 05:39:23 +00:00
|
|
|
statuses.insert(contentsOf: pendingStatuses, at: 0)
|
2022-12-26 07:24:55 +00:00
|
|
|
pendingStatuses = []
|
2022-12-28 18:10:13 +00:00
|
|
|
withAnimation {
|
2022-12-30 07:36:22 +00:00
|
|
|
statusesState = .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage)
|
2022-12-28 18:10:13 +00:00
|
|
|
}
|
2022-12-26 06:36:54 +00:00
|
|
|
} else {
|
2022-12-26 07:24:55 +00:00
|
|
|
newStatuses = newStatuses.filter { status in
|
2023-01-30 21:11:55 +00:00
|
|
|
!statuses.contains(where: { $0.id == status.id })
|
2022-12-26 07:24:55 +00:00
|
|
|
}
|
|
|
|
pendingStatuses.insert(contentsOf: newStatuses, at: 0)
|
2023-01-30 21:11:55 +00:00
|
|
|
statuses.insert(contentsOf: newStatuses, at: 0)
|
2023-01-30 20:41:42 +00:00
|
|
|
withAnimation {
|
|
|
|
statusesState = .display(statuses: statuses, nextPageState: statuses.count < 20 ? .none : .hasNextPage)
|
|
|
|
}
|
2022-12-26 06:36:54 +00:00
|
|
|
}
|
2022-12-24 11:20:42 +00:00
|
|
|
}
|
2022-11-25 11:00:01 +00:00
|
|
|
} catch {
|
2022-12-19 06:17:01 +00:00
|
|
|
statusesState = .error(error: error)
|
2022-12-21 11:39:29 +00:00
|
|
|
print("timeline parse error: \(error)")
|
2022-11-25 11:00:01 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-28 18:10:13 +00:00
|
|
|
func fetchNewPages(minId: String, maxPages: Int) async -> [Status] {
|
|
|
|
guard let client else { return [] }
|
|
|
|
var pagesLoaded = 0
|
|
|
|
var allStatuses: [Status] = []
|
|
|
|
var latestMinId = minId
|
|
|
|
do {
|
|
|
|
while let newStatuses: [Status] = try await client.get(endpoint: timeline.endpoint(sinceId: nil,
|
|
|
|
maxId: nil,
|
2023-01-01 13:28:15 +00:00
|
|
|
minId: latestMinId,
|
|
|
|
offset: statuses.count)),
|
2023-01-17 10:36:01 +00:00
|
|
|
!newStatuses.isEmpty,
|
|
|
|
pagesLoaded < maxPages
|
|
|
|
{
|
2022-12-28 18:10:13 +00:00
|
|
|
pagesLoaded += 1
|
|
|
|
allStatuses.insert(contentsOf: newStatuses, at: 0)
|
|
|
|
latestMinId = newStatuses.first?.id ?? ""
|
|
|
|
}
|
|
|
|
} catch {
|
2023-01-05 05:39:23 +00:00
|
|
|
return allStatuses
|
2022-12-28 18:10:13 +00:00
|
|
|
}
|
|
|
|
return allStatuses
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-19 06:17:01 +00:00
|
|
|
func fetchNextPage() async {
|
2022-12-19 11:28:55 +00:00
|
|
|
guard let client else { return }
|
2022-11-25 11:00:01 +00:00
|
|
|
do {
|
|
|
|
guard let lastId = statuses.last?.id else { return }
|
2022-12-19 06:17:01 +00:00
|
|
|
statusesState = .display(statuses: statuses, nextPageState: .loadingNextPage)
|
2022-12-28 18:10:13 +00:00
|
|
|
let newStatuses: [Status] = try await client.get(endpoint: timeline.endpoint(sinceId: nil,
|
|
|
|
maxId: lastId,
|
2023-01-01 13:28:15 +00:00
|
|
|
minId: nil,
|
|
|
|
offset: statuses.count))
|
2022-11-25 11:00:01 +00:00
|
|
|
statuses.append(contentsOf: newStatuses)
|
2022-12-19 06:17:01 +00:00
|
|
|
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
2022-11-25 11:00:01 +00:00
|
|
|
} catch {
|
2022-12-19 06:17:01 +00:00
|
|
|
statusesState = .error(error: error)
|
2022-11-25 11:00:01 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-21 11:39:29 +00:00
|
|
|
func fetchTag(id: String) async {
|
|
|
|
guard let client else { return }
|
|
|
|
do {
|
|
|
|
tag = try await client.get(endpoint: Tags.tag(id: id))
|
|
|
|
} catch {}
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2022-12-25 18:18:19 +00:00
|
|
|
func handleEvent(event: any StreamEvent, currentAccount: CurrentAccount) {
|
2023-01-05 05:39:23 +00:00
|
|
|
if let event = event as? StreamEventUpdate,
|
|
|
|
pendingStatusesEnabled,
|
2023-01-30 21:11:55 +00:00
|
|
|
!statuses.contains(where: { $0.id == event.status.id })
|
2023-01-17 10:36:01 +00:00
|
|
|
{
|
2023-01-30 20:41:42 +00:00
|
|
|
pendingStatuses.insert(event.status, at: 0)
|
|
|
|
statuses.insert(event.status, at: 0)
|
|
|
|
withAnimation {
|
|
|
|
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
2022-12-25 18:18:19 +00:00
|
|
|
}
|
2022-12-25 11:46:42 +00:00
|
|
|
} else if let event = event as? StreamEventDelete {
|
2023-01-05 05:39:23 +00:00
|
|
|
withAnimation {
|
|
|
|
statuses.removeAll(where: { $0.id == event.status })
|
|
|
|
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
|
|
|
}
|
2022-12-26 06:36:54 +00:00
|
|
|
} else if let event = event as? StreamEventStatusUpdate {
|
|
|
|
if let originalIndex = statuses.firstIndex(where: { $0.id == event.status.id }) {
|
|
|
|
statuses[originalIndex] = event.status
|
|
|
|
statusesState = .display(statuses: statuses, nextPageState: .hasNextPage)
|
|
|
|
}
|
2022-12-25 11:46:42 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-17 10:36:01 +00:00
|
|
|
|
2023-01-30 20:41:42 +00:00
|
|
|
func statusDidAppear(status: Status) async {
|
|
|
|
if let index = pendingStatuses.firstIndex(of: status) {
|
|
|
|
pendingStatuses.remove(at: index)
|
2023-01-05 06:07:28 +00:00
|
|
|
}
|
|
|
|
}
|
2022-11-25 11:00:01 +00:00
|
|
|
}
|