Batch accept / reject notifications requests

This commit is contained in:
Thomas Ricouard 2024-10-19 12:27:18 +02:00
parent d8f2b0f3ae
commit 15a6fd6f1c
3 changed files with 159 additions and 52 deletions

View file

@ -11,8 +11,8 @@ public enum Notifications: Endpoint {
case policy case policy
case putPolicy(policy: Models.NotificationsPolicy) case putPolicy(policy: Models.NotificationsPolicy)
case requests case requests
case acceptRequest(id: String) case acceptRequests(ids: [String])
case dismissRequest(id: String) case dismissRequests(ids: [String])
case clear case clear
public func path() -> String { public func path() -> String {
@ -25,10 +25,10 @@ public enum Notifications: Endpoint {
"notifications/policy" "notifications/policy"
case .requests: case .requests:
"notifications/requests" "notifications/requests"
case let .acceptRequest(id): case .acceptRequests:
"notifications/requests/\(id)/accept" "notifications/requests/accept"
case let .dismissRequest(id): case .dismissRequests:
"notifications/requests/\(id)/dismiss" "notifications/requests/dismiss"
case .clear: case .clear:
"notifications/clear" "notifications/clear"
} }
@ -58,6 +58,8 @@ public enum Notifications: Endpoint {
} }
} }
return params return params
case let .acceptRequests(ids), let .dismissRequests(ids):
return ids.map { URLQueryItem(name: "id[]", value: $0) }
default: default:
return nil return nil
} }

View file

