diff --git a/Sources/Screens/Invitation/InvitationCreate.swift b/Sources/Screens/Invitation/InvitationCreate.swift index dab7c914..75977ab5 100644 --- a/Sources/Screens/Invitation/InvitationCreate.swift +++ b/Sources/Screens/Invitation/InvitationCreate.swift @@ -12,16 +12,13 @@ import Foundation import SwiftUI import VoltaserveCore -struct InvitationCreate: View { +struct InvitationCreate: View, FormValidatable, TokenDistributing, ErrorPresentable { @EnvironmentObject private var tokenStore: TokenStore @StateObject private var invitationStore = InvitationStore() @Environment(\.dismiss) private var dismiss @State private var commaSeparated = "" @State private var emails: [String] = [] @State private var isSending = false - @State private var showError = false - @State private var errorTitle: String? - @State private var errorMessage: String? private let organizationID: String init(_ organizationID: String) { @@ -66,7 +63,7 @@ struct InvitationCreate: View { } } } - .voErrorAlert(isPresented: $showError, title: errorTitle, message: errorMessage) + .voErrorSheet(isPresented: $errorIsPresented, message: errorMessage) .onAppear { invitationStore.organizationID = organizationID if let token = tokenStore.token { @@ -80,10 +77,6 @@ struct InvitationCreate: View { } } - private func assignTokenToStores(_ token: VOToken.Value) { - invitationStore.token = token - } - private func parseEmails() { var values: [String] = [] for item in commaSeparated.split(separator: ",") { @@ -103,15 +96,27 @@ struct InvitationCreate: View { } success: { dismiss() } failure: { message in - errorTitle = "Error: Sending Invitation" errorMessage = message - showError = true + errorIsPresented = true } anyways: { isSending = false } } - private func isValid() -> Bool { + // MARK: - ErrorPresentable + + @State var errorIsPresented: Bool = false + @State var errorMessage: String? + + // MARK: - FormValidatable + + func isValid() -> Bool { !emails.isEmpty } + + // MARK: - TokenDistributing + + func assignTokenToStores(_ token: VOToken.Value) { + invitationStore.token = token + } } diff --git a/Sources/Screens/Invitation/InvitationIncomingList.swift b/Sources/Screens/Invitation/InvitationIncomingList.swift index 06964837..a74823a7 100644 --- a/Sources/Screens/Invitation/InvitationIncomingList.swift +++ b/Sources/Screens/Invitation/InvitationIncomingList.swift @@ -11,7 +11,9 @@ import SwiftUI import VoltaserveCore -struct InvitationIncomingList: View { +struct InvitationIncomingList: View, ViewDataProvider, LoadStateProvider, TimerLifecycle, TokenDistributing, + ListItemScrollable +{ @EnvironmentObject private var tokenStore: TokenStore @StateObject private var invitationStore = InvitationStore() @State private var showInfo = false @@ -19,34 +21,38 @@ struct InvitationIncomingList: View { var body: some View { VStack { - if let entities = invitationStore.entities { - Group { - if entities.isEmpty { - Text("There are no invitations.") - } else { - List { - ForEach(entities, id: \.id) { invitation in - NavigationLink { - InvitationOverview( - invitation, - invitationStore: invitationStore, - isAcceptableDeclinable: true - ) - } label: { - InvitationIncomingRow(invitation) - .onAppear { - onListItemAppear(invitation.id) - } + if isLoading { + ProgressView() + } else if let error { + VOErrorMessage(error) + } else { + if let entities = invitationStore.entities { + Group { + if entities.isEmpty { + Text("There are no invitations.") + } else { + List { + ForEach(entities, id: \.id) { invitation in + NavigationLink { + InvitationOverview( + invitation, + invitationStore: invitationStore, + isAcceptableDeclinable: true + ) + } label: { + InvitationIncomingRow(invitation) + .onAppear { + onListItemAppear(invitation.id) + } + } } } } } + .refreshable { + invitationStore.fetchNextPage(replace: true) + } } - .refreshable { - invitationStore.fetchNextPage(replace: true) - } - } else { - ProgressView() } } .navigationBarTitleDisplayMode(.inline) @@ -76,27 +82,45 @@ struct InvitationIncomingList: View { } } - private func onAppearOrChange() { + // MARK: - LoadStateProvider + + var isLoading: Bool { + invitationStore.entitiesIsLoadingFirstTime + } + + var error: String? { + invitationStore.entitiesError + } + + // MARK: - ViewDataProvider + + func onAppearOrChange() { fetchData() } - private func fetchData() { + func fetchData() { invitationStore.fetchNextPage(replace: true) } - private func startTimers() { + // MARK: - TimerLifecycle + + func startTimers() { invitationStore.startTimer() } - private func stopTimers() { + func stopTimers() { invitationStore.stopTimer() } - private func assignTokenToStores(_ token: VOToken.Value) { + // MARK: - TokenDistributing + + func assignTokenToStores(_ token: VOToken.Value) { invitationStore.token = token } - private func onListItemAppear(_ id: String) { + // MARK: - ListItemScrollable + + func onListItemAppear(_ id: String) { if invitationStore.isEntityThreshold(id) { invitationStore.fetchNextPage() } diff --git a/Sources/Screens/Invitation/InvitationOutgoingList.swift b/Sources/Screens/Invitation/InvitationOutgoingList.swift index 1f3d2216..c66dce8a 100644 --- a/Sources/Screens/Invitation/InvitationOutgoingList.swift +++ b/Sources/Screens/Invitation/InvitationOutgoingList.swift @@ -11,7 +11,9 @@ import SwiftUI import VoltaserveCore -struct InvitationOutgoingList: View { +struct InvitationOutgoingList: View, ViewDataProvider, LoadStateProvider, TimerLifecycle, TokenDistributing, + ListItemScrollable +{ @EnvironmentObject private var tokenStore: TokenStore @StateObject private var invitationStore = InvitationStore() @StateObject private var organizationStore = OrganizationStore() @@ -25,34 +27,38 @@ struct InvitationOutgoingList: View { var body: some View { VStack { - if let entities = invitationStore.entities { - Group { - if entities.isEmpty { - Text("There are no invitations.") - } else { - List { - ForEach(entities, id: \.id) { invitation in - NavigationLink { - InvitationOverview( - invitation, - invitationStore: invitationStore, - isDeletable: true - ) - } label: { - InvitationOutgoingRow(invitation) - .onAppear { - onListItemAppear(invitation.id) - } + if isLoading { + ProgressView() + } else if let error { + VOErrorMessage(error) + } else { + if let entities = invitationStore.entities { + Group { + if entities.isEmpty { + Text("There are no invitations.") + } else { + List { + ForEach(entities, id: \.id) { invitation in + NavigationLink { + InvitationOverview( + invitation, + invitationStore: invitationStore, + isDeletable: true + ) + } label: { + InvitationOutgoingRow(invitation) + .onAppear { + onListItemAppear(invitation.id) + } + } } } } } + .refreshable { + invitationStore.fetchNextPage(replace: true) + } } - .refreshable { - invitationStore.fetchNextPage(replace: true) - } - } else { - ProgressView() } } .navigationTitle("Invitations") @@ -92,30 +98,48 @@ struct InvitationOutgoingList: View { } } - private func onAppearOrChange() { + // MARK: - LoadStateProvider + + var isLoading: Bool { + invitationStore.entitiesIsLoadingFirstTime + } + + var error: String? { + invitationStore.entitiesError + } + + // MARK: - ViewDataProvider + + func onAppearOrChange() { fetchData() } - private func fetchData() { + func fetchData() { invitationStore.fetchNextPage(replace: true) } - private func startTimers() { + // MARK: - TimerLifecycle + + func startTimers() { invitationStore.startTimer() organizationStore.startTimer() } - private func stopTimers() { + func stopTimers() { invitationStore.stopTimer() organizationStore.stopTimer() } - private func assignTokenToStores(_ token: VOToken.Value) { + // MARK: - TokenDistributing + + func assignTokenToStores(_ token: VOToken.Value) { invitationStore.token = token organizationStore.token = token } - private func onListItemAppear(_ id: String) { + // MARK: - ListItemScrollable + + func onListItemAppear(_ id: String) { if invitationStore.isEntityThreshold(id) { invitationStore.fetchNextPage() } diff --git a/Sources/Screens/Invitation/InvitationOverview.swift b/Sources/Screens/Invitation/InvitationOverview.swift index ad08822b..b12284f9 100644 --- a/Sources/Screens/Invitation/InvitationOverview.swift +++ b/Sources/Screens/Invitation/InvitationOverview.swift @@ -11,16 +11,13 @@ import SwiftUI import VoltaserveCore -struct InvitationOverview: View { +struct InvitationOverview: View, TokenDistributing, ErrorPresentable { @EnvironmentObject private var tokenStore: TokenStore @ObservedObject private var invitationStore: InvitationStore @StateObject private var userStore = UserStore() @Environment(\.dismiss) private var dismiss - @State private var showError = false - @State private var errorTitle: String? - @State private var errorMessage: String? - @State private var showDeleteConfirmation = false - @State private var showDeclineConfirmation = false + @State private var deleteConfirmationIsPresented = false + @State private var declineConfirmationIsPresented = false @State private var isAccepting = false @State private var isDeclining = false @State private var isDeleting = false @@ -82,7 +79,7 @@ struct InvitationOverview: View { Section { if isDeletable { Button(role: .destructive) { - showDeleteConfirmation = true + deleteConfirmationIsPresented = true } label: { HStack { Text("Delete Invitation") @@ -93,7 +90,7 @@ struct InvitationOverview: View { } } .disabled(isProcessing) - .confirmationDialog("Delete Invitation", isPresented: $showDeleteConfirmation) { + .confirmationDialog("Delete Invitation", isPresented: $deleteConfirmationIsPresented) { Button("Delete", role: .destructive) { performDelete() } @@ -115,7 +112,7 @@ struct InvitationOverview: View { } .disabled(isProcessing) Button(role: .destructive) { - showDeclineConfirmation = true + declineConfirmationIsPresented = true } label: { HStack { Text("Decline Invitation") @@ -126,7 +123,7 @@ struct InvitationOverview: View { } } .disabled(isProcessing) - .confirmationDialog("Decline Invitation", isPresented: $showDeclineConfirmation) { + .confirmationDialog("Decline Invitation", isPresented: $declineConfirmationIsPresented) { Button("Decline", role: .destructive) { performDecline() } @@ -138,7 +135,7 @@ struct InvitationOverview: View { } .navigationBarTitleDisplayMode(.inline) .navigationTitle("#\(invitation.id)") - .voErrorAlert(isPresented: $showError, title: errorTitle, message: errorMessage) + .voErrorSheet(isPresented: $errorIsPresented, message: errorMessage) .onAppear { userStore.invitationID = invitation.id if let token = tokenStore.token { @@ -156,10 +153,6 @@ struct InvitationOverview: View { isAccepting || isDeclining || isDeleting } - private func assignTokenToStores(_ token: VOToken.Value) { - userStore.token = token - } - private func performAccept() { isAccepting = true withErrorHandling { @@ -168,9 +161,8 @@ struct InvitationOverview: View { } success: { dismiss() } failure: { message in - errorTitle = "Error: Accepting Invitation" errorMessage = message - showError = true + errorIsPresented = true } anyways: { isAccepting = false } @@ -184,9 +176,8 @@ struct InvitationOverview: View { } success: { dismiss() } failure: { message in - errorTitle = "Error: Declining Invitation" errorMessage = message - showError = true + errorIsPresented = true } anyways: { isDeclining = false } @@ -200,11 +191,21 @@ struct InvitationOverview: View { } success: { dismiss() } failure: { message in - errorTitle = "Error: Deleting Invitation" errorMessage = message - showError = true + errorIsPresented = true } anyways: { isDeleting = false } } + + // MARK: - ErrorPresentable + + @State var errorIsPresented: Bool = false + @State var errorMessage: String? + + // MARK: - TokenDistributing + + func assignTokenToStores(_ token: VOToken.Value) { + userStore.token = token + } }