From cbcc7549e4996ba3e7e48d96bb846b15cdbf25ce Mon Sep 17 00:00:00 2001 From: Theo Sementa Date: Wed, 1 Apr 2026 19:53:41 +0200 Subject: [PATCH 1/5] feat: create toast banner --- .../ToastBannerUIModel+Extensions.swift | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/Modules/DesignSystem/Sources/Extensions/UIModels/ToastBannerUIModel+Extensions.swift b/Modules/DesignSystem/Sources/Extensions/UIModels/ToastBannerUIModel+Extensions.swift index 6ee6ddf..4b4906c 100644 --- a/Modules/DesignSystem/Sources/Extensions/UIModels/ToastBannerUIModel+Extensions.swift +++ b/Modules/DesignSystem/Sources/Extensions/UIModels/ToastBannerUIModel+Extensions.swift @@ -10,16 +10,15 @@ import ToastBannerKit import UIKit // MARK: - Errors +@MainActor public extension ToastBannerUIModel { - @MainActor static let errorNameMandatory: ToastBannerUIModel = .init( title: "toast_banner_name_mandatory".localized, uiImage: UIImage(asset: .iconWarning), style: ToastBannerStyle.error ) - @MainActor static let errorAmountMandatory: ToastBannerUIModel = .init( title: "toast_banner_amount_mandatory".localized, uiImage: UIImage(asset: .iconWarning), @@ -27,3 +26,45 @@ public extension ToastBannerUIModel { ) } + +// MARK: - Success +@MainActor +public extension ToastBannerUIModel { + + static let successGoalCreated: ToastBannerUIModel = .init( + title: "toast_banner_goal_created".localized, + uiImage: UIImage(asset: .iconCheckmarkRounded), + style: ToastBannerStyle.success + ) + + static let successGoalUpdated: ToastBannerUIModel = .init( + title: "toast_banner_goal_updated".localized, + uiImage: UIImage(asset: .iconCheckmarkRounded), + style: ToastBannerStyle.success + ) + + static let successGoalDeleted: ToastBannerUIModel = .init( + title: "toast_banner_goal_deleted".localized, + uiImage: UIImage(asset: .iconCheckmarkRounded), + style: ToastBannerStyle.success + ) + + static let successContributionAdded: ToastBannerUIModel = .init( + title: "toast_banner_contribution_added".localized, + uiImage: UIImage(asset: .iconCheckmarkRounded), + style: ToastBannerStyle.success + ) + + static let successContributionUpdated: ToastBannerUIModel = .init( + title: "toast_banner_contribution_updated".localized, + uiImage: UIImage(asset: .iconCheckmarkRounded), + style: ToastBannerStyle.success + ) + + static let successContributionDeleted: ToastBannerUIModel = .init( + title: "toast_banner_contribution_deleted".localized, + uiImage: UIImage(asset: .iconCheckmarkRounded), + style: ToastBannerStyle.success + ) + +} From 8408649ca47104645001a979adcb03ddcd753b61 Mon Sep 17 00:00:00 2001 From: Theo Sementa Date: Thu, 2 Apr 2026 18:20:59 +0200 Subject: [PATCH 2/5] feat: implement toast banner --- Modules/DesignSystem/Package.swift | 2 +- .../Composants/Rows/ContributionRowView.swift | 2 ++ .../Composants/Rows/FinancialGoalRowView.swift | 2 ++ .../Add/AddContributionScreen+ViewModel.swift | 2 ++ .../Add/AddFinancialGoalScreen+ViewModel.swift | 2 ++ Modules/Models/Sources/AppConstant.swift | 18 ++++++++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 6 +++--- Modulo/RootScreen.swift | 11 +++++++++++ 8 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 Modules/Models/Sources/AppConstant.swift diff --git a/Modules/DesignSystem/Package.swift b/Modules/DesignSystem/Package.swift index d8c9dc4..fc55b6d 100644 --- a/Modules/DesignSystem/Package.swift +++ b/Modules/DesignSystem/Package.swift @@ -18,7 +18,7 @@ let package = Package( .package(name: "Navigation", path: "../Navigation"), .package(name: "Stores", path: "../Stores"), - .package(url: "https://github.com/theosementa/ToastBannerKit.git", exact: "1.0.3") + .package(url: "https://github.com/theosementa/ToastBannerKit.git", exact: "1.0.31") ], targets: [ .target( diff --git a/Modules/DesignSystem/Sources/Composants/Rows/ContributionRowView.swift b/Modules/DesignSystem/Sources/Composants/Rows/ContributionRowView.swift index 3e72a27..e060014 100644 --- a/Modules/DesignSystem/Sources/Composants/Rows/ContributionRowView.swift +++ b/Modules/DesignSystem/Sources/Composants/Rows/ContributionRowView.swift @@ -9,6 +9,7 @@ import SwiftUI import Models import Stores import Navigation +import ToastBannerKit public struct ContributionRowView: View { @@ -54,6 +55,7 @@ public struct ContributionRowView: View { .contentShape(.contextMenuPreview, RoundedRectangle(cornerRadius: .medium, style: .continuous)) .confirmationAlert(.deletion, isPresented: $isAlertPresented) { DefaultContributionStore.shared.delete(by: item.id) + ToastBannerService.shared.send(.successContributionDeleted) } .contextMenu { Button { diff --git a/Modules/DesignSystem/Sources/Composants/Rows/FinancialGoalRowView.swift b/Modules/DesignSystem/Sources/Composants/Rows/FinancialGoalRowView.swift index a569a2d..7f23dd2 100644 --- a/Modules/DesignSystem/Sources/Composants/Rows/FinancialGoalRowView.swift +++ b/Modules/DesignSystem/Sources/Composants/Rows/FinancialGoalRowView.swift @@ -9,6 +9,7 @@ import SwiftUI import Models import Stores import Navigation +import ToastBannerKit public struct FinancialGoalRowView: View { @@ -56,6 +57,7 @@ public struct FinancialGoalRowView: View { .contentShape(.contextMenuPreview, .rect(cornerRadius: .large)) .confirmationAlert(.deletion, isPresented: $isAlertPresented) { DefaultFinancialGoalStore.shared.delete(by: item.id) + ToastBannerService.shared.send(.successGoalDeleted) } .contextMenu { Button { diff --git a/Modules/Features/Sources/Contribution/Screens/Add/AddContributionScreen+ViewModel.swift b/Modules/Features/Sources/Contribution/Screens/Add/AddContributionScreen+ViewModel.swift index 1d41171..333045c 100644 --- a/Modules/Features/Sources/Contribution/Screens/Add/AddContributionScreen+ViewModel.swift +++ b/Modules/Features/Sources/Contribution/Screens/Add/AddContributionScreen+ViewModel.swift @@ -116,6 +116,7 @@ private extension AddContributionScreen.ViewModel { contributionStore.create(contribution: contribution) router?.dismiss() + toastBannerService.send(.successContributionAdded, delay: AppConstant.Animation.toastDelayAfterCloseSheet) } func update() { @@ -132,6 +133,7 @@ private extension AddContributionScreen.ViewModel { contributionStore.update(contribution: contribution) router?.dismiss() + toastBannerService.send(.successContributionUpdated, delay: AppConstant.Animation.toastDelayAfterCloseSheet) } } diff --git a/Modules/Features/Sources/FinancialGoal/Screens/Add/AddFinancialGoalScreen+ViewModel.swift b/Modules/Features/Sources/FinancialGoal/Screens/Add/AddFinancialGoalScreen+ViewModel.swift index 5546125..ff7d02b 100644 --- a/Modules/Features/Sources/FinancialGoal/Screens/Add/AddFinancialGoalScreen+ViewModel.swift +++ b/Modules/Features/Sources/FinancialGoal/Screens/Add/AddFinancialGoalScreen+ViewModel.swift @@ -118,6 +118,7 @@ private extension AddFinancialGoalScreen.ViewModel { ) store.create(goal: domain) router?.dismiss() + toastBannerService.send(.successGoalCreated, delay: AppConstant.Animation.toastDelayAfterCloseSheet) } func update() { @@ -132,6 +133,7 @@ private extension AddFinancialGoalScreen.ViewModel { ) store.update(goal: domain) router?.dismiss() + toastBannerService.send(.successGoalUpdated, delay: AppConstant.Animation.toastDelayAfterCloseSheet) } func randomPlaceholder() -> String { diff --git a/Modules/Models/Sources/AppConstant.swift b/Modules/Models/Sources/AppConstant.swift new file mode 100644 index 0000000..4f993c1 --- /dev/null +++ b/Modules/Models/Sources/AppConstant.swift @@ -0,0 +1,18 @@ +// +// File.swift +// Models +// +// Created by Theo Sementa on 02/04/2026. +// + +import Foundation + +public struct AppConstant { + + public struct Animation { + + public static let toastDelayAfterCloseSheet = 0.6 + + } + +} diff --git a/Modulo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Modulo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c5fecf8..9a5ee3c 100644 --- a/Modulo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Modulo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "53ca7d4ed651192831e811b520368b47dc06c7a9694523d7f2e810f1be642dd4", + "originHash" : "25e88206344d3de9fbbbcc66db349ee64aae019fe2784e886b5a22c6c2e13fdc", "pins" : [ { "identity" : "mcemojipicker", @@ -24,8 +24,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/theosementa/ToastBannerKit.git", "state" : { - "revision" : "210aaac66093420588a1801180c2917822355886", - "version" : "1.0.3" + "revision" : "e23b17c0790ab0a8ea905d459e2948cc6b0932e4", + "version" : "1.0.31" } } ], diff --git a/Modulo/RootScreen.swift b/Modulo/RootScreen.swift index fe3cd3a..4685d6b 100644 --- a/Modulo/RootScreen.swift +++ b/Modulo/RootScreen.swift @@ -8,9 +8,14 @@ import SwiftUI import Navigation import FinancialGoal +import ToastBannerKit +import DesignSystem struct RootScreen: View { + // MARK: States + @State private var toastBannerService: ToastBannerService = .shared + // MARK: Constants private let router: Router = .init() private let routerManager: AppRouterManager = .shared @@ -24,6 +29,12 @@ struct RootScreen: View { ) { FinancialGoalListScreen() } + .toastBanner( + item: $toastBannerService.toastBanner, + config: .init(yOffset: 10, animation: .smooth), + ) { toastBanner in + ToastBannerView(banner: toastBanner) + } } } From b2009861861dcfb18d2d9fd123bb955a9555b964 Mon Sep 17 00:00:00 2001 From: Theo Sementa Date: Thu, 2 Apr 2026 18:43:42 +0200 Subject: [PATCH 3/5] feat: add observer of events --- .../List/FinancialGoalListScreen.swift | 4 +++ Modulo/App.swift | 6 +++-- Modulo/AppDelegate.swift | 22 +++++++++++++++ Modulo/ModuloWindow.swift | 24 +++++++++++++++++ Modulo/SceneDelegate.swift | 27 +++++++++++++++++++ 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 Modulo/AppDelegate.swift create mode 100644 Modulo/ModuloWindow.swift create mode 100644 Modulo/SceneDelegate.swift diff --git a/Modules/Features/Sources/FinancialGoal/Screens/List/FinancialGoalListScreen.swift b/Modules/Features/Sources/FinancialGoal/Screens/List/FinancialGoalListScreen.swift index 376a8bb..f8aca27 100644 --- a/Modules/Features/Sources/FinancialGoal/Screens/List/FinancialGoalListScreen.swift +++ b/Modules/Features/Sources/FinancialGoal/Screens/List/FinancialGoalListScreen.swift @@ -12,9 +12,13 @@ import Core public struct FinancialGoalListScreen: View { + // MARK: States @State private var viewModel: ViewModel = .init() + + // MARK: Environments @Environment(\.theme) private var theme + // MARK: Init public init() { } // MARK: - View diff --git a/Modulo/App.swift b/Modulo/App.swift index 39e2dd6..84d8483 100644 --- a/Modulo/App.swift +++ b/Modulo/App.swift @@ -13,13 +13,15 @@ import Settings @main struct ModuloApp: App { - + + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + init() { NavigationRegistry.shared.registerFinancialGoalsRoutes() NavigationRegistry.shared.registerContributionRoutes() NavigationRegistry.shared.registerSettingsRoutes() } - + var body: some Scene { WindowGroup { RootScreen() diff --git a/Modulo/AppDelegate.swift b/Modulo/AppDelegate.swift new file mode 100644 index 0000000..200133a --- /dev/null +++ b/Modulo/AppDelegate.swift @@ -0,0 +1,22 @@ +// +// AppDelegate.swift +// Modulo +// +// Created by Theo Sementa on 31/03/2026. +// + +import UIKit + +final class AppDelegate: NSObject, UIApplicationDelegate { + + func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + let config = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role) + config.delegateClass = SceneDelegate.self + return config + } + +} diff --git a/Modulo/ModuloWindow.swift b/Modulo/ModuloWindow.swift new file mode 100644 index 0000000..66347c9 --- /dev/null +++ b/Modulo/ModuloWindow.swift @@ -0,0 +1,24 @@ +// +// ModuloWindow.swift +// Modulo +// +// Created by Theo Sementa on 31/03/2026. +// + +import UIKit +import ToastBannerKit + +final class ModuloWindow: UIWindow { + + override func sendEvent(_ event: UIEvent) { + if event.type == .touches, + let touch = event.allTouches?.first, + touch.phase == .began { + if ToastBannerService.shared.toastBanner != nil { + ToastBannerService.shared.toastBanner = nil + } + } + super.sendEvent(event) + } + +} diff --git a/Modulo/SceneDelegate.swift b/Modulo/SceneDelegate.swift new file mode 100644 index 0000000..a826b95 --- /dev/null +++ b/Modulo/SceneDelegate.swift @@ -0,0 +1,27 @@ +// +// SceneDelegate.swift +// Modulo +// +// Created by Theo Sementa on 31/03/2026. +// + +import UIKit +import SwiftUI + +final class SceneDelegate: NSObject, UIWindowSceneDelegate { + + var window: UIWindow? + + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options: UIScene.ConnectionOptions + ) { + guard let windowScene = scene as? UIWindowScene else { return } + let window = ModuloWindow(windowScene: windowScene) + window.rootViewController = UIHostingController(rootView: RootScreen()) + window.makeKeyAndVisible() + self.window = window + } + +} From 217e4b420acb977d193961667724adf1b58a6031 Mon Sep 17 00:00:00 2001 From: Theo Sementa Date: Thu, 2 Apr 2026 18:45:20 +0200 Subject: [PATCH 4/5] feat: add localizations for toast banner --- .../Localizable/en.lproj/Localizable.strings | 18 ++++++++++++++++++ .../Localizable/fr.lproj/Localizable.strings | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/Modulo/Resources/Localizable/en.lproj/Localizable.strings b/Modulo/Resources/Localizable/en.lproj/Localizable.strings index 30a5f92..f14ba26 100644 --- a/Modulo/Resources/Localizable/en.lproj/Localizable.strings +++ b/Modulo/Resources/Localizable/en.lproj/Localizable.strings @@ -163,6 +163,24 @@ /* */ "toast_banner_amount_mandatory" = "The amount is required"; +/* */ +"toast_banner_contribution_added" = "Contribution added!"; + +/* */ +"toast_banner_contribution_deleted" = "Contribution deleted!"; + +/* */ +"toast_banner_contribution_updated" = "Contribution updated!"; + +/* */ +"toast_banner_goal_created" = "Goal created!"; + +/* */ +"toast_banner_goal_deleted" = "Goal deleted!"; + +/* */ +"toast_banner_goal_updated" = "Goal updated!"; + /* */ "toast_banner_name_mandatory" = "The name is required"; diff --git a/Modulo/Resources/Localizable/fr.lproj/Localizable.strings b/Modulo/Resources/Localizable/fr.lproj/Localizable.strings index 7207a0f..9442b2f 100644 --- a/Modulo/Resources/Localizable/fr.lproj/Localizable.strings +++ b/Modulo/Resources/Localizable/fr.lproj/Localizable.strings @@ -163,6 +163,24 @@ /* */ "toast_banner_amount_mandatory" = "Le montant est obligatoire"; +/* */ +"toast_banner_contribution_added" = "Contribution ajoutée !"; + +/* */ +"toast_banner_contribution_deleted" = "Contribution supprimée !"; + +/* */ +"toast_banner_contribution_updated" = "Contribution mis à jour !"; + +/* */ +"toast_banner_goal_created" = "Objectif créé !"; + +/* */ +"toast_banner_goal_deleted" = "Objectif supprimé !"; + +/* */ +"toast_banner_goal_updated" = "Objectif mis à jour !"; + /* */ "toast_banner_name_mandatory" = "Le nom est obligatoire"; From ee86795ed92f411a7cd8252526c18fcda199fc64 Mon Sep 17 00:00:00 2001 From: Theo Sementa Date: Thu, 2 Apr 2026 19:04:42 +0200 Subject: [PATCH 5/5] feat: update toast banner style --- .../Sources/Composants/Generic/ToastBannerView.swift | 4 ++-- .../Sources/Extensions/SwiftUI/Color+Extensions.swift | 4 ++-- .../Extensions/UIModels/ToastBannerUIModel+Extensions.swift | 6 ++++++ .../Sources/Settings/Screens/SettingsScreen+ViewModel.swift | 2 ++ Modulo/Resources/Localizable/en.lproj/Localizable.strings | 3 +++ Modulo/Resources/Localizable/fr.lproj/Localizable.strings | 3 +++ 6 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Modules/DesignSystem/Sources/Composants/Generic/ToastBannerView.swift b/Modules/DesignSystem/Sources/Composants/Generic/ToastBannerView.swift index 01c40b4..dabe03b 100644 --- a/Modules/DesignSystem/Sources/Composants/Generic/ToastBannerView.swift +++ b/Modules/DesignSystem/Sources/Composants/Generic/ToastBannerView.swift @@ -32,11 +32,11 @@ public struct ToastBannerView: View { .resizable() .renderingMode(.template) .foregroundStyle(style.foregroundColor) - .frame(width: .standard, height: .standard) + .frame(width: .mediumLarge, height: .mediumLarge) } Text(banner.title) - .font(.Body.smallRegular, color: style.foregroundColor) + .font(.Body.mediumMedium, color: style.foregroundColor) .lineLimit(1) } .padding(.small) diff --git a/Modules/DesignSystem/Sources/Extensions/SwiftUI/Color+Extensions.swift b/Modules/DesignSystem/Sources/Extensions/SwiftUI/Color+Extensions.swift index 8315272..6ca1835 100644 --- a/Modules/DesignSystem/Sources/Extensions/SwiftUI/Color+Extensions.swift +++ b/Modules/DesignSystem/Sources/Extensions/SwiftUI/Color+Extensions.swift @@ -61,12 +61,12 @@ public extension Color { struct Success { public static let s500: Color = Color.green - public static let s100: Color = Color.green.opacity(0.15) + public static let s100: Color = Color(hex: 0xC2EECD) } struct Error { public static let e500: Color = Color.red - public static let e100: Color = Color.red.opacity(0.15) + public static let e100: Color = Color(hex: 0xFFC3C5) } } diff --git a/Modules/DesignSystem/Sources/Extensions/UIModels/ToastBannerUIModel+Extensions.swift b/Modules/DesignSystem/Sources/Extensions/UIModels/ToastBannerUIModel+Extensions.swift index 4b4906c..2b711ad 100644 --- a/Modules/DesignSystem/Sources/Extensions/UIModels/ToastBannerUIModel+Extensions.swift +++ b/Modules/DesignSystem/Sources/Extensions/UIModels/ToastBannerUIModel+Extensions.swift @@ -67,4 +67,10 @@ public extension ToastBannerUIModel { style: ToastBannerStyle.success ) + static let successDeleteAllData: ToastBannerUIModel = .init( + title: "toast_banner_delete_all".localized, + uiImage: UIImage(asset: .iconCheckmarkRounded), + style: ToastBannerStyle.success + ) + } diff --git a/Modules/Features/Sources/Settings/Screens/SettingsScreen+ViewModel.swift b/Modules/Features/Sources/Settings/Screens/SettingsScreen+ViewModel.swift index 4e1468b..ddbe53e 100644 --- a/Modules/Features/Sources/Settings/Screens/SettingsScreen+ViewModel.swift +++ b/Modules/Features/Sources/Settings/Screens/SettingsScreen+ViewModel.swift @@ -9,6 +9,7 @@ import Foundation import Core import Models import Stores +import ToastBannerKit extension SettingsScreen { @@ -40,6 +41,7 @@ extension SettingsScreen.ViewModel { @MainActor func deleteAll() { DefaultFinancialGoalStore.shared.deleteAll() + ToastBannerService.shared.send(.successDeleteAllData) } } diff --git a/Modulo/Resources/Localizable/en.lproj/Localizable.strings b/Modulo/Resources/Localizable/en.lproj/Localizable.strings index f14ba26..1ff7802 100644 --- a/Modulo/Resources/Localizable/en.lproj/Localizable.strings +++ b/Modulo/Resources/Localizable/en.lproj/Localizable.strings @@ -175,6 +175,9 @@ /* */ "toast_banner_goal_created" = "Goal created!"; +/* */ +"toast_banner_delete_all" = "All data has been deleted!"; + /* */ "toast_banner_goal_deleted" = "Goal deleted!"; diff --git a/Modulo/Resources/Localizable/fr.lproj/Localizable.strings b/Modulo/Resources/Localizable/fr.lproj/Localizable.strings index 9442b2f..2c0a7f0 100644 --- a/Modulo/Resources/Localizable/fr.lproj/Localizable.strings +++ b/Modulo/Resources/Localizable/fr.lproj/Localizable.strings @@ -172,6 +172,9 @@ /* */ "toast_banner_contribution_updated" = "Contribution mis à jour !"; +/* */ +"toast_banner_delete_all" = "Toutes les données ont été supprimées !"; + /* */ "toast_banner_goal_created" = "Objectif créé !";