Refactor alerts to use latest API (#320)

This commit is contained in:
Davide De Rosa 2023-07-03 16:41:49 +02:00 committed by GitHub
parent de7e574fec
commit 7198150f00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 203 additions and 111 deletions

View File

@ -16,6 +16,8 @@ private struct ErrorAlert: Identifiable {
final class ErrorHandler: ObservableObject {
static let shared = ErrorHandler()
@Published fileprivate var isPresented = false
@Published fileprivate var currentAlert: ErrorAlert?
func handle(_ error: Error, title: String? = nil, onDismiss: (() -> Void)? = nil) {
@ -24,6 +26,7 @@ final class ErrorHandler: ObservableObject {
message: AppError(error).localizedDescription,
dismissAction: onDismiss
)
isPresented = true
}
func handle(title: String, message: String, onDismiss: (() -> Void)? = nil) {
@ -32,6 +35,7 @@ final class ErrorHandler: ObservableObject {
message: message,
dismissAction: onDismiss
)
isPresented = true
}
}
@ -49,14 +53,18 @@ struct HandleErrorsByShowingAlertViewModifier: ViewModifier {
// other .alert modifiers inside of content would not work anymore
.background(
EmptyView()
.alert(item: $errorHandler.currentAlert) { currentAlert in
Alert(
title: Text(currentAlert.title ?? Unlocalized.appName),
message: Text(currentAlert.message.withTrailingDot),
dismissButton: .cancel(Text(L10n.Global.Strings.ok)) {
currentAlert.dismissAction?()
.alert(
errorHandler.currentAlert?.title ?? Unlocalized.appName,
isPresented: $errorHandler.isPresented,
presenting: errorHandler.currentAlert
) { alert in
Button(role: .cancel) {
alert.dismissAction?()
} label: {
Text(L10n.Global.Strings.ok)
}
)
} message: { alert in
Text(alert.message.withTrailingDot)
}
)
}

View File

@ -74,8 +74,12 @@ extension AddHostView {
}
}
}
}.alert(isPresented: $viewModel.isAskingOverwrite, content: alertOverwriteExistingProfile)
.onAppear(perform: requestResourcePermissions)
}.alert(
L10n.AddProfile.Shared.title,
isPresented: $viewModel.isAskingOverwrite,
actions: alertOverwriteActions,
message: alertOverwriteMessage
).onAppear(perform: requestResourcePermissions)
.onDisappear(perform: dropResourcePermissions)
.navigationTitle(L10n.AddProfile.Shared.title)
.themeSecondaryView()
@ -158,15 +162,21 @@ extension AddHostView {
url.stopAccessingSecurityScopedResource()
}
private func alertOverwriteExistingProfile() -> Alert {
Alert(
title: Text(L10n.AddProfile.Shared.title),
message: Text(L10n.AddProfile.Shared.Alerts.Overwrite.message),
primaryButton: .destructive(Text(L10n.Global.Strings.ok)) {
@ViewBuilder
private func alertOverwriteActions() -> some View {
Button(role: .destructive) {
processProfile(replacingExisting: true)
},
secondaryButton: .cancel(Text(L10n.Global.Strings.cancel))
)
} label: {
Text(L10n.Global.Strings.ok)
}
Button(role: .cancel) {
} label: {
Text(L10n.Global.Strings.cancel)
}
}
private func alertOverwriteMessage() -> some View {
Text(L10n.AddProfile.Shared.Alerts.Overwrite.message)
}
private func processProfile(replacingExisting: Bool) {

View File

@ -77,8 +77,12 @@ extension AddProviderView {
} label: {
themeSaveButtonLabel()
}
}.alert(isPresented: $viewModel.isAskingOverwrite, content: alertOverwriteExistingProfile)
.navigationTitle(providerMetadata.fullName)
}.alert(
L10n.AddProfile.Shared.title,
isPresented: $viewModel.isAskingOverwrite,
actions: alertOverwriteActions,
message: alertOverwriteMessage
).navigationTitle(providerMetadata.fullName)
}
private var hiddenAccountLink: some View {
@ -90,15 +94,21 @@ extension AddProviderView {
}
}
private func alertOverwriteExistingProfile() -> Alert {
return Alert(
title: Text(L10n.AddProfile.Shared.title),
message: Text(L10n.AddProfile.Shared.Alerts.Overwrite.message),
primaryButton: .destructive(Text(L10n.Global.Strings.ok)) {
@ViewBuilder
private func alertOverwriteActions() -> some View {
Button(role: .destructive) {
saveProfile(replacingExisting: true)
},
secondaryButton: .cancel(Text(L10n.Global.Strings.cancel))
)
} label: {
Text(L10n.Global.Strings.ok)
}
Button(role: .cancel) {
} label: {
Text(L10n.Global.Strings.cancel)
}
}
private func alertOverwriteMessage() -> some View {
Text(L10n.AddProfile.Shared.Alerts.Overwrite.message)
}
private func saveProfile(replacingExisting: Bool) {

View File

@ -53,6 +53,8 @@ extension DiagnosticsView {
@State private var isReportingIssue = false
@State private var isAlertPresented = false
@State private var alertType: AlertType?
private let vpnProtocol: VPNProtocolType = .openVPN
@ -75,17 +77,26 @@ extension DiagnosticsView {
issueReporterSection
}
}.sheet(isPresented: $isReportingIssue, content: reportIssueView)
.alert(item: $alertType, content: presentedAlert)
.alert(
L10n.ReportIssue.Alert.title,
isPresented: $isAlertPresented,
presenting: alertType,
actions: alertActions,
message: alertMessage
)
}
private func presentedAlert(_ alertType: AlertType) -> Alert {
private func alertActions(_ alertType: AlertType) -> some View {
Button(role: .cancel) {
} label: {
Text(L10n.Global.Strings.ok)
}
}
private func alertMessage(_ alertType: AlertType) -> some View {
switch alertType {
case .emailNotConfigured:
return Alert(
title: Text(L10n.ReportIssue.Alert.title),
message: Text(L10n.Global.Messages.emailNotConfigured),
dismissButton: .cancel(Text(L10n.Global.Strings.ok))
)
return Text(L10n.Global.Messages.emailNotConfigured)
}
}
@ -180,6 +191,7 @@ extension DiagnosticsView.OpenVPNView {
}
guard URL.open(url) else {
alertType = .emailNotConfigured
isAlertPresented = true
return
}
}

View File

@ -43,6 +43,8 @@ struct DonateView: View {
@ObservedObject private var productManager: ProductManager
@State private var isAlertPresented = false
@State private var alertType: AlertType?
@State private var pendingDonationIdentifier: String?
@ -57,7 +59,13 @@ struct DonateView: View {
.disabled(pendingDonationIdentifier != nil)
}.themeSecondaryView()
.navigationTitle(L10n.Donate.title)
.alert(item: $alertType, content: presentedAlert)
.alert(
L10n.Donate.title,
isPresented: $isAlertPresented,
presenting: alertType,
actions: alertActions,
message: alertMessage
)
// reloading
.onAppear {
@ -69,14 +77,20 @@ struct DonateView: View {
}.themeAnimation(on: productManager.isRefreshingProducts)
}
private func presentedAlert(_ alertType: AlertType) -> Alert {
private func alertActions(_ alertType: AlertType) -> some View {
switch alertType {
case .thankYou:
return Alert(
title: Text(L10n.Donate.Alerts.Purchase.Success.title),
message: Text(L10n.Donate.Alerts.Purchase.Success.message),
dismissButton: .cancel(Text(L10n.Global.Strings.ok))
)
return Button(role: .cancel) {
} label: {
Text(L10n.Global.Strings.ok)
}
}
}
private func alertMessage(_ alertType: AlertType) -> some View {
switch alertType {
case .thankYou:
return Text(L10n.Donate.Alerts.Purchase.Success.message)
}
}
@ -124,6 +138,7 @@ extension DonateView {
case .success(let value):
if case .done = value {
alertType = .thankYou
isAlertPresented = true
} else {
// cancelled
}

View File

@ -30,13 +30,16 @@ extension OrganizerView {
struct SceneView: View {
@Environment(\.scenePhase) private var scenePhase
@Binding private var isAlertPresented: Bool
@Binding private var alertType: AlertType?
@Binding private var didHandleSubreddit: Bool
@State private var isFirstLaunch = true
init(alertType: Binding<AlertType?>, didHandleSubreddit: Binding<Bool>) {
init(isAlertPresented: Binding<Bool>, alertType: Binding<AlertType?>, didHandleSubreddit: Binding<Bool>) {
_isAlertPresented = isAlertPresented
_alertType = alertType
_didHandleSubreddit = didHandleSubreddit
}
@ -53,6 +56,7 @@ extension OrganizerView {
private func onAppear() {
guard didHandleSubreddit else {
alertType = .subscribeReddit
isAlertPresented = true
return
}

View File

@ -53,6 +53,8 @@ struct OrganizerView: View {
@State private var modalType: ModalType?
@State private var isAlertPresented = false
@State private var alertType: AlertType?
@State private var isHostFileImporterPresented = false
@ -81,8 +83,13 @@ struct OrganizerView: View {
}
}
}.sheet(item: $modalType, content: presentedModal)
.alert(item: $alertType, content: presentedAlert)
.fileImporter(
.alert(
Unlocalized.appName,
isPresented: $isAlertPresented,
presenting: alertType,
actions: alertActions,
message: alertMessage
).fileImporter(
isPresented: $isHostFileImporterPresented,
allowedContentTypes: hostFileTypes,
allowsMultipleSelection: false,
@ -93,6 +100,7 @@ struct OrganizerView: View {
private var hiddenSceneView: some View {
SceneView(
isAlertPresented: $isAlertPresented,
alertType: $alertType,
didHandleSubreddit: $didHandleSubreddit
)
@ -133,26 +141,27 @@ extension OrganizerView {
}
}
private func presentedAlert(_ alertType: AlertType) -> Alert {
private func alertActions(_ alertType: AlertType) -> some View {
switch alertType {
case .subscribeReddit:
return Alert(
title: Text(Unlocalized.Social.reddit),
message: Text(L10n.Organizer.Alerts.Reddit.message),
primaryButton: .default(Text(L10n.Organizer.Alerts.Reddit.Buttons.subscribe)) {
return Group {
Button(L10n.Organizer.Alerts.Reddit.Buttons.subscribe) {
didHandleSubreddit = true
URL.open(redditURL)
},
secondaryButton: .cancel(Text(L10n.Global.Alerts.Buttons.never)) {
didHandleSubreddit = true
}
)
Button(role: .cancel) {
didHandleSubreddit = true
} label: {
Text(L10n.Global.Alerts.Buttons.never)
}
}
}
}
extension OrganizerView {
private func presentSubscribeReddit() {
alertType = .subscribeReddit
private func alertMessage(_ alertType: AlertType) -> some View {
switch alertType {
case .subscribeReddit:
return Text(L10n.Organizer.Alerts.Reddit.message)
}
}
}

View File

@ -28,7 +28,7 @@ import SwiftUI
extension ProfileView {
struct MainMenu: View {
enum ActionSheetType: Int, Identifiable {
enum AlertType: Int, Identifiable {
case uninstallVPN
case deleteProfile
@ -52,14 +52,14 @@ extension ProfileView {
@Binding private var modalType: ModalType?
@State private var actionSheetType: ActionSheetType?
@State private var isAlertPresented = false
@State private var alertType: AlertType?
private let uninstallVPNTitle = L10n.Global.Strings.uninstall
private let deleteProfileTitle = L10n.Global.Strings.delete
private let cancelTitle = L10n.Global.Strings.cancel
init(currentProfile: ObservableProfile, modalType: Binding<ModalType?>) {
profileManager = .shared
vpnManager = .shared
@ -70,25 +70,13 @@ extension ProfileView {
var body: some View {
mainView
.alert(item: $actionSheetType) {
switch $0 {
case .uninstallVPN:
return Alert(
title: Text(uninstallVPNTitle),
message: Text(L10n.Profile.Alerts.UninstallVpn.message),
primaryButton: .destructive(Text(uninstallVPNTitle), action: uninstallVPN),
secondaryButton: .cancel(Text(cancelTitle))
.alert(
Text(Unlocalized.appName),
isPresented: $isAlertPresented,
presenting: alertType,
actions: alertActions,
message: alertMessage
)
case .deleteProfile:
return Alert(
title: Text(deleteProfileTitle),
message: Text(L10n.Organizer.Alerts.RemoveProfile.message(header.name)),
primaryButton: .destructive(Text(deleteProfileTitle), action: removeProfile),
secondaryButton: .cancel(Text(cancelTitle))
)
}
}
}
private var mainView: some View {
@ -113,9 +101,46 @@ extension ProfileView {
}
}
private func alertActions(_ alertType: AlertType) -> some View {
switch alertType {
case .uninstallVPN:
return Group {
Button(role: .destructive, action: uninstallVPN) {
Text(uninstallVPNTitle)
}
Button(role: .cancel) {
} label: {
Text(L10n.Global.Strings.cancel)
}
}
case .deleteProfile:
return Group {
Button(role: .destructive, action: removeProfile) {
Text(deleteProfileTitle)
}
Button(role: .cancel) {
} label: {
Text(L10n.Global.Strings.cancel)
}
}
}
}
private func alertMessage(_ alertType: AlertType) -> some View {
switch alertType {
case .uninstallVPN:
return Text(L10n.Profile.Alerts.UninstallVpn.message)
case .deleteProfile:
return Text(L10n.Organizer.Alerts.RemoveProfile.message(header.name))
}
}
private var uninstallVPNButton: some View {
Button {
actionSheetType = .uninstallVPN
alertType = .uninstallVPN
isAlertPresented = true
} label: {
Label(uninstallVPNTitle, systemImage: themeUninstallImage)
}
@ -123,7 +148,8 @@ extension ProfileView {
private var deleteProfileButton: some View {
DestructiveButton {
actionSheetType = .deleteProfile
alertType = .deleteProfile
isAlertPresented = true
} label: {
Label(deleteProfileTitle, systemImage: themeDeleteImage)
}

View File

@ -59,18 +59,29 @@ extension ProfileView {
ToolbarItem(placement: .primaryAction) {
Button(action: commitRenaming, label: themeSaveButtonLabel)
}
}.alert(isPresented: $isOverwritingExistingProfile, content: alertOverwriteExistingProfile)
}.alert(
L10n.Profile.Alerts.Rename.title,
isPresented: $isOverwritingExistingProfile,
actions: alertOverwriteActions,
message: alertOverwriteMessage
)
}
private func alertOverwriteExistingProfile() -> Alert {
Alert(
title: Text(L10n.Profile.Alerts.Rename.title),
message: Text(L10n.AddProfile.Shared.Alerts.Overwrite.message),
primaryButton: .destructive(Text(L10n.Global.Strings.ok)) {
@ViewBuilder
private func alertOverwriteActions() -> some View {
Button(role: .destructive) {
commitRenaming(force: true)
},
secondaryButton: .cancel(Text(L10n.Global.Strings.cancel))
)
} label: {
Text(L10n.Global.Strings.ok)
}
Button(role: .cancel) {
} label: {
Text(L10n.Global.Strings.cancel)
}
}
private func alertOverwriteMessage() -> some View {
Text(L10n.AddProfile.Shared.Alerts.Overwrite.message)
}
private func loadCurrentName() {

View File

@ -314,7 +314,6 @@
"donate.sections.one_time.footer" = "Wenn du dich erkenntlich zeigen möchtest für meine Arbeit, gibt es hier ein paar Beträge die du direkt spenden kannst.\n\nDu bezahlst pro Spende nur einmal und kannst mehrmals spenden wenn du möchtest.";
"donate.items.loading.caption" = "Lade Spenden";
"donate.items.purchasing.caption" = "Führe Spende durch";
"donate.alerts.purchase.success.title" = "Danke";
"donate.alerts.purchase.success.message" = "Das bedeutet mir viel und ich hoffe wirklich dass du die App weiterhin benutzt und unterstützt.";
"donate.alerts.purchase.failure.message" = "Konnte Spende nicht durchführen. %@";

View File

@ -314,7 +314,6 @@
"donate.sections.one_time.footer" = "Αν είστε χαρούμενη με τη δουλειά μου, εδώ είναι λίγα ποσά που μπορείτε να δώσετε αμέσως.\n\nΘα χρεωθείτε μόνο μία φορά και μπορείτε να δώσετε πολλές φορές.";
"donate.items.loading.caption" = "Φόρτωση δωρεών";
"donate.items.purchasing.caption" = "Εκτέλεση δωρεάς";
"donate.alerts.purchase.success.title" = "Ευχαριστώ";
"donate.alerts.purchase.success.message" = "Αυτό σημαίνει πολλά για μένα και πραγματικά ελπίζω να συνεχίσετε να χρησιμοποιείτε και να προωθείτε αυτήν την εφαρμογή.";
"donate.alerts.purchase.failure.message" = "Δεν είναι δυνατή η εκτέλεση της δωρεάς. %@";

View File

@ -314,7 +314,6 @@
"donate.sections.one_time.footer" = "If you want to display gratitude for my free work, here are a couple amounts you can donate instantly.\n\nYou will only be charged once per donation, and you can donate multiple times.";
"donate.items.loading.caption" = "Loading donations";
"donate.items.purchasing.caption" = "Performing donation";
"donate.alerts.purchase.success.title" = "Thank you";
"donate.alerts.purchase.success.message" = "This means a lot to me and I really hope you keep using and promoting this app.";
"donate.alerts.purchase.failure.message" = "Unable to perform the donation. %@";

View File

@ -314,7 +314,6 @@
"donate.sections.one_time.footer" = "Si te gusta mi trabajo, aquí puedes colaborar con una donación.\n\nSólo se te cobrará una vez por donación, y puedes donar las veces que quieras.";
"donate.items.loading.caption" = "Cargando donaciones";
"donate.items.purchasing.caption" = "Efectuando donación";
"donate.alerts.purchase.success.title" = "Muchas gracias";
"donate.alerts.purchase.success.message" = "Ésto significa mucho para mí y espero sinceramente que sigas usando y promoviendo esta aplicación.";
"donate.alerts.purchase.failure.message" = "Imposible completar la donación, por favor vuelve a intentarlo. %@";

View File

@ -314,7 +314,6 @@
"donate.sections.one_time.footer" = "Si vous voulez manifester votre gratitude envers mon travail bénévole, voici certains montants pour faire un don instantanément.\n\n Vous n'allez être chargé qu'une seule fois par don et vous pouvez faire un don plus d'une fois.";
"donate.items.loading.caption" = "Chargement des dons";
"donate.items.purchasing.caption" = "Don en cours";
"donate.alerts.purchase.success.title" = "Merci";
"donate.alerts.purchase.success.message" = "Ceci signifie beaucoup pour moi et j'espère sincèrement que vous continuerez d'utiliser et de promouvoir cette app.";
"donate.alerts.purchase.failure.message" = "Impossible de faire le don. %@";

View File

@ -314,7 +314,6 @@
"donate.sections.one_time.footer" = "Se vuoi mostrare gratitudine per il mio lavoro a titolo gratuito, qui trovi varie somme da donare all'istante.\n\nLa donazione ti sarà addebitata solo una volta, e puoi effettuare più donazioni.";
"donate.items.loading.caption" = "Caricando donazioni";
"donate.items.purchasing.caption" = "Effettuando donazione";
"donate.alerts.purchase.success.title" = "Grazie";
"donate.alerts.purchase.success.message" = "Questo significa molto per me e spero vivamente che tu continui ad usare e promuovere quest'applicazione.";
"donate.alerts.purchase.failure.message" = "Impossibile effettuare la donazione. %@";

View File

@ -314,7 +314,6 @@
"donate.sections.one_time.footer" = "Als je dankbaarheid wilt tonen voor mijn gratis werk, zijn hier een paar bedragen die je direct kunt doneren.\n\nHet bedrag wordt slechts één keer per donatie in rekening gebracht en u kunt meerdere keren doneren.";
"donate.items.loading.caption" = "Ophalen donaties";
"donate.items.purchasing.caption" = "Doneren";
"donate.alerts.purchase.success.title" = "Hartelijk dank";
"donate.alerts.purchase.success.message" = "Dit betekent veel voor mij en ik hoop echt dat je deze app blijft gebruiken en promoten.";
"donate.alerts.purchase.failure.message" = "Donatie mislukt. %@";

View File

@ -314,7 +314,6 @@
"donate.sections.one_time.footer" = "Jeśli chcesz docenić moją pracę, poniżej znajdziesz kilka kwot do wyboru dotacji.\n\nTwoje konto zostanie obciążone tylko raz na jedną dotację, możesz wysłać dotację kilka razy.";
"donate.items.loading.caption" = "Ładowanie dotacji";
"donate.items.purchasing.caption" = "Wykonywanie dotacji";
"donate.alerts.purchase.success.title" = "Dziękuję";
"donate.alerts.purchase.success.message" = "To dla mnie dużo znaczy, mam nadzięję że będziesz używać aplikacji i przyczynisz się do jej rozpowrzechnienia.";
"donate.alerts.purchase.failure.message" = "Nie można dokonać dotacji. %@";

View File

@ -314,7 +314,6 @@
"donate.sections.one_time.footer" = "Se você deseja mostrar gratidão pelo meu trabalho, aqui estão alguns valores do qual você pode contribuir.\n\nVocé só será cobrado uma única vez, ou doar mais vezes caso desejar.";
"donate.items.loading.caption" = "Carregando doações";
"donate.items.purchasing.caption" = "Efetuando doação";
"donate.alerts.purchase.success.title" = "Obrigado";
"donate.alerts.purchase.success.message" = "Isso significa muito para mim! Espero que você continue usando e promovendo esse aplicativo.";
"donate.alerts.purchase.failure.message" = "Não foi possível realizar doação. %@";

View File

@ -314,7 +314,6 @@
"donate.sections.one_time.footer" = "Если Вы хотите поблагодарить мою бесплатную работу, здесь есть несколько сумм, которые Вы можете пожертвовать прямо сейчас.\n\nСумма будет списана только один раз, а Вы можете пожертвовать несколько раз.";
"donate.items.loading.caption" = "Загружаем пожертвования";
"donate.items.purchasing.caption" = "Исполняется";
"donate.alerts.purchase.success.title" = "Спасибо";
"donate.alerts.purchase.success.message" = "Это значит многое для меня, и, я надеюсь, Вы продолжить использовать и рассказывать об этом приложении.";
"donate.alerts.purchase.failure.message" = "Не получается совершить пожертвование. %@";

View File

@ -314,7 +314,6 @@
"donate.sections.one_time.footer" = "Om du vill visa tacksamhet för mitt fria arbete, här är några belopp du kan donera direkt. \n\nDu betalas endast en gång per donation, och du kan donera flera gånger. ";
"donate.items.loading.caption" = "Laddar donationer";
"donate.items.purchasing.caption" = "Performing donation";
"donate.alerts.purchase.success.title" = "Tack";
"donate.alerts.purchase.success.message" = "Detta betyder mycket för mig och jag hoppas verkligen att du fortsätter att använda och marknadsföra denna app.";
"donate.alerts.purchase.failure.message" = "Kan inte göra donationen. %@";

View File

@ -314,7 +314,6 @@
"donate.sections.one_time.footer" = "Якщо ви бажаєте подякувати за мою безкоштовну роботу, тут є декілька сум, які ви можете пожертвувати прямо зараз.\n\nЗ вас буде стягнено плату лише один раз, але ви можете робити пожертви кілька разів.";
"donate.items.loading.caption" = "Завантажуємо пожертвування";
"donate.items.purchasing.caption" = "Виконується";
"donate.alerts.purchase.success.title" = "Дякуємо";
"donate.alerts.purchase.success.message" = "Це дуже важливо для мене, і я сподіваюся, що ви й надалі будете користуватися цією програмою та рекламувати її.";
"donate.alerts.purchase.failure.message" = "Не вдається здійснити пожертвування. %@";

View File

@ -314,7 +314,6 @@
"donate.sections.one_time.footer" = "如果你想对我免费的工作表示感谢,这里是一些你可以捐助的数额。\n\n每次捐助只需要付款一次你可以捐助多次。";
"donate.items.loading.caption" = "加载捐助中";
"donate.items.purchasing.caption" = "展示捐助页面中";
"donate.alerts.purchase.success.title" = "感谢";
"donate.alerts.purchase.success.message" = "这对于我意味着很多,希望你保持使用并使它更好。";
"donate.alerts.purchase.failure.message" = "无法展现捐助内容。%@";