1
1
import 'package:flutter/material.dart' ;
2
2
import 'package:flutter_bloc/flutter_bloc.dart' ;
3
3
import 'package:ht_dashboard/app_configuration/bloc/app_configuration_bloc.dart' ;
4
+ import 'package:ht_dashboard/l10n/l10n.dart' ;
4
5
import 'package:ht_dashboard/shared/constants/app_spacing.dart' ;
5
6
import 'package:ht_dashboard/shared/widgets/widgets.dart' ;
6
7
import 'package:ht_shared/ht_shared.dart' ; // For AppConfig and its nested models
@@ -45,18 +46,18 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
45
46
return Scaffold (
46
47
appBar: AppBar (
47
48
title: Text (
48
- 'App Configuration' ,
49
+ context.l10n.appConfigurationPageTitle ,
49
50
style: Theme .of (context).textTheme.headlineSmall,
50
51
),
51
52
bottom: TabBar (
52
53
controller: _tabController,
53
54
isScrollable: true ,
54
- tabs: const [
55
- Tab (text: 'User Content Limits' ),
56
- Tab (text: 'Ad Settings' ),
57
- Tab (text: 'In-App Prompts' ),
58
- Tab (text: 'App Operational Status' ),
59
- Tab (text: 'Force Update' ),
55
+ tabs: [
56
+ Tab (text: context.l10n.userContentLimitsTab ),
57
+ Tab (text: context.l10n.adSettingsTab ),
58
+ Tab (text: context.l10n.inAppPromptsTab ),
59
+ Tab (text: context.l10n.appOperationalStatusTab ),
60
+ Tab (text: context.l10n.forceUpdateTab ),
60
61
],
61
62
),
62
63
),
@@ -69,7 +70,7 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
69
70
..showSnackBar (
70
71
SnackBar (
71
72
content: Text (
72
- 'App configuration saved successfully!' ,
73
+ context.l10n.appConfigSaveSuccessMessage ,
73
74
style: Theme .of (context).textTheme.bodyMedium? .copyWith (
74
75
color: Theme .of (context).colorScheme.onPrimary,
75
76
),
@@ -87,7 +88,9 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
87
88
..showSnackBar (
88
89
SnackBar (
89
90
content: Text (
90
- 'Error: ${state .errorMessage ?? "Unknown error" }' ,
91
+ context.l10n.appConfigSaveErrorMessage (
92
+ state.errorMessage ?? context.l10n.unknownError,
93
+ ),
91
94
style: Theme .of (context).textTheme.bodyMedium? .copyWith (
92
95
color: Theme .of (context).colorScheme.onError,
93
96
),
@@ -100,14 +103,16 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
100
103
builder: (context, state) {
101
104
if (state.status == AppConfigurationStatus .loading ||
102
105
state.status == AppConfigurationStatus .initial) {
103
- return const LoadingStateWidget (
106
+ return LoadingStateWidget (
104
107
icon: Icons .settings_applications_outlined,
105
- headline: 'Loading Configuration' ,
106
- subheadline: 'Please wait while settings are loaded...' ,
108
+ headline: context.l10n.loadingConfigurationHeadline ,
109
+ subheadline: context.l10n.loadingConfigurationSubheadline ,
107
110
);
108
111
} else if (state.status == AppConfigurationStatus .failure) {
109
112
return FailureStateWidget (
110
- message: state.errorMessage ?? 'Failed to load configuration.' ,
113
+ message:
114
+ state.errorMessage ??
115
+ context.l10n.failedToLoadConfigurationMessage,
111
116
onRetry: () {
112
117
context.read <AppConfigurationBloc >().add (
113
118
const AppConfigurationLoaded (),
@@ -128,10 +133,10 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
128
133
],
129
134
);
130
135
}
131
- return const InitialStateWidget (
136
+ return InitialStateWidget (
132
137
icon: Icons .settings_applications_outlined,
133
- headline: 'App Configuration' ,
134
- subheadline: 'Load application settings from the backend.' ,
138
+ headline: context.l10n.appConfigurationPageTitle ,
139
+ subheadline: context.l10n.loadAppSettingsSubheadline ,
135
140
); // Fallback
136
141
},
137
142
),
@@ -162,7 +167,7 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
162
167
);
163
168
}
164
169
: null ,
165
- child: const Text ('Discard Changes' ),
170
+ child: Text (context.l10n.discardChangesButton ),
166
171
),
167
172
const SizedBox (width: AppSpacing .md),
168
173
ElevatedButton (
@@ -176,7 +181,7 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
176
181
}
177
182
}
178
183
: null ,
179
- child: const Text ('Save Changes' ),
184
+ child: Text (context.l10n.saveChangesButton ),
180
185
),
181
186
],
182
187
),
@@ -190,17 +195,17 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
190
195
builder: (BuildContext dialogContext) {
191
196
return AlertDialog (
192
197
title: Text (
193
- 'Confirm Configuration Update' ,
198
+ context.l10n.confirmConfigUpdateDialogTitle ,
194
199
style: Theme .of (dialogContext).textTheme.titleLarge,
195
200
),
196
201
content: Text (
197
- 'Are you sure you want to apply these changes to the live application configuration? This is a critical operation.' ,
202
+ context.l10n.confirmConfigUpdateDialogContent ,
198
203
style: Theme .of (dialogContext).textTheme.bodyMedium,
199
204
),
200
205
actions: < Widget > [
201
206
TextButton (
202
207
onPressed: () => Navigator .of (dialogContext).pop (false ),
203
- child: const Text ('Cancel' ),
208
+ child: Text (context.l10n.cancelButton ),
204
209
),
205
210
ElevatedButton (
206
211
onPressed: () => Navigator .of (dialogContext).pop (true ),
@@ -210,7 +215,7 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
210
215
dialogContext,
211
216
).colorScheme.onError,
212
217
),
213
- child: const Text ('Confirm Save' ),
218
+ child: Text (context.l10n.confirmSaveButton ),
214
219
),
215
220
],
216
221
);
@@ -231,25 +236,22 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
231
236
crossAxisAlignment: CrossAxisAlignment .start,
232
237
children: [
233
238
Text (
234
- 'User Content Limits' ,
239
+ context.l10n.userContentLimitsTab ,
235
240
style: Theme .of (context).textTheme.headlineSmall,
236
241
),
237
242
const SizedBox (height: AppSpacing .md),
238
243
Text (
239
- 'These settings define the maximum number of countries, news sources, '
240
- 'categories, and saved headlines a user can follow or save. '
241
- 'Limits vary by user type (Guest, Standard, Premium) and directly '
242
- 'impact what content users can curate.' ,
244
+ context.l10n.userContentLimitsDescription,
243
245
style: Theme .of (context).textTheme.bodySmall? .copyWith (
244
246
color: Theme .of (context).colorScheme.onSurface.withOpacity (0.7 ),
245
247
),
246
248
),
247
249
const SizedBox (height: AppSpacing .lg),
248
250
TabBar (
249
- tabs: const [
250
- Tab (text: 'Guest' ),
251
- Tab (text: 'Authenticated' ),
252
- Tab (text: 'Premium' ),
251
+ tabs: [
252
+ Tab (text: context.l10n.guestUserTab ),
253
+ Tab (text: context.l10n.authenticatedUserTab ),
254
+ Tab (text: context.l10n.premiumUserTab ),
253
255
],
254
256
labelColor: Theme .of (context).colorScheme.primary,
255
257
unselectedLabelColor: Theme .of (
@@ -316,26 +318,22 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
316
318
crossAxisAlignment: CrossAxisAlignment .start,
317
319
children: [
318
320
Text (
319
- 'Ad Settings' ,
321
+ context.l10n.adSettingsTab ,
320
322
style: Theme .of (context).textTheme.headlineSmall,
321
323
),
322
324
const SizedBox (height: AppSpacing .md),
323
325
Text (
324
- 'These settings control how advertisements are displayed within '
325
- 'the app\' s news feed, with different rules for Guest, Standard, '
326
- 'and Premium users. "Ad Frequency" determines how often an ad '
327
- 'can appear, while "Ad Placement Interval" sets how many news '
328
- 'items must be shown before the very first ad appears.' ,
326
+ context.l10n.adSettingsDescription,
329
327
style: Theme .of (context).textTheme.bodySmall? .copyWith (
330
328
color: Theme .of (context).colorScheme.onSurface.withOpacity (0.7 ),
331
329
),
332
330
),
333
331
const SizedBox (height: AppSpacing .lg),
334
332
TabBar (
335
- tabs: const [
336
- Tab (text: 'Guest' ),
337
- Tab (text: 'Standard User' ),
338
- Tab (text: 'Premium' ),
333
+ tabs: [
334
+ Tab (text: context.l10n.guestUserTab ),
335
+ Tab (text: context.l10n.standardUserAdTab ),
336
+ Tab (text: context.l10n.premiumUserTab ),
339
337
],
340
338
labelColor: Theme .of (context).colorScheme.primary,
341
339
unselectedLabelColor: Theme .of (
@@ -405,25 +403,21 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
405
403
crossAxisAlignment: CrossAxisAlignment .start,
406
404
children: [
407
405
Text (
408
- 'In-App Prompts' ,
406
+ context.l10n.inAppPromptsTab ,
409
407
style: Theme .of (context).textTheme.headlineSmall,
410
408
),
411
409
const SizedBox (height: AppSpacing .md),
412
410
Text (
413
- 'These settings control how often special in-app messages or '
414
- '"prompts" are shown to users in their news feed. These prompts '
415
- 'encourage actions like linking an account (for guests) or '
416
- 'upgrading to a premium subscription (for authenticated users). '
417
- 'The frequency varies by user type.' ,
411
+ context.l10n.inAppPromptsDescription,
418
412
style: Theme .of (context).textTheme.bodySmall? .copyWith (
419
413
color: Theme .of (context).colorScheme.onSurface.withOpacity (0.7 ),
420
414
),
421
415
),
422
416
const SizedBox (height: AppSpacing .lg),
423
417
TabBar (
424
- tabs: const [
425
- Tab (text: 'Guest' ),
426
- Tab (text: 'Standard User' ),
418
+ tabs: [
419
+ Tab (text: context.l10n.guestUserTab ),
420
+ Tab (text: context.l10n.standardUserAdTab ),
427
421
],
428
422
labelColor: Theme .of (context).colorScheme.primary,
429
423
unselectedLabelColor: Theme .of (
@@ -476,12 +470,12 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
476
470
crossAxisAlignment: CrossAxisAlignment .start,
477
471
children: [
478
472
Text (
479
- 'App Operational Status' ,
473
+ context.l10n.appOperationalStatusTab ,
480
474
style: Theme .of (context).textTheme.headlineSmall,
481
475
),
482
476
const SizedBox (height: AppSpacing .md),
483
477
Text (
484
- 'WARNING: Changing the app \' s operational status can affect all users. Use with extreme caution.' ,
478
+ context.l10n.appOperationalStatusWarning ,
485
479
style: Theme .of (context).textTheme.bodyMedium? .copyWith (
486
480
color: Theme .of (context).colorScheme.error,
487
481
fontWeight: FontWeight .bold,
@@ -490,9 +484,8 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
490
484
const SizedBox (height: AppSpacing .lg),
491
485
_buildDropdownField <RemoteAppStatus >(
492
486
context,
493
- label: 'App Operational Status' ,
494
- description:
495
- 'The current operational status of the app (e.g., active, maintenance, disabled).' ,
487
+ label: context.l10n.appOperationalStatusLabel,
488
+ description: context.l10n.appOperationalStatusDescription,
496
489
value: appConfig.appOperationalStatus,
497
490
items: RemoteAppStatus .values,
498
491
itemLabelBuilder: (status) => status.name,
@@ -509,9 +502,8 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
509
502
if (appConfig.appOperationalStatus == RemoteAppStatus .maintenance)
510
503
_buildTextField (
511
504
context,
512
- label: 'Maintenance Message' ,
513
- description:
514
- 'Message displayed when the app is in maintenance mode.' ,
505
+ label: context.l10n.maintenanceMessageLabel,
506
+ description: context.l10n.maintenanceMessageDescription,
515
507
value: appConfig.maintenanceMessage,
516
508
onChanged: (value) {
517
509
context.read <AppConfigurationBloc >().add (
@@ -524,9 +516,8 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
524
516
if (appConfig.appOperationalStatus == RemoteAppStatus .disabled)
525
517
_buildTextField (
526
518
context,
527
- label: 'Disabled Message' ,
528
- description:
529
- 'Message displayed when the app is permanently disabled.' ,
519
+ label: context.l10n.disabledMessageLabel,
520
+ description: context.l10n.disabledMessageDescription,
530
521
value: appConfig.disabledMessage,
531
522
onChanged: (value) {
532
523
context.read <AppConfigurationBloc >().add (
@@ -548,22 +539,21 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
548
539
crossAxisAlignment: CrossAxisAlignment .start,
549
540
children: [
550
541
Text (
551
- 'Force Update Configuration' ,
542
+ context.l10n.forceUpdateConfigurationTitle ,
552
543
style: Theme .of (context).textTheme.headlineSmall,
553
544
),
554
545
const SizedBox (height: AppSpacing .md),
555
546
Text (
556
- 'These settings control app version enforcement. Users on versions below the minimum allowed will be forced to update.' ,
547
+ context.l10n.forceUpdateDescription ,
557
548
style: Theme .of (context).textTheme.bodySmall? .copyWith (
558
549
color: Theme .of (context).colorScheme.onSurface.withOpacity (0.7 ),
559
550
),
560
551
),
561
552
const SizedBox (height: AppSpacing .lg),
562
553
_buildTextField (
563
554
context,
564
- label: 'Minimum Allowed App Version' ,
565
- description:
566
- 'The lowest app version allowed to run (e.g., "1.2.0").' ,
555
+ label: context.l10n.minAllowedAppVersionLabel,
556
+ description: context.l10n.minAllowedAppVersionDescription,
567
557
value: appConfig.minAllowedAppVersion,
568
558
onChanged: (value) {
569
559
context.read <AppConfigurationBloc >().add (
@@ -575,8 +565,8 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
575
565
),
576
566
_buildTextField (
577
567
context,
578
- label: 'Latest App Version' ,
579
- description: 'The latest available app version (e.g., "1.5.0").' ,
568
+ label: context.l10n.latestAppVersionLabel ,
569
+ description: context.l10n.latestAppVersionDescription ,
580
570
value: appConfig.latestAppVersion,
581
571
onChanged: (value) {
582
572
context.read <AppConfigurationBloc >().add (
@@ -588,8 +578,8 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
588
578
),
589
579
_buildTextField (
590
580
context,
591
- label: 'Update Required Message' ,
592
- description: 'Message displayed when a force update is required.' ,
581
+ label: context.l10n.updateRequiredMessageLabel ,
582
+ description: context.l10n.updateRequiredMessageDescription ,
593
583
value: appConfig.updateRequiredMessage,
594
584
onChanged: (value) {
595
585
context.read <AppConfigurationBloc >().add (
@@ -601,8 +591,8 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
601
591
),
602
592
_buildTextField (
603
593
context,
604
- label: 'Update Optional Message' ,
605
- description: 'Message displayed for an optional update.' ,
594
+ label: context.l10n.updateOptionalMessageLabel ,
595
+ description: context.l10n.updateOptionalMessageDescription ,
606
596
value: appConfig.updateOptionalMessage,
607
597
onChanged: (value) {
608
598
context.read <AppConfigurationBloc >().add (
@@ -614,8 +604,8 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
614
604
),
615
605
_buildTextField (
616
606
context,
617
- label: 'iOS Store URL' ,
618
- description: 'URL to the app on the Apple App Store.' ,
607
+ label: context.l10n.iosStoreUrlLabel ,
608
+ description: context.l10n.iosStoreUrlDescription ,
619
609
value: appConfig.iosStoreUrl,
620
610
onChanged: (value) {
621
611
context.read <AppConfigurationBloc >().add (
@@ -627,8 +617,8 @@ class _AppConfigurationPageState extends State<AppConfigurationPage>
627
617
),
628
618
_buildTextField (
629
619
context,
630
- label: 'Android Store URL' ,
631
- description: 'URL to the app on the Google Play Store.' ,
620
+ label: context.l10n.androidStoreUrlLabel ,
621
+ description: context.l10n.androidStoreUrlDescription ,
632
622
value: appConfig.androidStoreUrl,
633
623
onChanged: (value) {
634
624
context.read <AppConfigurationBloc >().add (
0 commit comments