Prevent save profile without active modules (#1164)

A further improvement over #1163 with the same purpose. That is: if a
profile won't connect, don't let the user create/save it in the first
place.
This commit is contained in:
Davide 2025-02-11 17:45:00 +01:00 committed by GitHub
parent c189668f3b
commit cc8a500dab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 51 additions and 14 deletions

View File

@ -71,6 +71,9 @@ private extension AppUIMain {
}
}
} catch {
if (error as? PassepartoutError)?.code == .incompleteModule {
return
}
fatalError("\(moduleType): empty module is not buildable: \(error)")
}
}

View File

@ -139,7 +139,7 @@ private extension ProfileCoordinator {
do {
try iapManager.verify(profileToSave, extra: profileEditor.extraFeatures)
} catch AppError.ineligibleProfile(var requiredFeatures) {
} catch AppError.ineligibleProfile(let requiredFeatures) {
guard !iapManager.isLoadingReceipt else {
let V = Strings.Views.Paywall.Alerts.Verification.self
errorHandler.handle(

View File

@ -108,7 +108,7 @@ private extension ProfileEditView {
.onMove(perform: moveModules)
.onDelete(perform: removeModules)
addModuleButton
addModuleMenu
}
.themeSection(
header: Strings.Global.Nouns.modules,
@ -137,7 +137,7 @@ private extension ProfileEditView {
}
}
var addModuleButton: some View {
var addModuleMenu: some View {
AddModuleMenu(moduleTypes: profileEditor.availableModuleTypes) {
flow?.onNewModule($0)
} label: {

View File

@ -191,6 +191,12 @@ private extension ProfileEditor {
extension ProfileEditor {
public func build() throws -> Profile {
// add this check in the app, the library does not enforce it
guard !editableProfile.activeModulesIds.isEmpty else {
throw PassepartoutError(.noActiveModules)
}
let builder = try editableProfile.builder()
let profile = try builder.tryBuild()

View File

@ -69,15 +69,22 @@ extension TaskTimeoutError: PassepartoutErrorMappable {
extension PassepartoutError: @retroactive LocalizedError {
public var errorDescription: String? {
let V = Strings.Errors.App.Passepartout.self
switch code {
case .connectionModuleRequired:
return Strings.Errors.App.Passepartout.connectionModuleRequired
return V.connectionModuleRequired
case .corruptProviderModule:
return Strings.Errors.App.Passepartout.corruptProviderModule(reason?.localizedDescription ?? "")
return V.corruptProviderModule(reason?.localizedDescription ?? "")
case .incompatibleModules:
return Strings.Errors.App.Passepartout.incompatibleModules
return V.incompatibleModules
case .incompleteModule:
guard let builder = userInfo as? any ModuleBuilder else {
break
}
return V.incompleteModule(builder.moduleType.localizedDescription)
case .invalidFields:
let fields = (userInfo as? [String: String?])
@ -88,35 +95,36 @@ extension PassepartoutError: @retroactive LocalizedError {
.joined(separator: ",")
}
return [Strings.Errors.App.Passepartout.invalidFields, fields]
return [V.invalidFields, fields]
.compactMap { $0 }
.joined(separator: " ")
case .missingProviderEntity:
return Strings.Errors.App.Passepartout.missingProviderEntity
return V.missingProviderEntity
case .noActiveModules:
return Strings.Errors.App.Passepartout.noActiveModules
return V.noActiveModules
case .parsing:
let message = userInfo as? String ?? (reason as? LocalizedError)?.localizedDescription
return [Strings.Errors.App.Passepartout.parsing, message]
return [V.parsing, message]
.compactMap { $0 }
.joined(separator: " ")
case .providerRequired:
return Strings.Errors.App.Passepartout.providerRequired
return V.providerRequired
case .timeout:
return Strings.Errors.App.Passepartout.timeout
return V.timeout
case .unhandled:
return reason?.localizedDescription
default:
return Strings.Errors.App.Passepartout.default(code.rawValue)
break
}
return V.default(code.rawValue)
}
}

View File

@ -129,6 +129,10 @@ public enum Strings {
}
/// Some active modules are incompatible, try to only activate one of them.
public static let incompatibleModules = Strings.tr("Localizable", "errors.app.passepartout.incompatible_modules", fallback: "Some active modules are incompatible, try to only activate one of them.")
/// Please finish the configuration of the %@ module.
public static func incompleteModule(_ p1: Any) -> String {
return Strings.tr("Localizable", "errors.app.passepartout.incomplete_module", String(describing: p1), fallback: "Please finish the configuration of the %@ module.")
}
/// Invalid fields.
public static let invalidFields = Strings.tr("Localizable", "errors.app.passepartout.invalid_fields", fallback: "Invalid fields.")
/// No server selected in provider.

View File

@ -31,6 +31,7 @@
"errors.app.passepartout.corrupt_provider_module" = "Verbindung zum Anbieter-Server konnte nicht hergestellt werden (Grund=%@).";
"errors.app.passepartout.default" = "Operation konnte nicht abgeschlossen werden (Code=%@).";
"errors.app.passepartout.incompatible_modules" = "Einige aktive Module sind inkompatibel, versuche, nur eines von ihnen zu aktivieren.";
"errors.app.passepartout.incomplete_module" = "Bitte schließen Sie die Konfiguration des %@-Moduls ab.";
"errors.app.passepartout.invalid_fields" = "Ungültige Felder.";
"errors.app.passepartout.missing_provider_entity" = "Kein Server beim Anbieter ausgewählt.";
"errors.app.passepartout.no_active_modules" = "Das Profil hat keine aktiven Module.";

View File

@ -31,6 +31,7 @@
"errors.app.passepartout.corrupt_provider_module" = "Δεν ήταν δυνατή η σύνδεση στον διακομιστή παρόχου (λόγος=%@).";
"errors.app.passepartout.default" = "Δεν ήταν δυνατή η ολοκλήρωση της λειτουργίας (κωδικός=%@).";
"errors.app.passepartout.incompatible_modules" = "Μερικές ενεργές μονάδες είναι ασύμβατες, δοκιμάστε να ενεργοποιήσετε μόνο μία.";
"errors.app.passepartout.incomplete_module" = "Ολοκληρώστε τη ρύθμιση του %@ module.";
"errors.app.passepartout.invalid_fields" = "Μη έγκυρα πεδία.";
"errors.app.passepartout.missing_provider_entity" = "Δεν επιλέχθηκε διακομιστής στον πάροχο.";
"errors.app.passepartout.no_active_modules" = "Το προφίλ δεν έχει ενεργές μονάδες.";

View File

@ -372,6 +372,7 @@
"errors.app.passepartout.corrupt_provider_module" = "Unable to connect to provider server (reason=%@).";
"errors.app.passepartout.default" = "Unable to complete operation (code=%@).";
"errors.app.passepartout.incompatible_modules" = "Some active modules are incompatible, try to only activate one of them.";
"errors.app.passepartout.incomplete_module" = "Please finish the configuration of the %@ module.";
"errors.app.passepartout.invalid_fields" = "Invalid fields.";
"errors.app.passepartout.missing_provider_entity" = "No server selected in provider.";
"errors.app.passepartout.no_active_modules" = "The profile has no active modules.";

View File

@ -31,6 +31,7 @@
"errors.app.passepartout.corrupt_provider_module" = "No se pudo conectar al servidor del proveedor (motivo=%@).";
"errors.app.passepartout.default" = "No se pudo completar la operación (código=%@).";
"errors.app.passepartout.incompatible_modules" = "Algunos módulos activos son incompatibles, intenta activar solo uno de ellos.";
"errors.app.passepartout.incomplete_module" = "Por favor, completa la configuración del módulo %@.";
"errors.app.passepartout.invalid_fields" = "Campos no válidos.";
"errors.app.passepartout.missing_provider_entity" = "No se seleccionó un servidor en el proveedor.";
"errors.app.passepartout.no_active_modules" = "El perfil no tiene módulos activos.";

View File

@ -31,6 +31,7 @@
"errors.app.passepartout.corrupt_provider_module" = "Impossible de se connecter au serveur du fournisseur (raison=%@).";
"errors.app.passepartout.default" = "Impossible de terminer l'opération (code=%@).";
"errors.app.passepartout.incompatible_modules" = "Certains modules actifs sont incompatibles, essayez d'en activer un seul.";
"errors.app.passepartout.incomplete_module" = "Veuillez terminer la configuration du module %@.";
"errors.app.passepartout.invalid_fields" = "Champs invalides.";
"errors.app.passepartout.missing_provider_entity" = "Aucun serveur sélectionné chez le fournisseur.";
"errors.app.passepartout.no_active_modules" = "Le profil n'a pas de modules actifs.";

View File

@ -31,6 +31,7 @@
"errors.app.passepartout.corrupt_provider_module" = "Impossibile connettersi al server del provider (motivo=%@).";
"errors.app.passepartout.default" = "Impossibile completare l'operazione (codice=%@).";
"errors.app.passepartout.incompatible_modules" = "Alcuni moduli attivi sono incompatibili, prova ad attivare solo uno di essi.";
"errors.app.passepartout.incomplete_module" = "Completa la configurazione del modulo %@.";
"errors.app.passepartout.invalid_fields" = "Campi non validi.";
"errors.app.passepartout.missing_provider_entity" = "Nessun server selezionato nel provider.";
"errors.app.passepartout.no_active_modules" = "Il profilo non ha moduli attivi.";

View File

@ -31,6 +31,7 @@
"errors.app.passepartout.corrupt_provider_module" = "Kan geen verbinding maken met de provider-server (reden=%@).";
"errors.app.passepartout.default" = "Kan bewerking niet voltooien (code=%@).";
"errors.app.passepartout.incompatible_modules" = "Sommige actieve modules zijn incompatibel. Probeer er slechts één te activeren.";
"errors.app.passepartout.incomplete_module" = "Voltooi de configuratie van de %@-module.";
"errors.app.passepartout.invalid_fields" = "Ongeldige velden.";
"errors.app.passepartout.missing_provider_entity" = "Geen server geselecteerd bij provider.";
"errors.app.passepartout.no_active_modules" = "Het profiel heeft geen actieve modules.";

View File

@ -31,6 +31,7 @@
"errors.app.passepartout.corrupt_provider_module" = "Nie można połączyć się z serwerem dostawcy (powód=%@).";
"errors.app.passepartout.default" = "Nie można ukończyć operacji (kod=%@).";
"errors.app.passepartout.incompatible_modules" = "Niektóre aktywne moduły są niekompatybilne. Spróbuj aktywować tylko jeden.";
"errors.app.passepartout.incomplete_module" = "Proszę dokończyć konfigurację modułu %@.";
"errors.app.passepartout.invalid_fields" = "Nieprawidłowe pola.";
"errors.app.passepartout.missing_provider_entity" = "Nie wybrano serwera w dostawcy.";
"errors.app.passepartout.no_active_modules" = "Profil nie ma aktywnych modułów.";

View File

@ -31,6 +31,7 @@
"errors.app.passepartout.corrupt_provider_module" = "Não foi possível conectar ao servidor do provedor (motivo=%@).";
"errors.app.passepartout.default" = "Não foi possível concluir a operação (código=%@).";
"errors.app.passepartout.incompatible_modules" = "Alguns módulos ativos são incompatíveis, tente ativar apenas um.";
"errors.app.passepartout.incomplete_module" = "Conclua a configuração do módulo %@.";
"errors.app.passepartout.invalid_fields" = "Campos inválidos.";
"errors.app.passepartout.missing_provider_entity" = "Nenhum servidor selecionado no provedor.";
"errors.app.passepartout.no_active_modules" = "O perfil não possui módulos ativos.";

View File

@ -31,6 +31,7 @@
"errors.app.passepartout.corrupt_provider_module" = "Не удалось подключиться к серверу поставщика (причина=%@).";
"errors.app.passepartout.default" = "Не удалось выполнить операцию (код=%@).";
"errors.app.passepartout.incompatible_modules" = "Некоторые активные модули несовместимы, попробуйте активировать только один.";
"errors.app.passepartout.incomplete_module" = "Пожалуйста, завершите настройку модуля %@.";
"errors.app.passepartout.invalid_fields" = "Некорректные поля.";
"errors.app.passepartout.missing_provider_entity" = "На провайдере не выбран сервер.";
"errors.app.passepartout.no_active_modules" = "В профиле нет активных модулей.";

View File

@ -31,6 +31,7 @@
"errors.app.passepartout.corrupt_provider_module" = "Kan inte ansluta till leverantörsservern (anledning=%@).";
"errors.app.passepartout.default" = "Kan inte slutföra åtgärden (kod=%@).";
"errors.app.passepartout.incompatible_modules" = "Vissa aktiva moduler är inkompatibla, försök att endast aktivera en.";
"errors.app.passepartout.incomplete_module" = "Vänligen slutför konfigurationen av %@-modulen.";
"errors.app.passepartout.invalid_fields" = "Ogiltiga fält.";
"errors.app.passepartout.missing_provider_entity" = "Ingen server vald hos leverantören.";
"errors.app.passepartout.no_active_modules" = "Profilen har inga aktiva moduler.";

View File

@ -31,6 +31,7 @@
"errors.app.passepartout.corrupt_provider_module" = "Не вдалося підключитися до сервера постачальника (причина=%@).";
"errors.app.passepartout.default" = "Не вдалося виконати операцію (код=%@).";
"errors.app.passepartout.incompatible_modules" = "Деякі активні модулі несумісні, спробуйте активувати лише один.";
"errors.app.passepartout.incomplete_module" = "Будь ласка, завершіть налаштування модуля %@.";
"errors.app.passepartout.invalid_fields" = "Некоректні поля.";
"errors.app.passepartout.missing_provider_entity" = "Не вибрано сервер у постачальника.";
"errors.app.passepartout.no_active_modules" = "Профіль не має активних модулів.";

View File

@ -31,6 +31,7 @@
"errors.app.passepartout.corrupt_provider_module" = "无法连接到提供商服务器(原因=%@)。";
"errors.app.passepartout.default" = "无法完成操作(代码=%@)。";
"errors.app.passepartout.incompatible_modules" = "某些激活的模块不兼容,请尝试仅激活一个模块。";
"errors.app.passepartout.incomplete_module" = "请完成 %@ 模块的配置。";
"errors.app.passepartout.invalid_fields" = "字段无效。";
"errors.app.passepartout.missing_provider_entity" = "未在提供商中选择服务器。";
"errors.app.passepartout.no_active_modules" = "配置文件没有激活模块。";

View File

@ -72,6 +72,9 @@ private extension UILibrary {
fatalError("\(moduleType): #2 is not AppFeatureRequiring")
}
} catch {
if (error as? PassepartoutError)?.code == .incompleteModule {
return
}
fatalError("\(moduleType): empty module is not buildable: \(error)")
}
}

@ -1 +1 @@
Subproject commit c3101f02e620d4ff986a8b846c0ea35fd62d63ec
Subproject commit 191369cb4a9a34acac747f2fffd19ea470f8f79e