@ -1,4 +1,5 @@
import DesignSystem import DesignSystem
import Env
import Models import Models
import Network import Network
import SwiftUI import SwiftUI
@ -7,6 +8,7 @@ import SwiftUI
public struct NotificationsRequestsListView: View { public struct NotificationsRequestsListView: View {
@Environment(Client.self) private var client @Environment(Client.self) private var client
@Environment(Theme.self) private var theme @Environment(Theme.self) private var theme
@Environment(RouterPath.self) private var routerPath
enum ViewState { enum ViewState {
case loading case loading
@ -15,6 +17,9 @@ public struct NotificationsRequestsListView: View {
} }
@State private var viewState: ViewState = .loading @State private var viewState: ViewState = .loading
@State private var isProcessingRequests: Bool = false
@State private var isInSelectMode: Bool = false
@State private var selectedRequests: [NotificationsRequest] = []
public init() {} public init() {}
@ -28,10 +33,11 @@ public struct NotificationsRequestsListView: View {
#endif #endif
.listSectionSeparator(.hidden) .listSectionSeparator(.hidden)
case .error: case .error:
ErrorView(title: "notifications.error.title", ErrorView(
title: "notifications.error.title",
message: "notifications.error.message", message: "notifications.error.message",
buttonTitle: "action.retry") buttonTitle: "action.retry"
{ ) {
await fetchRequests(client) await fetchRequests(client)
} }
#if !os(visionOS) #if !os(visionOS)
@ -39,8 +45,119 @@ public struct NotificationsRequestsListView: View {
#endif #endif
.listSectionSeparator(.hidden) .listSectionSeparator(.hidden)
case let .requests(data): case let .requests(data):
if isInSelectMode {
selectSection(data: data)
}
requestsSection(data: data)
}
}
.listStyle(.plain)
#if !os(visionOS)
.scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor)
#endif
.navigationTitle("notifications.content-filter.requests.title")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
withAnimation {
isInSelectMode.toggle()
if !isInSelectMode {
selectedRequests.removeAll()
}
}
} label: {
Text(isInSelectMode ? "Cancel" : "Select")
}
}
}
.task {
await fetchRequests(client)
}
.refreshable {
await fetchRequests(client)
}
}
private func selectSection(data: [NotificationsRequest]) -> some View {
Section {
HStack(alignment: .center) {
Button {
withAnimation {
selectedRequests = data
}
} label: {
Text("Select all")
}
.buttonStyle(.bordered)
Button {
Task { await acceptSelectedRequests(client) }
} label: {
Text("Accept")
}
.disabled(isProcessingRequests)
.buttonStyle(.borderedProminent)
Button {
Task { await rejectSelectedRequests(client) }
} label: {
Text("Reject")
}
.disabled(isProcessingRequests)
.buttonStyle(.bordered)
.tint(.red)
}
}
.listRowBackground(theme.primaryBackgroundColor)
}
private func requestsSection(data: [NotificationsRequest]) -> some View {
Section {
ForEach(data) { request in ForEach(data) { request in
let isSelected = selectedRequests.contains(where: { $0.id == request.id })
HStack {
if isInSelectMode {
if isSelected {
Image(systemName: "checkmark.circle.fill")
.resizable()
.frame(width: 20, height: 20)
.foregroundStyle(theme.tintColor)
} else {
Circle()
.strokeBorder(theme.tintColor, lineWidth: 1)
.frame(width: 20, height: 20)
}
}
NotificationsRequestsRowView(request: request) NotificationsRequestsRowView(request: request)
}
.onTapGesture {
if isInSelectMode {
withAnimation {
if isSelected {
selectedRequests.removeAll { $0.id == request.id }
} else {
selectedRequests.append(request)
}
}
} else {
routerPath.navigate(to: .notificationForAccount(accountId: request.account.id))
}
}
.listRowInsets(
.init(
top: 12,
leading: .layoutPadding,
bottom: 12,
trailing: .layoutPadding)
)
#if os(visionOS)
.listRowBackground(
RoundedRectangle(cornerRadius: 8)
.foregroundStyle(.background))
#else
.listRowBackground(theme.primaryBackgroundColor)
#endif
.swipeActions { .swipeActions {
Button { Button {
Task { await acceptRequest(client, request) } Task { await acceptRequest(client, request) }
@ -58,20 +175,6 @@ public struct NotificationsRequestsListView: View {
} }
} }
} }
.listStyle(.plain)
#if !os(visionOS)
.scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor)
#endif
.navigationTitle("notifications.content-filter.requests.title")
.navigationBarTitleDisplayMode(.inline)
.task {
await fetchRequests(client)
}
.refreshable {
await fetchRequests(client)
}
}
private func fetchRequests(_ client: Client) async { private func fetchRequests(_ client: Client) async {
do { do {
@ -82,12 +185,28 @@ public struct NotificationsRequestsListView: View {
} }
private func acceptRequest(_ client: Client, _ request: NotificationsRequest) async { private func acceptRequest(_ client: Client, _ request: NotificationsRequest) async {
_ = try? await client.post(endpoint: Notifications.acceptRequest(id: request.id)) _ = try? await client.post(endpoint: Notifications.acceptRequests(ids: [request.id]))
await fetchRequests(client) await fetchRequests(client)
} }
private func dismissRequest(_ client: Client, _ request: NotificationsRequest) async { private func dismissRequest(_ client: Client, _ request: NotificationsRequest) async {
_ = try? await client.post(endpoint: Notifications.dismissRequest(id: request.id)) _ = try? await client.post(endpoint: Notifications.dismissRequests(ids: [request.id]))
await fetchRequests(client) await fetchRequests(client)
} }
private func acceptSelectedRequests(_ client: Client) async {
isProcessingRequests = true
_ = try? await client.post(
endpoint: Notifications.acceptRequests(ids: selectedRequests.map { $0.id }))
await fetchRequests(client)
isProcessingRequests = false
}
private func rejectSelectedRequests(_ client: Client) async {
isProcessingRequests = true
_ = try? await client.post(
endpoint: Notifications.dismissRequests(ids: selectedRequests.map { $0.id }))
await fetchRequests(client)
isProcessingRequests = false
}
} }

View file

@ -6,7 +6,6 @@ import SwiftUI
struct NotificationsRequestsRowView: View { struct NotificationsRequestsRowView: View {
@Environment(Theme.self) private var theme @Environment(Theme.self) private var theme
@Environment(RouterPath.self) private var routerPath
@Environment(Client.self) private var client @Environment(Client.self) private var client
let request: NotificationsRequest let request: NotificationsRequest
@ -39,18 +38,5 @@ struct NotificationsRequestsRowView: View {
Image(systemName: "chevron.right") Image(systemName: "chevron.right")
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
.onTapGesture {
routerPath.navigate(to: .notificationForAccount(accountId: request.account.id))
}
.listRowInsets(.init(top: 12,
leading: .layoutPadding,
bottom: 12,
trailing: .layoutPadding))
#if os(visionOS)
.listRowBackground(RoundedRectangle(cornerRadius: 8)
.foregroundStyle(.background))
#else
.listRowBackground(theme.primaryBackgroundColor)
#endif
} }
} }