Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent save profile without active modules #1164

Merged
merged 8 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Packages/App/Sources/AppUIMain/AppUIMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ private extension AppUIMain {
}
}
} catch {
if (error as? PassepartoutError)?.code == .incompleteModule {
return
}
fatalError("\(moduleType): empty module is not buildable: \(error)")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ private extension ProfileEditView {
.onMove(perform: moveModules)
.onDelete(perform: removeModules)

addModuleButton
addModuleMenu
}
.themeSection(
header: Strings.Global.Nouns.modules,
Expand Down Expand Up @@ -137,7 +137,7 @@ private extension ProfileEditView {
}
}

var addModuleButton: some View {
var addModuleMenu: some View {
AddModuleMenu(moduleTypes: profileEditor.availableModuleTypes) {
flow?.onNewModule($0)
} label: {
Expand Down
6 changes: 6 additions & 0 deletions Packages/App/Sources/UILibrary/Business/ProfileEditor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
28 changes: 18 additions & 10 deletions Packages/App/Sources/UILibrary/L10n/AppError+L10n.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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?])
Expand All @@ -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)
}
}

Expand Down
4 changes: 4 additions & 0 deletions Packages/App/Sources/UILibrary/L10n/SwiftGen+Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" = "Το προφίλ δεν έχει ενεργές μονάδες.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" = "В профиле нет активных модулей.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" = "Профіль не має активних модулів.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" = "配置文件没有激活模块。";
Expand Down
3 changes: 3 additions & 0 deletions Packages/App/Sources/UILibrary/UILibrary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
}
}
Expand Down
2 changes: 1 addition & 1 deletion Packages/PassepartoutKit-Source