Vernissage/Vernissage/Views/NotificationsView/NotificationsView.swift

148 lines
5.2 KiB
Swift
Raw Normal View History

2022-12-30 18:20:54 +01:00
//
// https://mczachurski.dev
2023-01-18 18:41:42 +01:00
// Copyright © 2023 Marcin Czachurski and the repository contributors.
2023-03-28 10:35:38 +02:00
// Licensed under the Apache License 2.0.
2022-12-30 18:20:54 +01:00
//
2022-12-30 18:20:54 +01:00
import SwiftUI
2023-02-19 10:32:38 +01:00
import PixelfedKit
2023-04-07 14:20:12 +02:00
import ClientKit
2023-04-07 14:38:50 +02:00
import ServicesKit
2023-04-07 16:59:18 +02:00
import EnvironmentKit
import WidgetsKit
2022-12-30 18:20:54 +01:00
2023-10-19 13:24:02 +02:00
@MainActor
2022-12-30 18:20:54 +01:00
struct NotificationsView: View {
2023-10-19 13:24:02 +02:00
@Environment(ApplicationState.self) var applicationState
@Environment(Client.self) var client
2023-10-22 10:09:02 +02:00
@Environment(\.modelContext) private var modelContext
2023-01-18 18:41:42 +01:00
@State var accountId: String
2023-02-19 10:32:38 +01:00
@State private var notifications: [PixelfedKit.Notification] = []
2023-01-18 18:41:42 +01:00
@State private var allItemsLoaded = false
2023-02-01 18:40:28 +01:00
@State private var state: ViewState = .loading
2023-01-18 18:41:42 +01:00
@State private var minId: String?
@State private var maxId: String?
2023-02-01 18:40:28 +01:00
private let defaultPageSize = 40
2022-12-30 18:20:54 +01:00
var body: some View {
2023-02-01 18:40:28 +01:00
self.mainBody()
2023-03-13 13:53:36 +01:00
.navigationTitle("notifications.navigationBar.title")
2023-02-01 18:40:28 +01:00
}
2023-02-01 18:40:28 +01:00
@ViewBuilder
private func mainBody() -> some View {
switch state {
case .loading:
LoadingIndicator()
.task {
await self.loadNotifications()
2023-01-18 18:41:42 +01:00
}
2023-02-01 18:40:28 +01:00
case .loaded:
if self.notifications.isEmpty {
2023-03-13 13:53:36 +01:00
NoDataView(imageSystemName: "bell", text: "notifications.title.noNotifications")
2023-01-18 18:41:42 +01:00
} else {
self.list()
2023-01-18 18:41:42 +01:00
}
2023-02-01 18:40:28 +01:00
case .error(let error):
ErrorView(error: error) {
self.state = .loading
await self.loadMoreNotifications()
}
.padding()
2023-01-18 18:41:42 +01:00
}
}
@ViewBuilder
private func list() -> some View {
List {
ForEach(notifications, id: \.id) { notification in
NotificationRowView(notification: notification)
}
if allItemsLoaded == false {
HStack {
Spacer()
LoadingIndicator()
.task {
await self.loadMoreNotifications()
}
Spacer()
}
.listRowSeparator(.hidden)
}
}
.listStyle(PlainListStyle())
.refreshable {
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
2023-10-22 10:09:02 +02:00
await self.refreshNotifications()
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
}
}
2023-01-18 18:41:42 +01:00
func loadNotifications() async {
do {
2023-02-03 15:16:30 +01:00
if let linkable = try await self.client.notifications?.notifications(maxId: maxId, minId: minId, limit: 5) {
self.minId = linkable.link?.minId
self.maxId = linkable.link?.maxId
self.notifications = linkable.data
2023-02-03 15:16:30 +01:00
if linkable.data.isEmpty {
self.allItemsLoaded = true
}
withAnimation {
self.state = .loaded
}
2023-10-22 10:09:02 +02:00
try AccountDataHandler.shared.update(lastSeenNotificationId: linkable.data.first?.id, applicationState: self.applicationState, modelContext: modelContext)
self.applicationState.newNotificationsHasBeenAdded = false
2023-01-18 18:41:42 +01:00
}
} catch {
2023-02-01 18:40:28 +01:00
if !Task.isCancelled {
2023-03-13 13:53:36 +01:00
ErrorService.shared.handle(error, message: "notifications.error.loadingNotificationsFailed", showToastr: true)
2023-02-01 18:40:28 +01:00
self.state = .error(error)
} else {
2023-03-13 13:53:36 +01:00
ErrorService.shared.handle(error, message: "notifications.error.loadingNotificationsFailed", showToastr: false)
2023-02-01 18:40:28 +01:00
}
2023-01-18 18:41:42 +01:00
}
}
2023-01-18 18:41:42 +01:00
private func loadMoreNotifications() async {
do {
2023-02-03 15:16:30 +01:00
if let linkable = try await self.client.notifications?.notifications(maxId: self.maxId, limit: self.defaultPageSize) {
if linkable.data.isEmpty {
self.allItemsLoaded = true
return
}
2023-02-03 15:16:30 +01:00
self.maxId = linkable.link?.maxId
self.notifications.append(contentsOf: linkable.data)
2023-01-18 18:41:42 +01:00
}
} catch {
2023-03-13 13:53:36 +01:00
ErrorService.shared.handle(error, message: "notifications.error.loadingNotificationsFailed", showToastr: !Task.isCancelled)
2023-01-18 18:41:42 +01:00
}
}
2023-10-22 10:09:02 +02:00
private func refreshNotifications() async {
2023-01-18 18:41:42 +01:00
do {
2023-02-03 15:16:30 +01:00
if let linkable = try await self.client.notifications?.notifications(minId: self.minId, limit: self.defaultPageSize) {
if let first = linkable.data.first, self.notifications.contains(where: { notification in notification.id == first.id }) {
// We have all notifications, we don't have to do anything.
return
}
2023-10-22 10:09:02 +02:00
try AccountDataHandler.shared.update(lastSeenNotificationId: linkable.data.first?.id, applicationState: self.applicationState, modelContext: modelContext)
self.applicationState.newNotificationsHasBeenAdded = false
2023-02-03 15:16:30 +01:00
self.minId = linkable.link?.minId
self.notifications.insert(contentsOf: linkable.data, at: 0)
2023-01-18 18:41:42 +01:00
}
} catch {
2023-03-13 13:53:36 +01:00
ErrorService.shared.handle(error, message: "notifications.error.loadingNotificationsFailed", showToastr: !Task.isCancelled)
2023-01-18 18:41:42 +01:00
}
}
2022-12-30 18:20:54 +01:00
}