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

View file

@ -1,4 +1,5 @@
import DesignSystem
import Env
import Models
import Network
import SwiftUI
@ -7,6 +8,7 @@ import SwiftUI
public struct NotificationsRequestsListView: View {
@Environment(Client.self) private var client
@Environment(Theme.self) private var theme
@Environment(RouterPath.self) private var routerPath
enum ViewState {
case loading
@ -15,6 +17,9 @@ public struct NotificationsRequestsListView: View {
}
@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() {}
@ -23,39 +28,27 @@ public struct NotificationsRequestsListView: View {
switch viewState {
case .loading:
ProgressView()
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
#endif
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
#endif
.listSectionSeparator(.hidden)
case .error:
ErrorView(title: "notifications.error.title",
message: "notifications.error.message",
buttonTitle: "action.retry")
{
ErrorView(
title: "notifications.error.title",
message: "notifications.error.message",
buttonTitle: "action.retry"
) {
await fetchRequests(client)
}
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor)
.listRowBackground(theme.primaryBackgroundColor)
#endif
.listSectionSeparator(.hidden)
case let .requests(data):
ForEach(data) { request in
NotificationsRequestsRowView(request: request)
.swipeActions {
Button {
Task { await acceptRequest(client, request) }
} label: {
Label("account.follow-request.accept", systemImage: "checkmark")
}
Button {
Task { await dismissRequest(client, request) }
} label: {
Label("account.follow-request.reject", systemImage: "xmark")
}
.tint(.red)
}
if isInSelectMode {
selectSection(data: data)
}
requestsSection(data: data)
}
}
.listStyle(.plain)
@ -63,14 +56,124 @@ public struct NotificationsRequestsListView: View {
.scrollContentBackground(.hidden)
.background(theme.primaryBackgroundColor)
#endif
.navigationTitle("notifications.content-filter.requests.title")
.navigationBarTitleDisplayMode(.inline)
.task {
await fetchRequests(client)
.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")
}
}
.refreshable {
await fetchRequests(client)
}
.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
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)
}
.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 {
Button {
Task { await acceptRequest(client, request) }
} label: {
Label("account.follow-request.accept", systemImage: "checkmark")
}
Button {
Task { await dismissRequest(client, request) }
} label: {
Label("account.follow-request.reject", systemImage: "xmark")
}
.tint(.red)
}
}
}
}
private func fetchRequests(_ client: Client) async {
@ -82,12 +185,28 @@ public struct NotificationsRequestsListView: View {
}
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)
}
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)
}
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 {
@Environment(Theme.self) private var theme
@Environment(RouterPath.self) private var routerPath
@Environment(Client.self) private var client
let request: NotificationsRequest
@ -39,18 +38,5 @@ struct NotificationsRequestsRowView: View {
Image(systemName: "chevron.right")
.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
}
}