11import 'package:bb_mobile/core/themes/app_theme.dart' ;
22import 'package:bb_mobile/core/utils/build_context_x.dart' ;
3+ import 'package:bb_mobile/core/widgets/buttons/button.dart' ;
34import 'package:bb_mobile/core/widgets/navbar/top_bar.dart' ;
45import 'package:bb_mobile/core/widgets/settings_entry_item.dart' ;
6+ import 'package:bb_mobile/core/widgets/text/text.dart' ;
7+ import 'package:bb_mobile/features/backup_settings/domain/backup_health_reminder.dart' ;
58import 'package:bb_mobile/features/backup_settings/presentation/backup_settings_failure_l10n.dart' ;
69import 'package:bb_mobile/features/backup_settings/presentation/cubit/backup_settings_cubit.dart' ;
710import 'package:bb_mobile/features/backup_settings/ui/backup_settings_router.dart' ;
811import 'package:bb_mobile/features/backup_settings/ui/widgets/view_vault_key_warning_bottom_sheet.dart' ;
912import 'package:bb_mobile/features/labels/labels_facade.dart' ;
13+ import 'package:bb_mobile/features/test_wallet_backup/public/test_wallet_backup_routes.dart' ;
1014import 'package:bb_mobile/features/transactions/ui/transactions_router.dart' ;
1115import 'package:bb_mobile/features/recoverbull/presentation/bloc.dart' ;
1216import 'package:bb_mobile/features/recoverbull/router.dart' ;
@@ -16,6 +20,7 @@ import 'package:flutter/material.dart';
1620import 'package:flutter_bloc/flutter_bloc.dart' ;
1721import 'package:gap/gap.dart' ;
1822import 'package:go_router/go_router.dart' ;
23+ import 'package:timeago/timeago.dart' as timeago;
1924
2025class BackupSettingsScreen extends StatefulWidget {
2126 const BackupSettingsScreen ({super .key});
@@ -34,6 +39,12 @@ class _BackupSettingsScreenState extends State<BackupSettingsScreen> {
3439 }
3540}
3641
42+ /// Answers one question, top to bottom: if this phone vanished right now,
43+ /// could you get your money back?
44+ ///
45+ /// The status rows state the facts, the hero names the single most useful
46+ /// action, and the menu holds everything else. Each section is built by its
47+ /// own function so a row can be added to one without disturbing the others.
3748class _Screen extends StatelessWidget {
3849 const _Screen ();
3950
@@ -49,6 +60,7 @@ class _Screen extends StatelessWidget {
4960 },
5061 child: BlocBuilder <BackupSettingsCubit , BackupSettingsState >(
5162 builder: (context, state) {
63+ final hero = _hero (state);
5264 return Scaffold (
5365 appBar: AppBar (
5466 forceMaterialTransparency: true ,
@@ -65,20 +77,16 @@ class _Screen extends StatelessWidget {
6577 child: Column (
6678 crossAxisAlignment: .start,
6779 children: [
68- const Padding (
69- padding: EdgeInsets .symmetric (horizontal: 16 ),
70- child: _BackupTestStatusWidget (),
80+ Padding (
81+ padding: const EdgeInsets .symmetric (horizontal: 16 ),
82+ child: Column (
83+ crossAxisAlignment: .start,
84+ children: _statusRows (context, state),
85+ ),
7186 ),
72- const Gap (40 ),
73- const _StartBackupButton (),
74- if (state.lastEncryptedBackup != null )
75- const _ViewVaultKeyButton (),
76- if (state.lastEncryptedBackup != null ||
77- state.lastPhysicalBackup != null )
78- const _TestBackupButton (),
79- const _RecoverBullSettingsButton (),
80- const _Bip329LabelsButton (),
81- const _TransactionHistoryButton (),
87+ if (hero != null ) ...[const Gap (32 ), hero],
88+ const Gap (24 ),
89+ ..._menuRows (state),
8290 ],
8391 ),
8492 ),
@@ -89,89 +97,243 @@ class _Screen extends StatelessWidget {
8997 ),
9098 );
9199 }
100+
101+ /// The glanceable facts. Insertion point for the fork's metadata backup
102+ /// status row: append one [_StatusRow] here.
103+ List <Widget > _statusRows (BuildContext context, BackupSettingsState state) => [
104+ _StatusRow (
105+ label: context.loc.backupSettingsPhysicalBackup,
106+ isTested: state.isDefaultPhysicalBackupTested,
107+ lastTestedAt: state.lastPhysicalBackup,
108+ ),
109+ const Gap (15 ),
110+ _StatusRow (
111+ label: context.loc.backupSettingsEncryptedVault,
112+ isTested: state.isDefaultEncryptedBackupTested,
113+ ),
114+ ];
115+
116+ /// The single most important action right now, or nothing at all when the
117+ /// status rows already say everything worth saying.
118+ Widget ? _hero (BackupSettingsState state) {
119+ // Every wallet looks unprotected until the first load resolves; an urgent
120+ // card must never flash on the way in.
121+ if (state.status != BackupSettingsStatus .success) return null ;
122+
123+ final posture = BackupHealthPosture .of (
124+ isEncryptedVaultTested: state.isDefaultEncryptedBackupTested,
125+ isPhysicalBackupTested: state.isDefaultPhysicalBackupTested,
126+ );
127+ switch (posture) {
128+ case null :
129+ return const _ZeroBackupHero ();
130+ case BackupHealthPosture .recoverbullOnly:
131+ return const _AddPhysicalBackupHero ();
132+ case BackupHealthPosture .physicalOnly:
133+ case BackupHealthPosture .both:
134+ final lastTestedAt = state.lastPhysicalBackup;
135+ final isDue = isBackupReminderDue (
136+ anchor: lastTestedAt,
137+ now: DateTime .now (),
138+ interval: posture.reminderInterval,
139+ );
140+ return isDue ? _TestBackupHero (lastTestedAt: lastTestedAt) : null ;
141+ }
142+ }
143+
144+ /// The settings rows. Insertion point for the fork's metadata backup menu
145+ /// row: add one [SettingsEntryItem] to this list.
146+ List <Widget > _menuRows (BackupSettingsState state) => [
147+ if (state.lastEncryptedBackup != null ) const _ViewVaultKeyButton (),
148+ if (state.lastEncryptedBackup != null || state.lastPhysicalBackup != null )
149+ const _TestBackupButton (),
150+ const _EncryptedVaultSettingsButton (),
151+ const _Bip329LabelsButton (),
152+ const _TransactionHistoryButton (),
153+ ];
92154}
93155
94- class _BackupTestStatusWidget extends StatelessWidget {
95- const _BackupTestStatusWidget ();
156+ class _StatusRow extends StatelessWidget {
157+ final String label;
158+ final bool isTested;
159+ final DateTime ? lastTestedAt;
160+
161+ const _StatusRow ({
162+ required this .label,
163+ required this .isTested,
164+ this .lastTestedAt,
165+ });
96166
97167 @override
98168 Widget build (BuildContext context) {
99- return BlocBuilder <BackupSettingsCubit , BackupSettingsState >(
100- builder: (context, state) {
101- return Column (
102- crossAxisAlignment: .start,
169+ final testedAt = lastTestedAt;
170+ return Column (
171+ crossAxisAlignment: .start,
172+ children: [
173+ Row (
103174 children: [
104- _StatusRow (
105- label: context.loc.backupSettingsPhysicalBackup,
106- isTested: state.isDefaultPhysicalBackupTested,
107- ),
108- const Gap (15 ),
109- _StatusRow (
110- label: context.loc.backupSettingsEncryptedVault,
111- isTested: state.isDefaultEncryptedBackupTested,
175+ Text (label, style: context.font.bodyMedium),
176+ const Spacer (),
177+ Text (
178+ isTested
179+ ? context.loc.backupSettingsTested
180+ : context.loc.backupSettingsNotTested,
181+ style: context.font.bodyMedium? .copyWith (
182+ color: isTested
183+ ? context.appColors.success
184+ : context.appColors.error,
185+ ),
112186 ),
113187 ],
114- );
115- },
188+ ),
189+ if (isTested && testedAt != null )
190+ Text (
191+ context.loc.backupHealthLastTested (timeago.format (testedAt)),
192+ style: context.font.bodySmall? .copyWith (
193+ color: context.appColors.textMuted,
194+ ),
195+ ),
196+ ],
116197 );
117198 }
118199}
119200
120- class _StatusRow extends StatelessWidget {
121- final String label;
122- final bool isTested;
201+ /// One card, one verb. Shared by every hero so the screen can only ever ask
202+ /// for one thing at a time.
203+ class _HeroCard extends StatelessWidget {
204+ final String title;
205+ final String body;
206+ final String ? footnote;
207+ final String actionLabel;
208+ final VoidCallback onAction;
209+ final bool isUrgent;
123210
124- const _StatusRow ({required this .label, required this .isTested});
211+ const _HeroCard ({
212+ required this .title,
213+ required this .body,
214+ required this .actionLabel,
215+ required this .onAction,
216+ this .footnote,
217+ this .isUrgent = false ,
218+ });
125219
126220 @override
127221 Widget build (BuildContext context) {
128- return Row (
129- children: [
130- Text (label, style: context.font.bodyMedium),
131- const Spacer (),
132- Text (
133- isTested
134- ? context.loc.backupSettingsTested
135- : context.loc.backupSettingsNotTested,
136- style: context.font.bodyMedium? .copyWith (
137- color: isTested
138- ? context.appColors.success
139- : context.appColors.error,
222+ final accent = isUrgent
223+ ? context.appColors.error
224+ : context.appColors.onSurface;
225+ return Container (
226+ width: double .infinity,
227+ padding: const EdgeInsets .all (16 ),
228+ decoration: BoxDecoration (
229+ color: context.appColors.surfaceContainer,
230+ border: Border .all (color: accent, width: isUrgent ? 2 : 1 ),
231+ borderRadius: BorderRadius .circular (2 ),
232+ ),
233+ child: Column (
234+ crossAxisAlignment: .start,
235+ children: [
236+ BBText (
237+ title,
238+ style: context.font.titleMedium? .copyWith (fontWeight: .bold),
239+ color: accent,
140240 ),
141- ),
142- ],
241+ const Gap (8 ),
242+ BBText (
243+ body,
244+ style: context.font.bodyMedium,
245+ color: context.appColors.onSurface,
246+ ),
247+ if (footnote != null ) ...[
248+ const Gap (4 ),
249+ BBText (
250+ footnote! ,
251+ style: context.font.bodySmall,
252+ color: context.appColors.textMuted,
253+ ),
254+ ],
255+ const Gap (16 ),
256+ BBButton .big (
257+ label: actionLabel,
258+ onPressed: onAction,
259+ bgColor: accent,
260+ textColor: context.appColors.surface,
261+ ),
262+ ],
263+ ),
143264 );
144265 }
145266}
146267
147- class _TestBackupButton extends StatelessWidget {
148- const _TestBackupButton ();
268+ class _ZeroBackupHero extends StatelessWidget {
269+ const _ZeroBackupHero ();
149270
150271 @override
151272 Widget build (BuildContext context) {
152- return SettingsEntryItem (
153- icon: Icons .verified,
154- title: context.loc.backupSettingsTestBackup,
155- onTap: () => context.pushNamed (
273+ return _HeroCard (
274+ isUrgent: true ,
275+ title: context.loc.backupSettingsHeroBackUpTitle,
276+ body: context.loc.backupSettingsHeroBackUpBody,
277+ actionLabel: context.loc.backupSettingsStartBackupAction,
278+ onAction: () => context.pushNamed (
156279 BackupSettingsSubroute .backupOptions.name,
157- extra: BackupSettingsFlow .test ,
280+ extra: BackupSettingsFlow .backup ,
158281 ),
159282 );
160283 }
161284}
162285
163- class _StartBackupButton extends StatelessWidget {
164- const _StartBackupButton ();
286+ class _AddPhysicalBackupHero extends StatelessWidget {
287+ const _AddPhysicalBackupHero ();
288+
289+ @override
290+ Widget build (BuildContext context) {
291+ return _HeroCard (
292+ title: context.loc.backupHealthReminderTitle,
293+ body: context.loc.backupHealthRecoverbullOnlyBody,
294+ actionLabel: context.loc.backupHealthAddPhysicalBackupAction,
295+ onAction: () => context.pushNamed (
296+ TestWalletBackupRoute .testPhysicalBackupFlow.name,
297+ extra: TestPhysicalBackupFlow .backup,
298+ ),
299+ );
300+ }
301+ }
302+
303+ class _TestBackupHero extends StatelessWidget {
304+ final DateTime ? lastTestedAt;
305+
306+ const _TestBackupHero ({required this .lastTestedAt});
307+
308+ @override
309+ Widget build (BuildContext context) {
310+ final testedAt = lastTestedAt;
311+ return _HeroCard (
312+ title: context.loc.backupHealthReminderTitle,
313+ body: context.loc.backupHealthTestBackupBody,
314+ footnote: testedAt == null
315+ ? null
316+ : context.loc.backupHealthLastTested (timeago.format (testedAt)),
317+ actionLabel: context.loc.backupHealthTestBackupAction,
318+ onAction: () => context.pushNamed (
319+ TestWalletBackupRoute .testPhysicalBackupFlow.name,
320+ extra: TestPhysicalBackupFlow .verify,
321+ ),
322+ );
323+ }
324+ }
325+
326+ class _TestBackupButton extends StatelessWidget {
327+ const _TestBackupButton ();
165328
166329 @override
167330 Widget build (BuildContext context) {
168331 return SettingsEntryItem (
169- icon: Icons .save_as,
170- iconColor: context.appColors.primary,
171- title: context.loc.backupSettingsStartBackup,
332+ icon: Icons .verified,
333+ title: context.loc.backupSettingsTestBackup,
172334 onTap: () => context.pushNamed (
173335 BackupSettingsSubroute .backupOptions.name,
174- extra: BackupSettingsFlow .backup ,
336+ extra: BackupSettingsFlow .test ,
175337 ),
176338 );
177339 }
@@ -228,16 +390,16 @@ class _Bip329LabelsButton extends StatelessWidget {
228390 }
229391}
230392
231- class _RecoverBullSettingsButton extends StatelessWidget {
232- const _RecoverBullSettingsButton ();
393+ class _EncryptedVaultSettingsButton extends StatelessWidget {
394+ const _EncryptedVaultSettingsButton ();
233395
234396 @override
235397 Widget build (BuildContext context) {
236398 return SettingsEntryItem (
237399 icon: Icons .cloud_circle,
238400 iconColor: context.appColors.secondary,
239401 textColor: context.appColors.secondary,
240- title: context.loc.backupSettingsRecoverBullSettings ,
402+ title: context.loc.backupSettingsEncryptedVaultSettings ,
241403 onTap: () {
242404 context.pushNamed (
243405 RecoverBullRoute .recoverbullFlows.name,
0 commit comments