banner: key by value so tip fades out on dismiss

The banner sits in a single if-let slot; without an identity key, clearing a
tip that was covering a network/offline banner swapped content in place with
no transition (instantly vanishing). Keying the banner by its value (.id)
makes every change — tip→network or tip→nil — run the shared removal
transition, so the tip slides up and fades out when it auto-dismisses or is
tapped.
This commit is contained in:
FuelBoard Contributor
2026-08-19 11:10:29 +01:00
parent cfd435d12a
commit 4c222cdf55
2 changed files with 6 additions and 2 deletions
+5 -1
View File
@@ -5,7 +5,7 @@ import WidgetKit
/// The single floating notification banner. Network/offline state and tip /// The single floating notification banner. Network/offline state and tip
/// outcomes all funnel through ONE style and render via the same chrome in /// outcomes all funnel through ONE style and render via the same chrome in
/// ContentView never pushing layout, always overlaid. /// ContentView never pushing layout, always overlaid.
enum AppBanner: Equatable { enum AppBanner: Hashable {
case offlineDump(date: String) case offlineDump(date: String)
case connectionProblem case connectionProblem
case tip(TipStore.TipOutcome) case tip(TipStore.TipOutcome)
@@ -491,6 +491,10 @@ struct ContentView: View {
// main content so it doesn't cover or block the list/pill // main content so it doesn't cover or block the list/pill
// beneath it. // beneath it.
floatingBanner(banner) floatingBanner(banner)
// Key by value so ANY change (tipnetwork, tipnil)
// runs the removal transition (fade/slide-out) instead
// of silently swapping content in place.
.id(banner)
.padding(.top, geo.safeAreaInsets.top + 10) .padding(.top, geo.safeAreaInsets.top + 10)
.transition(.asymmetric( .transition(.asymmetric(
insertion: .move(edge: .top).combined(with: .opacity), insertion: .move(edge: .top).combined(with: .opacity),
+1 -1
View File
@@ -604,7 +604,7 @@ final class TipStore: ObservableObject {
] ]
/// A banner outcome: the exact copy to show plus its icon + tint. /// A banner outcome: the exact copy to show plus its icon + tint.
struct TipOutcome: Equatable { struct TipOutcome: Hashable {
let message: String let message: String
let icon: String let icon: String
let tint: Color let tint: Color