added i8n

This commit is contained in:
Deep Koluguri 2026-04-06 00:08:34 -04:00
parent 1c861e1f4b
commit fa0e43885a
21 changed files with 3029 additions and 595 deletions

6
luckychit/l10n.yaml Normal file
View File

@ -0,0 +1,6 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
synthetic-package: false
output-dir: lib/l10n

View File

@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../l10n/app_localizations.dart';
/// Persists UI language (English / Telugu) for [AppLocalizations].
class LocaleController extends GetxController {
static LocaleController get to => Get.find();
static const String _prefKey = 'app_locale';
final Rx<Locale> locale = const Locale('en').obs;
/// Call from [main] after [WidgetsFlutterBinding.ensureInitialized].
Future<void> ensureLoaded() async {
final prefs = await SharedPreferences.getInstance();
final code = prefs.getString(_prefKey);
if (code == 'te' || code == 'en') {
locale.value = Locale(code!);
} else {
locale.value = const Locale('en');
}
}
Future<void> setLocale(Locale value) async {
if (value.languageCode != 'en' && value.languageCode != 'te') {
return;
}
locale.value = value;
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_prefKey, value.languageCode);
update();
}
/// Current choice label for settings subtitle (uses [l10n] for both languages).
String currentLanguageLabel(AppLocalizations l10n) {
switch (locale.value.languageCode) {
case 'te':
return l10n.languageTelugu;
case 'en':
default:
return l10n.languageEnglish;
}
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../l10n/l10n_x.dart';
import '../../../shared/widgets/empty_state_widget.dart'; import '../../../shared/widgets/empty_state_widget.dart';
class StateView extends StatelessWidget { class StateView extends StatelessWidget {
@ -8,8 +9,8 @@ class StateView extends StatelessWidget {
final bool isEmpty; final bool isEmpty;
final Widget child; final Widget child;
final Widget? loading; final Widget? loading;
final String emptyTitle; final String? emptyTitle;
final String emptyMessage; final String? emptyMessage;
final EmptyStateType emptyType; final EmptyStateType emptyType;
const StateView({ const StateView({
@ -20,8 +21,8 @@ class StateView extends StatelessWidget {
this.onRetry, this.onRetry,
this.isEmpty = false, this.isEmpty = false,
this.loading, this.loading,
this.emptyTitle = 'Nothing here yet', this.emptyTitle,
this.emptyMessage = 'Try again later.', this.emptyMessage,
this.emptyType = EmptyStateType.noResults, this.emptyType = EmptyStateType.noResults,
}); });
@ -35,6 +36,7 @@ class StateView extends StatelessWidget {
} }
if (error != null) { if (error != null) {
final l = context.l10n;
return Center( return Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
@ -44,7 +46,7 @@ class StateView extends StatelessWidget {
Icon(Icons.error_outline, size: 44, color: Theme.of(context).colorScheme.error), Icon(Icons.error_outline, size: 44, color: Theme.of(context).colorScheme.error),
const SizedBox(height: 12), const SizedBox(height: 12),
Text( Text(
'Something went wrong', l.stateSomethingWentWrong,
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@ -60,7 +62,7 @@ class StateView extends StatelessWidget {
const SizedBox(height: 16), const SizedBox(height: 16),
FilledButton( FilledButton(
onPressed: onRetry, onPressed: onRetry,
child: const Text('Retry'), child: Text(l.retry),
) )
] ]
], ],
@ -70,12 +72,13 @@ class StateView extends StatelessWidget {
} }
if (isEmpty) { if (isEmpty) {
final l = context.l10n;
return Center( return Center(
child: EmptyStateWidget( child: EmptyStateWidget(
type: emptyType, type: emptyType,
customTitle: emptyTitle, customTitle: emptyTitle,
customMessage: emptyMessage, customMessage: emptyMessage,
actionLabel: onRetry != null ? 'Retry' : null, actionLabel: onRetry != null ? l.retry : null,
onActionPressed: onRetry, onActionPressed: onRetry,
), ),
); );

View File

@ -3,6 +3,7 @@ import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import '../models/user.dart'; import '../models/user.dart';
import 'api_service.dart'; import 'api_service.dart';
import '../../l10n/l10n_x.dart';
class AuthService extends GetxController { class AuthService extends GetxController {
static AuthService get to => Get.find(); static AuthService get to => Get.find();
@ -87,12 +88,12 @@ class AuthService extends GetxController {
return true; return true;
} else { } else {
Get.snackbar('Signup Failed', response['message']); Get.snackbar(L10nSvc.of().signupFailedTitle, response['message']);
return false; return false;
} }
} catch (e) { } catch (e) {
print('Signup error: $e'); print('Signup error: $e');
Get.snackbar('Error', 'Signup failed. Please try again.'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().signupFailedGeneric);
return false; return false;
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
@ -118,12 +119,12 @@ class AuthService extends GetxController {
return true; return true;
} else { } else {
Get.snackbar('Login Failed', response['message']); Get.snackbar(L10nSvc.of().loginFailedTitle, response['message']);
return false; return false;
} }
} catch (e) { } catch (e) {
print('Login error: $e'); print('Login error: $e');
Get.snackbar('Error', 'Login failed. Please try again.'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().loginFailedGeneric);
return false; return false;
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
@ -159,15 +160,15 @@ class AuthService extends GetxController {
final response = await _apiService.changePassword(currentPassword, newPassword); final response = await _apiService.changePassword(currentPassword, newPassword);
if (response['success']) { if (response['success']) {
Get.snackbar('Success', 'Password changed successfully'); Get.snackbar(L10nSvc.of().snackTitleSuccess, L10nSvc.of().passwordChangedSuccess);
return true; return true;
} else { } else {
Get.snackbar('Failed', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
return false; return false;
} }
} catch (e) { } catch (e) {
print('Change password error: $e'); print('Change password error: $e');
Get.snackbar('Error', 'Failed to change password'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedChangePassword);
return false; return false;
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;

View File

@ -7,6 +7,7 @@ import '../models/monthly_draw.dart';
import '../models/financial_table_entry.dart'; import '../models/financial_table_entry.dart';
import '../models/user.dart'; import '../models/user.dart';
import 'api_service.dart'; import 'api_service.dart';
import '../../l10n/l10n_x.dart';
class ChitGroupService extends GetxController { class ChitGroupService extends GetxController {
static ChitGroupService get to => Get.find(); static ChitGroupService get to => Get.find();
@ -55,11 +56,11 @@ class ChitGroupService extends GetxController {
print('Successfully loaded ${_chitGroups.length} chit groups'); print('Successfully loaded ${_chitGroups.length} chit groups');
} else { } else {
print('API returned error: ${response['message']}'); print('API returned error: ${response['message']}');
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
} }
} catch (e) { } catch (e) {
print('Error loading manager chit groups: $e'); print('Error loading manager chit groups: $e');
Get.snackbar('Error', 'Failed to load chit groups'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedLoadChitGroups);
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
} }
@ -76,11 +77,11 @@ class ChitGroupService extends GetxController {
final groupsData = response['data']['chitGroups'] as List; final groupsData = response['data']['chitGroups'] as List;
_chitGroups.value = groupsData.map((json) => ChitGroup.fromJson(json)).toList(); _chitGroups.value = groupsData.map((json) => ChitGroup.fromJson(json)).toList();
} else { } else {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
} }
} catch (e) { } catch (e) {
print('Error loading member chit groups: $e'); print('Error loading member chit groups: $e');
Get.snackbar('Error', 'Failed to load chit groups'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedLoadChitGroups);
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
} }
@ -96,15 +97,15 @@ class ChitGroupService extends GetxController {
if (response['success']) { if (response['success']) {
final newGroup = ChitGroup.fromJson(response['data']); final newGroup = ChitGroup.fromJson(response['data']);
_chitGroups.add(newGroup); _chitGroups.add(newGroup);
Get.snackbar('Success', 'Chitfund created successfully'); Get.snackbar(L10nSvc.of().snackTitleSuccess, L10nSvc.of().chitfundCreatedSuccess);
return true; return true;
} else { } else {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
return false; return false;
} }
} catch (e) { } catch (e) {
print('Error creating chit group: $e'); print('Error creating chit group: $e');
Get.snackbar('Error', 'Failed to create chit group'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedCreateChitGroup);
return false; return false;
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
@ -122,12 +123,12 @@ class ChitGroupService extends GetxController {
await loadChitGroupDetails(groupId); await loadChitGroupDetails(groupId);
return true; return true;
} else { } else {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
return false; return false;
} }
} catch (e) { } catch (e) {
print('Error updating chit group: $e'); print('Error updating chit group: $e');
Get.snackbar('Error', 'Failed to update chit group'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedUpdateChitGroup);
return false; return false;
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
@ -143,15 +144,15 @@ class ChitGroupService extends GetxController {
if (response['success']) { if (response['success']) {
_chitGroups.removeWhere((g) => g.id == groupId); _chitGroups.removeWhere((g) => g.id == groupId);
Get.snackbar('Success', 'Chit group deleted successfully'); Get.snackbar(L10nSvc.of().snackTitleSuccess, L10nSvc.of().chitGroupDeletedSuccess);
return true; return true;
} else { } else {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
return false; return false;
} }
} catch (e) { } catch (e) {
print('Error deleting chit group: $e'); print('Error deleting chit group: $e');
Get.snackbar('Error', 'Failed to delete chit group'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedDeleteChitGroup);
return false; return false;
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
@ -169,11 +170,11 @@ class ChitGroupService extends GetxController {
final group = ChitGroup.fromJson(response['data']); final group = ChitGroup.fromJson(response['data']);
_selectedGroup.value = group; _selectedGroup.value = group;
} else { } else {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
} }
} catch (e) { } catch (e) {
print('Error loading chit group details: $e'); print('Error loading chit group details: $e');
Get.snackbar('Error', 'Failed to load group details'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedLoadGroupDetails);
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
} }
@ -193,7 +194,7 @@ class ChitGroupService extends GetxController {
if (showErrors) { if (showErrors) {
// Use post frame callback to avoid showing snackbar during build // Use post frame callback to avoid showing snackbar during build
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
}); });
} }
} }
@ -202,7 +203,7 @@ class ChitGroupService extends GetxController {
if (showErrors) { if (showErrors) {
// Use post frame callback to avoid showing snackbar during build // Use post frame callback to avoid showing snackbar during build
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
Get.snackbar('Error', 'Failed to load group members'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedLoadGroupMembers);
}); });
} }
} finally { } finally {
@ -220,15 +221,15 @@ class ChitGroupService extends GetxController {
if (response['success']) { if (response['success']) {
// Reload group members // Reload group members
await loadGroupMembers(groupId); await loadGroupMembers(groupId);
Get.snackbar('Success', 'Member added successfully'); Get.snackbar(L10nSvc.of().snackTitleSuccess, L10nSvc.of().memberAddedSuccess);
return true; return true;
} else { } else {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
return false; return false;
} }
} catch (e) { } catch (e) {
print('Error adding member: $e'); print('Error adding member: $e');
Get.snackbar('Error', 'Failed to add member'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedAddMember);
return false; return false;
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
@ -245,15 +246,15 @@ class ChitGroupService extends GetxController {
if (response['success']) { if (response['success']) {
// Reload group members // Reload group members
await loadGroupMembers(groupId); await loadGroupMembers(groupId);
Get.snackbar('Success', 'Member removed successfully'); Get.snackbar(L10nSvc.of().snackTitleSuccess, L10nSvc.of().memberRemovedSuccess);
return true; return true;
} else { } else {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
return false; return false;
} }
} catch (e) { } catch (e) {
print('Error removing member: $e'); print('Error removing member: $e');
Get.snackbar('Error', 'Failed to remove member'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedRemoveMember);
return false; return false;
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
@ -313,15 +314,15 @@ class ChitGroupService extends GetxController {
if (response['success']) { if (response['success']) {
// Reload group members // Reload group members
await loadGroupMembers(groupId); await loadGroupMembers(groupId);
Get.snackbar('Success', 'Member status updated successfully'); Get.snackbar(L10nSvc.of().snackTitleSuccess, L10nSvc.of().memberStatusUpdatedSuccess);
return true; return true;
} else { } else {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
return false; return false;
} }
} catch (e) { } catch (e) {
print('Error updating member status: $e'); print('Error updating member status: $e');
Get.snackbar('Error', 'Failed to update member status'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedUpdateMemberStatus);
return false; return false;
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
@ -339,11 +340,11 @@ class ChitGroupService extends GetxController {
final paymentsData = response['data']['payments'] as List; final paymentsData = response['data']['payments'] as List;
_groupPayments.value = paymentsData.map((json) => Payment.fromJson(json)).toList(); _groupPayments.value = paymentsData.map((json) => Payment.fromJson(json)).toList();
} else { } else {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
} }
} catch (e) { } catch (e) {
print('Error loading group payments: $e'); print('Error loading group payments: $e');
Get.snackbar('Error', 'Failed to load payments'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedLoadPayments);
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
} }
@ -359,15 +360,15 @@ class ChitGroupService extends GetxController {
if (response['success']) { if (response['success']) {
// Reload group payments // Reload group payments
await loadGroupPayments(data['group_id']); await loadGroupPayments(data['group_id']);
Get.snackbar('Success', 'Payment recorded successfully'); Get.snackbar(L10nSvc.of().snackTitleSuccess, L10nSvc.of().paymentRecordedSuccess);
return true; return true;
} else { } else {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
return false; return false;
} }
} catch (e) { } catch (e) {
print('Error recording payment: $e'); print('Error recording payment: $e');
Get.snackbar('Error', 'Failed to record payment'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedRecordPayment);
return false; return false;
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
@ -384,11 +385,11 @@ class ChitGroupService extends GetxController {
if (response['success']) { if (response['success']) {
_groupStats.value = response['data']; _groupStats.value = response['data'];
} else { } else {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
} }
} catch (e) { } catch (e) {
print('Error loading group stats: $e'); print('Error loading group stats: $e');
Get.snackbar('Error', 'Failed to load group statistics'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedLoadGroupStatistics);
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
} }
@ -408,15 +409,15 @@ class ChitGroupService extends GetxController {
final updatedGroup = ChitGroup.fromJson(response['data']); final updatedGroup = ChitGroup.fromJson(response['data']);
_chitGroups[index] = updatedGroup; _chitGroups[index] = updatedGroup;
} }
Get.snackbar('Success', 'Chitfund started successfully'); Get.snackbar(L10nSvc.of().snackTitleSuccess, L10nSvc.of().chitfundStartedSuccess);
return true; return true;
} else { } else {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
return false; return false;
} }
} catch (e) { } catch (e) {
print('Error starting chit group: $e'); print('Error starting chit group: $e');
Get.snackbar('Error', 'Failed to start chit group'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedStartChitGroup);
return false; return false;
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
@ -450,11 +451,11 @@ class ChitGroupService extends GetxController {
)) ))
.toList(); .toList();
} else { } else {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
} }
} catch (e) { } catch (e) {
print('Error loading group monthly draws: $e'); print('Error loading group monthly draws: $e');
Get.snackbar('Error', 'Failed to load monthly draws'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedLoadMonthlyDraws);
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
} }
@ -499,14 +500,14 @@ class ChitGroupService extends GetxController {
return {}; return {};
} else { } else {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
Get.snackbar('Error', response['message']?.toString() ?? 'Failed'); Get.snackbar(L10nSvc.of().snackTitleError, response['message']?.toString() ?? L10nSvc.of().operationFailedShort);
}); });
return null; return null;
} }
} catch (e) { } catch (e) {
print('Error creating monthly draw: $e'); print('Error creating monthly draw: $e');
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
Get.snackbar('Error', 'Failed to create monthly draw'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedCreateMonthlyDraw);
}); });
return null; return null;
} finally { } finally {
@ -524,11 +525,11 @@ class ChitGroupService extends GetxController {
if (response['success']) { if (response['success']) {
_drawStats.value = response['data']; _drawStats.value = response['data'];
} else { } else {
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
} }
} catch (e) { } catch (e) {
print('Error loading draw statistics: $e'); print('Error loading draw statistics: $e');
Get.snackbar('Error', 'Failed to load draw statistics'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedLoadDrawStatistics);
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
} }
@ -555,12 +556,12 @@ class ChitGroupService extends GetxController {
_financialData.value = entries; _financialData.value = entries;
} else { } else {
print('API returned error: ${response['message']}'); print('API returned error: ${response['message']}');
Get.snackbar('Error', response['message']); Get.snackbar(L10nSvc.of().snackTitleError, response['message']);
} }
} catch (e) { } catch (e) {
print('Error loading group financial data: $e'); print('Error loading group financial data: $e');
print('Error stack trace: ${e.toString()}'); print('Error stack trace: ${e.toString()}');
Get.snackbar('Error', 'Failed to load financial data'); Get.snackbar(L10nSvc.of().snackTitleError, L10nSvc.of().failedLoadFinancialData);
} finally { } finally {
_isLoading.value = false; _isLoading.value = false;
} }

View File

@ -7,6 +7,7 @@ import '../../../core/design_system/app_components/auth_shell.dart';
import '../../../core/design_system/app_components/app_card.dart'; import '../../../core/design_system/app_components/app_card.dart';
import '../../../core/design_system/app_spacing.dart'; import '../../../core/design_system/app_spacing.dart';
import '../../../core/design_system/app_text.dart'; import '../../../core/design_system/app_text.dart';
import '../../../l10n/l10n_x.dart';
import 'signup_screen.dart'; import 'signup_screen.dart';
class LoginScreen extends StatefulWidget { class LoginScreen extends StatefulWidget {
@ -37,10 +38,11 @@ class _LoginScreenState extends State<LoginScreen> {
_passwordController.text, _passwordController.text,
); );
if (!success) { if (!success && mounted) {
final l = context.l10n;
SnackbarUtil.showError( SnackbarUtil.showError(
'Invalid mobile number or password. Please try again.', l.loginInvalidCredentials,
title: 'Login Failed', title: l.loginFailedTitle,
); );
} }
} }
@ -48,6 +50,7 @@ class _LoginScreenState extends State<LoginScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l = context.l10n;
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
body: AuthShell( body: AuthShell(
@ -61,7 +64,7 @@ class _LoginScreenState extends State<LoginScreen> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Semantics( Semantics(
label: 'LuckyChit logo', label: '${l.appDisplayName} logo',
child: Icon( child: Icon(
Icons.account_balance_wallet_rounded, Icons.account_balance_wallet_rounded,
size: 56.w, size: 56.w,
@ -69,10 +72,10 @@ class _LoginScreenState extends State<LoginScreen> {
), ),
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Text('LuckyChit', style: AppText.headline(context), textAlign: TextAlign.center), Text(l.appDisplayName, style: AppText.headline(context), textAlign: TextAlign.center),
SizedBox(height: 6.h), SizedBox(height: 6.h),
Text( Text(
'Chit fund management that feels effortless.', l.authLoginTagline,
style: AppText.bodyMuted(context), style: AppText.bodyMuted(context),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@ -82,13 +85,13 @@ class _LoginScreenState extends State<LoginScreen> {
controller: _mobileController, controller: _mobileController,
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: 'Mobile number', labelText: l.labelMobileNumber,
prefixIcon: Icon(Icons.phone_rounded), prefixIcon: const Icon(Icons.phone_rounded),
), ),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) return 'Please enter mobile number'; if (value == null || value.isEmpty) return l.validatorEnterMobile;
if (value.length != 10) return 'Mobile number must be 10 digits'; if (value.length != 10) return l.validatorMobileTenDigits;
return null; return null;
}, },
), ),
@ -98,17 +101,17 @@ class _LoginScreenState extends State<LoginScreen> {
obscureText: !_isPasswordVisible, obscureText: !_isPasswordVisible,
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Password', labelText: l.labelPassword,
prefixIcon: const Icon(Icons.lock_rounded), prefixIcon: const Icon(Icons.lock_rounded),
suffixIcon: IconButton( suffixIcon: IconButton(
tooltip: _isPasswordVisible ? 'Hide password' : 'Show password', tooltip: _isPasswordVisible ? l.tooltipHidePassword : l.tooltipShowPassword,
icon: Icon(_isPasswordVisible ? Icons.visibility : Icons.visibility_off), icon: Icon(_isPasswordVisible ? Icons.visibility : Icons.visibility_off),
onPressed: () => setState(() => _isPasswordVisible = !_isPasswordVisible), onPressed: () => setState(() => _isPasswordVisible = !_isPasswordVisible),
), ),
), ),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) return 'Please enter password'; if (value == null || value.isEmpty) return l.validatorEnterPasswordAuth;
if (value.length < 6) return 'Password must be at least 6 characters'; if (value.length < 6) return l.validatorPasswordMinSixAuth;
return null; return null;
}, },
), ),
@ -127,13 +130,13 @@ class _LoginScreenState extends State<LoginScreen> {
valueColor: AlwaysStoppedAnimation<Color>(scheme.onPrimary), valueColor: AlwaysStoppedAnimation<Color>(scheme.onPrimary),
), ),
) )
: const Text('Sign in'), : Text(l.signInButton),
); );
}), }),
SizedBox(height: 10.h), SizedBox(height: 10.h),
OutlinedButton( OutlinedButton(
onPressed: () => Get.to(() => const SignupScreen()), onPressed: () => Get.to(() => const SignupScreen()),
child: const Text('Create account'), child: Text(l.createAccountButton),
), ),
], ],
), ),

View File

@ -1,13 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../../app.dart';
import '../../../core/services/auth_service.dart'; import '../../../core/services/auth_service.dart';
import '../../../core/utils/snackbar_util.dart'; import '../../../core/utils/snackbar_util.dart';
import '../../../app.dart';
import '../../../core/design_system/app_components/auth_shell.dart'; import '../../../core/design_system/app_components/auth_shell.dart';
import '../../../core/design_system/app_components/app_card.dart'; import '../../../core/design_system/app_components/app_card.dart';
import '../../../core/design_system/app_spacing.dart'; import '../../../core/design_system/app_spacing.dart';
import '../../../core/design_system/app_text.dart'; import '../../../core/design_system/app_text.dart';
import '../../../l10n/l10n_x.dart';
class SignupScreen extends StatefulWidget { class SignupScreen extends StatefulWidget {
const SignupScreen({super.key}); const SignupScreen({super.key});
@ -52,19 +53,19 @@ class _SignupScreenState extends State<SignupScreen> {
emergencyContact: _emergencyContactController.text.trim().isEmpty ? null : _emergencyContactController.text.trim(), emergencyContact: _emergencyContactController.text.trim().isEmpty ? null : _emergencyContactController.text.trim(),
); );
if (!mounted) return;
final l = context.l10n;
if (success) { if (success) {
SnackbarUtil.showSuccess( SnackbarUtil.showSuccess(
'Account created successfully! Welcome to LuckyChit.', l.signupSuccessWelcome,
title: 'Success', title: l.snackTitleSuccess,
); );
// Navigate to home - App widget will auto-route based on role
// Use offAll to remove all previous routes (can't go back to signup)
Get.offAll(() => const App()); Get.offAll(() => const App());
} else { } else {
SnackbarUtil.showError( SnackbarUtil.showError(
'Failed to create account. Please try again.', l.signupFailedGenericUi,
title: 'Signup Failed', title: l.signupFailedTitle,
); );
} }
} }
@ -72,6 +73,7 @@ class _SignupScreenState extends State<SignupScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l = context.l10n;
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
body: AuthShell( body: AuthShell(
@ -90,173 +92,155 @@ class _SignupScreenState extends State<SignupScreen> {
color: scheme.primary, color: scheme.primary,
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Text('Create account', style: AppText.headline(context), textAlign: TextAlign.center), Text(l.authSignupScreenTitle, style: AppText.headline(context), textAlign: TextAlign.center),
SizedBox(height: 6.h), SizedBox(height: 6.h),
Text( Text(
'Set up your profile in under a minute.', l.authSignupTagline,
style: AppText.bodyMuted(context), style: AppText.bodyMuted(context),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
SizedBox(height: 24.h), SizedBox(height: 24.h),
// Mobile Number Field
TextFormField( TextFormField(
controller: _mobileController, controller: _mobileController,
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: 'Mobile number *', labelText: l.labelMobileNumberRequired,
prefixIcon: Icon(Icons.phone_rounded), prefixIcon: const Icon(Icons.phone_rounded),
), ),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Please enter mobile number'; return l.validatorEnterMobile;
} }
if (value.length != 10) { if (value.length != 10) {
return 'Mobile number must be 10 digits'; return l.validatorMobileTenDigits;
} }
if (!RegExp(r'^[0-9]+$').hasMatch(value)) { if (!RegExp(r'^[0-9]+$').hasMatch(value)) {
return 'Mobile number must contain only digits'; return l.validatorMobileDigitsOnly;
} }
return null; return null;
}, },
), ),
SizedBox(height: 20.h), SizedBox(height: 20.h),
// Full Name Field
TextFormField( TextFormField(
controller: _fullNameController, controller: _fullNameController,
keyboardType: TextInputType.name, keyboardType: TextInputType.name,
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: 'Full name *', labelText: l.labelFullNameRequired,
prefixIcon: Icon(Icons.person_rounded), prefixIcon: const Icon(Icons.person_rounded),
), ),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Please enter your full name'; return l.validatorEnterFullName;
} }
return null; return null;
}, },
), ),
SizedBox(height: 20.h), SizedBox(height: 20.h),
// Email Field (Optional)
TextFormField( TextFormField(
controller: _emailController, controller: _emailController,
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: 'Email (optional)', labelText: l.labelEmailOptional,
prefixIcon: Icon(Icons.email_rounded), prefixIcon: const Icon(Icons.email_rounded),
), ),
validator: (value) { validator: (value) {
if (value != null && value.isNotEmpty) { if (value != null && value.isNotEmpty) {
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) { if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'Please enter a valid email address'; return l.validatorValidEmail;
} }
} }
return null; return null;
}, },
), ),
SizedBox(height: 20.h), SizedBox(height: 20.h),
// Address Field (Optional)
TextFormField( TextFormField(
controller: _addressController, controller: _addressController,
keyboardType: TextInputType.streetAddress, keyboardType: TextInputType.streetAddress,
maxLines: 2, maxLines: 2,
textInputAction: TextInputAction.newline, textInputAction: TextInputAction.newline,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: 'Address (optional)', labelText: l.labelAddressOptional,
prefixIcon: Icon(Icons.home_rounded), prefixIcon: const Icon(Icons.home_rounded),
), ),
), ),
SizedBox(height: 20.h), SizedBox(height: 20.h),
// Emergency Contact Field (Optional)
TextFormField( TextFormField(
controller: _emergencyContactController, controller: _emergencyContactController,
keyboardType: TextInputType.phone, keyboardType: TextInputType.phone,
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
decoration: const InputDecoration( decoration: InputDecoration(
labelText: 'Emergency contact (optional)', labelText: l.labelEmergencyContactOptional,
prefixIcon: Icon(Icons.contact_phone_rounded), prefixIcon: const Icon(Icons.contact_phone_rounded),
), ),
validator: (value) { validator: (value) {
if (value != null && value.isNotEmpty) { if (value != null && value.isNotEmpty) {
if (value.length != 10) { if (value.length != 10) {
return 'Emergency contact must be 10 digits'; return l.validatorEmergencyTenDigits;
} }
if (!RegExp(r'^[0-9]+$').hasMatch(value)) { if (!RegExp(r'^[0-9]+$').hasMatch(value)) {
return 'Emergency contact must contain only digits'; return l.validatorEmergencyDigitsOnly;
} }
} }
return null; return null;
}, },
), ),
SizedBox(height: 20.h), SizedBox(height: 20.h),
// Password Field
TextFormField( TextFormField(
controller: _passwordController, controller: _passwordController,
obscureText: !_isPasswordVisible, obscureText: !_isPasswordVisible,
textInputAction: TextInputAction.next, textInputAction: TextInputAction.next,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Password *', labelText: l.labelPasswordRequired,
prefixIcon: const Icon(Icons.lock_rounded), prefixIcon: const Icon(Icons.lock_rounded),
suffixIcon: IconButton( suffixIcon: IconButton(
tooltip: _isPasswordVisible ? 'Hide password' : 'Show password', tooltip: _isPasswordVisible ? l.tooltipHidePassword : l.tooltipShowPassword,
icon: Icon(_isPasswordVisible ? Icons.visibility : Icons.visibility_off), icon: Icon(_isPasswordVisible ? Icons.visibility : Icons.visibility_off),
onPressed: () => setState(() => _isPasswordVisible = !_isPasswordVisible), onPressed: () => setState(() => _isPasswordVisible = !_isPasswordVisible),
), ),
), ),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Please enter password'; return l.validatorEnterPasswordAuth;
} }
if (value.length < 6) { if (value.length < 6) {
return 'Password must be at least 6 characters'; return l.validatorPasswordMinSixAuth;
} }
return null; return null;
}, },
), ),
SizedBox(height: 20.h), SizedBox(height: 20.h),
// Confirm Password Field
TextFormField( TextFormField(
controller: _confirmPasswordController, controller: _confirmPasswordController,
obscureText: !_isConfirmPasswordVisible, obscureText: !_isConfirmPasswordVisible,
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Confirm password *', labelText: l.labelConfirmPasswordRequired,
prefixIcon: const Icon(Icons.lock_outline_rounded), prefixIcon: const Icon(Icons.lock_outline_rounded),
suffixIcon: IconButton( suffixIcon: IconButton(
tooltip: _isConfirmPasswordVisible ? 'Hide password' : 'Show password', tooltip: _isConfirmPasswordVisible ? l.tooltipHidePassword : l.tooltipShowPassword,
icon: Icon(_isConfirmPasswordVisible ? Icons.visibility : Icons.visibility_off), icon: Icon(_isConfirmPasswordVisible ? Icons.visibility : Icons.visibility_off),
onPressed: () => setState(() => _isConfirmPasswordVisible = !_isConfirmPasswordVisible), onPressed: () => setState(() => _isConfirmPasswordVisible = !_isConfirmPasswordVisible),
), ),
), ),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Please confirm password'; return l.validatorConfirmPassword;
} }
if (value != _passwordController.text) { if (value != _passwordController.text) {
return 'Passwords do not match'; return l.validatorPasswordsMismatch;
} }
return null; return null;
}, },
), ),
SizedBox(height: 28.h), SizedBox(height: 28.h),
// Signup Button
Obx(() => SizedBox( Obx(() => SizedBox(
width: double.infinity, width: double.infinity,
height: 52.h, height: 52.h,
child: FilledButton( child: FilledButton(
onPressed: _authService.isLoading.value onPressed: _authService.isLoading.value ? null : _handleSignup,
? null
: _handleSignup,
child: _authService.isLoading.value child: _authService.isLoading.value
? SizedBox( ? SizedBox(
height: 24.h, height: 24.h,
@ -266,25 +250,21 @@ class _SignupScreenState extends State<SignupScreen> {
valueColor: AlwaysStoppedAnimation<Color>(scheme.onPrimary), valueColor: AlwaysStoppedAnimation<Color>(scheme.onPrimary),
), ),
) )
: const Text('Create account'), : Text(l.createAccountButton),
), ),
)), )),
SizedBox(height: 20.h), SizedBox(height: 20.h),
// Login Link
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
'Already have an account? ', l.alreadyHaveAccount,
style: AppText.bodyMuted(context), style: AppText.bodyMuted(context),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () => Get.back(),
Get.back();
},
child: Text( child: Text(
'Login', l.loginLink,
style: AppText.label(context).copyWith(color: scheme.primary), style: AppText.label(context).copyWith(color: scheme.primary),
), ),
), ),
@ -299,4 +279,3 @@ class _SignupScreenState extends State<SignupScreen> {
); );
} }
} }

View File

@ -3,27 +3,44 @@ import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../core/controllers/theme_controller.dart'; import '../../core/controllers/theme_controller.dart';
import '../../core/controllers/locale_controller.dart';
import '../../core/services/auth_service.dart'; import '../../core/services/auth_service.dart';
import '../../core/services/api_service.dart'; import '../../core/services/api_service.dart';
import '../../core/utils/snackbar_util.dart'; import '../../core/utils/snackbar_util.dart';
import '../../l10n/app_localizations.dart';
String _themeModeLabel(AppLocalizations l10n) {
switch (ThemeController.to.themeMode) {
case ThemeMode.light:
return l10n.themeLight;
case ThemeMode.dark:
return l10n.themeDark;
case ThemeMode.system:
return l10n.themeSystem;
}
}
class SettingsPage extends StatelessWidget { class SettingsPage extends StatelessWidget {
const SettingsPage({super.key}); const SettingsPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Settings'), title: Text(l10n.settingsTitle),
), ),
body: ListView( body: ListView(
padding: EdgeInsets.all(16.w), padding: EdgeInsets.all(16.w),
children: [ children: [
_buildSectionHeader(context, 'Appearance'), _buildSectionHeader(context, l10n.sectionAppearance),
_buildThemeSettings(context), _buildThemeSettings(context, l10n),
SizedBox(height: 24.h), SizedBox(height: 24.h),
_buildSectionHeader(context, 'Account'), _buildSectionHeader(context, l10n.sectionLanguage),
_buildAccountSettings(context), _buildLanguageSettings(context, l10n),
SizedBox(height: 24.h),
_buildSectionHeader(context, l10n.sectionAccount),
_buildAccountSettings(context, l10n),
SizedBox(height: 24.h), SizedBox(height: 24.h),
Obx(() { Obx(() {
final user = AuthService.to.currentUser.value; final user = AuthService.to.currentUser.value;
@ -31,21 +48,21 @@ class SettingsPage extends StatelessWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildSectionHeader(context, 'Payment Settings'), _buildSectionHeader(context, l10n.sectionPaymentSettings),
_buildPaymentSettings(context), _buildPaymentSettings(context, l10n),
SizedBox(height: 24.h), SizedBox(height: 24.h),
], ],
); );
} }
return const SizedBox.shrink(); return const SizedBox.shrink();
}), }),
_buildSectionHeader(context, 'Notifications'), _buildSectionHeader(context, l10n.sectionNotifications),
_buildNotificationSettings(context), _buildNotificationSettings(context, l10n),
SizedBox(height: 24.h), SizedBox(height: 24.h),
_buildSectionHeader(context, 'About'), _buildSectionHeader(context, l10n.sectionAbout),
_buildAboutSettings(context), _buildAboutSettings(context, l10n),
SizedBox(height: 32.h), SizedBox(height: 32.h),
_buildLogoutButton(context), _buildLogoutButton(context, l10n),
], ],
), ),
); );
@ -67,13 +84,88 @@ class SettingsPage extends StatelessWidget {
); );
} }
Widget _buildThemeSettings(BuildContext context) { Widget _buildLanguageSettings(BuildContext context, AppLocalizations l10n) {
final scheme = Theme.of(context).colorScheme;
return Card(
child: Obx(
() => ListTile(
leading: Container(
padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(
color: scheme.secondaryContainer,
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(
Icons.language_rounded,
color: scheme.onSecondaryContainer,
size: 24.w,
),
),
title: Text(
l10n.languageTitle,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
),
subtitle: Text(
LocaleController.to.currentLanguageLabel(l10n),
style: TextStyle(fontSize: 14.sp),
),
trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16.w),
onTap: () => _showLanguageDialog(context, l10n),
),
),
);
}
void _showLanguageDialog(BuildContext context, AppLocalizations l10n) {
final scheme = Theme.of(context).colorScheme;
Get.dialog(
Obx(
() {
final code = LocaleController.to.locale.value.languageCode;
return AlertDialog(
title: Text(l10n.chooseLanguageTitle),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
RadioListTile<String>(
title: Text(l10n.languageEnglish),
value: 'en',
groupValue: code,
onChanged: (v) async {
if (v != null) {
await LocaleController.to.setLocale(Locale(v));
Get.back();
}
},
activeColor: scheme.primary,
),
RadioListTile<String>(
title: Text(l10n.languageTelugu),
value: 'te',
groupValue: code,
onChanged: (v) async {
if (v != null) {
await LocaleController.to.setLocale(Locale(v));
Get.back();
}
},
activeColor: scheme.primary,
),
],
),
);
},
),
);
}
Widget _buildThemeSettings(BuildContext context, AppLocalizations l10n) {
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
return Card( return Card(
child: Column( child: Column(
children: [ children: [
Obx(() { Obx(
return ListTile( () => ListTile(
leading: Container( leading: Container(
padding: EdgeInsets.all(10.w), padding: EdgeInsets.all(10.w),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -87,22 +179,20 @@ class SettingsPage extends StatelessWidget {
), ),
), ),
title: Text( title: Text(
'Theme', l10n.themeTitle,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
), ),
subtitle: Text( subtitle: Text(
ThemeController.to.getThemeModeString(), _themeModeLabel(l10n),
style: TextStyle(fontSize: 14.sp), style: TextStyle(fontSize: 14.sp),
), ),
trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16.w), trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16.w),
onTap: () => _showThemeDialog(context), onTap: () => _showThemeDialog(context, l10n),
); ),
}), ),
Divider(height: 1.h, indent: 72.w), Divider(height: 1.h, indent: 72.w),
Obx(
Obx(() { () => SwitchListTile(
return SwitchListTile(
secondary: Container( secondary: Container(
padding: EdgeInsets.all(10.w), padding: EdgeInsets.all(10.w),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -116,11 +206,11 @@ class SettingsPage extends StatelessWidget {
), ),
), ),
title: Text( title: Text(
'Dark Mode', l10n.darkModeTitle,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
), ),
subtitle: Text( subtitle: Text(
'Override system settings', l10n.darkModeSubtitle,
style: TextStyle(fontSize: 14.sp), style: TextStyle(fontSize: 14.sp),
), ),
value: ThemeController.to.isDarkMode, value: ThemeController.to.isDarkMode,
@ -128,19 +218,20 @@ class SettingsPage extends StatelessWidget {
ThemeController.to.toggleTheme(); ThemeController.to.toggleTheme();
}, },
activeColor: scheme.primary, activeColor: scheme.primary,
); ),
}), ),
], ],
), ),
); );
} }
Widget _buildAccountSettings(BuildContext context) { Widget _buildAccountSettings(BuildContext context, AppLocalizations l10n) {
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
return Card( return Card(
child: Column( child: Column(
children: [ children: [
Obx(() { Obx(
() {
final user = AuthService.to.currentUser.value; final user = AuthService.to.currentUser.value;
return ListTile( return ListTile(
leading: Container( leading: Container(
@ -165,14 +256,12 @@ class SettingsPage extends StatelessWidget {
), ),
trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16.w), trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16.w),
onTap: () { onTap: () {
SnackbarUtil.showInfo('Profile page coming soon'); SnackbarUtil.showInfo(l10n.profileComingSoon);
}, },
); );
}), },
),
Divider(height: 1.h, indent: 72.w), Divider(height: 1.h, indent: 72.w),
// Change Password
ListTile( ListTile(
leading: Container( leading: Container(
padding: EdgeInsets.all(10.w), padding: EdgeInsets.all(10.w),
@ -187,16 +276,16 @@ class SettingsPage extends StatelessWidget {
), ),
), ),
title: Text( title: Text(
'Change Password', l10n.changePasswordTitle,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
), ),
subtitle: Text( subtitle: Text(
'Update your password', l10n.changePasswordSubtitle,
style: TextStyle(fontSize: 14.sp), style: TextStyle(fontSize: 14.sp),
), ),
trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16.w), trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16.w),
onTap: () { onTap: () {
SnackbarUtil.showInfo('Change password feature coming soon'); SnackbarUtil.showInfo(l10n.changePasswordComingSoon);
}, },
), ),
], ],
@ -204,7 +293,8 @@ class SettingsPage extends StatelessWidget {
); );
} }
Widget _buildNotificationSettings(BuildContext context) { Widget _buildNotificationSettings(
BuildContext context, AppLocalizations l10n) {
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
return Card( return Card(
child: Column( child: Column(
@ -223,22 +313,20 @@ class SettingsPage extends StatelessWidget {
), ),
), ),
title: Text( title: Text(
'Push Notifications', l10n.pushNotificationsTitle,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
), ),
subtitle: Text( subtitle: Text(
'Receive push notifications', l10n.pushNotificationsSubtitle,
style: TextStyle(fontSize: 14.sp), style: TextStyle(fontSize: 14.sp),
), ),
value: true, value: true,
onChanged: (value) { onChanged: (value) {
SnackbarUtil.showInfo('Notification settings coming soon'); SnackbarUtil.showInfo(l10n.notificationComingSoon);
}, },
activeColor: scheme.primary, activeColor: scheme.primary,
), ),
Divider(height: 1.h, indent: 72.w), Divider(height: 1.h, indent: 72.w),
SwitchListTile( SwitchListTile(
secondary: Container( secondary: Container(
padding: EdgeInsets.all(10.w), padding: EdgeInsets.all(10.w),
@ -253,22 +341,20 @@ class SettingsPage extends StatelessWidget {
), ),
), ),
title: Text( title: Text(
'Payment Reminders', l10n.paymentRemindersTitle,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
), ),
subtitle: Text( subtitle: Text(
'Reminders for upcoming payments', l10n.paymentRemindersSubtitle,
style: TextStyle(fontSize: 14.sp), style: TextStyle(fontSize: 14.sp),
), ),
value: true, value: true,
onChanged: (value) { onChanged: (value) {
SnackbarUtil.showInfo('Notification settings coming soon'); SnackbarUtil.showInfo(l10n.notificationComingSoon);
}, },
activeColor: scheme.primary, activeColor: scheme.primary,
), ),
Divider(height: 1.h, indent: 72.w), Divider(height: 1.h, indent: 72.w),
SwitchListTile( SwitchListTile(
secondary: Container( secondary: Container(
padding: EdgeInsets.all(10.w), padding: EdgeInsets.all(10.w),
@ -283,16 +369,16 @@ class SettingsPage extends StatelessWidget {
), ),
), ),
title: Text( title: Text(
'Draw Notifications', l10n.drawNotificationsTitle,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
), ),
subtitle: Text( subtitle: Text(
'Alerts for lottery draws', l10n.drawNotificationsSubtitle,
style: TextStyle(fontSize: 14.sp), style: TextStyle(fontSize: 14.sp),
), ),
value: true, value: true,
onChanged: (value) { onChanged: (value) {
SnackbarUtil.showInfo('Notification settings coming soon'); SnackbarUtil.showInfo(l10n.notificationComingSoon);
}, },
activeColor: scheme.primary, activeColor: scheme.primary,
), ),
@ -301,7 +387,7 @@ class SettingsPage extends StatelessWidget {
); );
} }
Widget _buildPaymentSettings(BuildContext context) { Widget _buildPaymentSettings(BuildContext context, AppLocalizations l10n) {
final apiService = ApiService(); final apiService = ApiService();
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
@ -326,17 +412,17 @@ class SettingsPage extends StatelessWidget {
), ),
), ),
title: Text( title: Text(
'UPI ID', l10n.upiIdTitle,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
), ),
subtitle: Text( subtitle: Text(
'Loading...', l10n.loading,
style: TextStyle(fontSize: 14.sp), style: TextStyle(fontSize: 14.sp),
), ),
); );
} }
final upiId = snapshot.data?['data']?['upi_id'] ?? 'Not configured'; final upiId = snapshot.data?['data']?['upi_id'] ?? l10n.notConfigured;
final isConfigured = snapshot.data?['data']?['is_configured'] ?? false; final isConfigured = snapshot.data?['data']?['is_configured'] ?? false;
return ListTile( return ListTile(
@ -359,7 +445,7 @@ class SettingsPage extends StatelessWidget {
title: Row( title: Row(
children: [ children: [
Text( Text(
'UPI ID', l10n.upiIdTitle,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
), ),
if (isConfigured) ...[ if (isConfigured) ...[
@ -371,7 +457,7 @@ class SettingsPage extends StatelessWidget {
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
), ),
child: Text( child: Text(
'Active', l10n.active,
style: TextStyle( style: TextStyle(
fontSize: 10.sp, fontSize: 10.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -387,7 +473,7 @@ class SettingsPage extends StatelessWidget {
children: [ children: [
SizedBox(height: 4.h), SizedBox(height: 4.h),
SelectableText( SelectableText(
upiId, upiId.toString(),
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -400,7 +486,7 @@ class SettingsPage extends StatelessWidget {
if (!isConfigured) ...[ if (!isConfigured) ...[
SizedBox(height: 4.h), SizedBox(height: 4.h),
Text( Text(
'Configure in backend/.env', l10n.configureBackend,
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: scheme.tertiary, color: scheme.tertiary,
@ -413,29 +499,26 @@ class SettingsPage extends StatelessWidget {
trailing: isConfigured trailing: isConfigured
? IconButton( ? IconButton(
onPressed: () { onPressed: () {
Clipboard.setData(ClipboardData(text: upiId)); Clipboard.setData(ClipboardData(text: upiId.toString()));
SnackbarUtil.showSuccess('UPI ID copied to clipboard'); SnackbarUtil.showSuccess(l10n.upiCopiedClipboard);
}, },
icon: Icon( icon: Icon(
Icons.copy_rounded, Icons.copy_rounded,
size: 20.w, size: 20.w,
color: scheme.secondary, color: scheme.secondary,
), ),
tooltip: 'Copy UPI ID', tooltip: l10n.copyUpiIdTooltip,
) )
: Icon( : Icon(
Icons.warning_rounded, Icons.warning_rounded,
size: 20.w, size: 20.w,
color: scheme.tertiary, color: scheme.tertiary,
), ),
onTap: () => _showUPIInfoDialog(context, upiId, isConfigured), onTap: () => _showUPIInfoDialog(context, l10n, upiId.toString(), isConfigured),
); );
}, },
), ),
Divider(height: 1.h, indent: 72.w), Divider(height: 1.h, indent: 72.w),
// Payment Statistics
ListTile( ListTile(
leading: Container( leading: Container(
padding: EdgeInsets.all(10.w), padding: EdgeInsets.all(10.w),
@ -450,21 +533,19 @@ class SettingsPage extends StatelessWidget {
), ),
), ),
title: Text( title: Text(
'Payment Statistics', l10n.paymentStatisticsTitle,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
), ),
subtitle: Text( subtitle: Text(
'View payment insights', l10n.paymentStatisticsSubtitle,
style: TextStyle(fontSize: 14.sp), style: TextStyle(fontSize: 14.sp),
), ),
trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16.w), trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16.w),
onTap: () { onTap: () {
SnackbarUtil.showInfo('Payment statistics coming soon'); SnackbarUtil.showInfo(l10n.paymentStatsComingSoon);
}, },
), ),
Divider(height: 1.h, indent: 72.w), Divider(height: 1.h, indent: 72.w),
ListTile( ListTile(
leading: Container( leading: Container(
padding: EdgeInsets.all(10.w), padding: EdgeInsets.all(10.w),
@ -479,11 +560,11 @@ class SettingsPage extends StatelessWidget {
), ),
), ),
title: Text( title: Text(
'Transaction Fees', l10n.transactionFeesTitle,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
), ),
subtitle: Text( subtitle: Text(
'0% fees • Save lakhs per year!', l10n.transactionFeesSubtitle,
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: scheme.primary, color: scheme.primary,
@ -497,7 +578,7 @@ class SettingsPage extends StatelessWidget {
borderRadius: BorderRadius.circular(8.r), borderRadius: BorderRadius.circular(8.r),
), ),
child: Text( child: Text(
'FREE', l10n.free,
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
@ -511,7 +592,12 @@ class SettingsPage extends StatelessWidget {
); );
} }
void _showUPIInfoDialog(BuildContext context, String upiId, bool isConfigured) { void _showUPIInfoDialog(
BuildContext context,
AppLocalizations l10n,
String upiId,
bool isConfigured,
) {
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
Get.dialog( Get.dialog(
AlertDialog( AlertDialog(
@ -523,7 +609,7 @@ class SettingsPage extends StatelessWidget {
size: 24.w, size: 24.w,
), ),
SizedBox(width: 12.w), SizedBox(width: 12.w),
const Text('UPI Payment Settings'), Expanded(child: Text(l10n.upiPaymentSettingsTitle)),
], ],
), ),
content: SingleChildScrollView( content: SingleChildScrollView(
@ -532,7 +618,7 @@ class SettingsPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Current UPI ID', l10n.currentUpiId,
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -573,7 +659,7 @@ class SettingsPage extends StatelessWidget {
IconButton( IconButton(
onPressed: () { onPressed: () {
Clipboard.setData(ClipboardData(text: upiId)); Clipboard.setData(ClipboardData(text: upiId));
SnackbarUtil.showSuccess('Copied!'); SnackbarUtil.showSuccess(l10n.copied);
}, },
icon: Icon( icon: Icon(
Icons.copy_rounded, Icons.copy_rounded,
@ -584,7 +670,6 @@ class SettingsPage extends StatelessWidget {
], ],
), ),
), ),
if (!isConfigured) ...[ if (!isConfigured) ...[
SizedBox(height: 16.h), SizedBox(height: 16.h),
Container( Container(
@ -603,7 +688,7 @@ class SettingsPage extends StatelessWidget {
SizedBox(width: 8.w), SizedBox(width: 8.w),
Expanded( Expanded(
child: Text( child: Text(
'UPI ID not configured. Update backend/.env file.', l10n.upiNotConfiguredMessage,
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: scheme.onTertiaryContainer, color: scheme.onTertiaryContainer,
@ -614,13 +699,11 @@ class SettingsPage extends StatelessWidget {
), ),
), ),
], ],
SizedBox(height: 16.h), SizedBox(height: 16.h),
const Divider(), const Divider(),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Text( Text(
'How to Update UPI ID', l10n.howToUpdateUpi,
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -628,11 +711,10 @@ class SettingsPage extends StatelessWidget {
), ),
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
_buildInfoStep(context, '1', 'Open backend/.env file'), _buildInfoStep(context, '1', l10n.stepOpenEnv),
_buildInfoStep(context, '2', 'Update PHONEPE_UPI_ID=your_upi@paytm'), _buildInfoStep(context, '2', l10n.stepUpdatePhonepe),
_buildInfoStep(context, '3', 'Restart backend server'), _buildInfoStep(context, '3', l10n.stepRestartBackend),
_buildInfoStep(context, '4', 'Refresh this screen'), _buildInfoStep(context, '4', l10n.stepRefreshScreen),
SizedBox(height: 16.h), SizedBox(height: 16.h),
Container( Container(
padding: EdgeInsets.all(12.w), padding: EdgeInsets.all(12.w),
@ -652,7 +734,7 @@ class SettingsPage extends StatelessWidget {
), ),
SizedBox(width: 8.w), SizedBox(width: 8.w),
Text( Text(
'Pro Tip', l10n.proTipTitle,
style: TextStyle( style: TextStyle(
fontSize: 13.sp, fontSize: 13.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -663,7 +745,7 @@ class SettingsPage extends StatelessWidget {
), ),
SizedBox(height: 8.h), SizedBox(height: 8.h),
Text( Text(
'Members can pay using ANY UPI app (PhonePe, GPay, Paytm) directly to your personal UPI ID with 0% transaction fees!', l10n.proTipBody,
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: scheme.onPrimaryContainer.withOpacity(0.92), color: scheme.onPrimaryContainer.withOpacity(0.92),
@ -679,7 +761,7 @@ class SettingsPage extends StatelessWidget {
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Get.back(), onPressed: () => Get.back(),
child: const Text('Close'), child: Text(l10n.close),
), ),
], ],
), ),
@ -730,7 +812,7 @@ class SettingsPage extends StatelessWidget {
); );
} }
Widget _buildAboutSettings(BuildContext context) { Widget _buildAboutSettings(BuildContext context, AppLocalizations l10n) {
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
return Card( return Card(
child: Column( child: Column(
@ -749,7 +831,7 @@ class SettingsPage extends StatelessWidget {
), ),
), ),
title: Text( title: Text(
'App Version', l10n.appVersionTitle,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
), ),
subtitle: Text( subtitle: Text(
@ -757,9 +839,7 @@ class SettingsPage extends StatelessWidget {
style: TextStyle(fontSize: 14.sp), style: TextStyle(fontSize: 14.sp),
), ),
), ),
Divider(height: 1.h, indent: 72.w), Divider(height: 1.h, indent: 72.w),
ListTile( ListTile(
leading: Container( leading: Container(
padding: EdgeInsets.all(10.w), padding: EdgeInsets.all(10.w),
@ -774,17 +854,15 @@ class SettingsPage extends StatelessWidget {
), ),
), ),
title: Text( title: Text(
'Privacy Policy', l10n.privacyPolicyTitle,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
), ),
trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16.w), trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16.w),
onTap: () { onTap: () {
SnackbarUtil.showInfo('Privacy policy page coming soon'); SnackbarUtil.showInfo(l10n.privacyComingSoon);
}, },
), ),
Divider(height: 1.h, indent: 72.w), Divider(height: 1.h, indent: 72.w),
ListTile( ListTile(
leading: Container( leading: Container(
padding: EdgeInsets.all(10.w), padding: EdgeInsets.all(10.w),
@ -799,12 +877,12 @@ class SettingsPage extends StatelessWidget {
), ),
), ),
title: Text( title: Text(
'Terms of Service', l10n.termsOfServiceTitle,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
), ),
trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16.w), trailing: Icon(Icons.arrow_forward_ios_rounded, size: 16.w),
onTap: () { onTap: () {
SnackbarUtil.showInfo('Terms of service page coming soon'); SnackbarUtil.showInfo(l10n.termsComingSoon);
}, },
), ),
], ],
@ -812,15 +890,15 @@ class SettingsPage extends StatelessWidget {
); );
} }
Widget _buildLogoutButton(BuildContext context) { Widget _buildLogoutButton(BuildContext context, AppLocalizations l10n) {
return SizedBox( return SizedBox(
width: double.infinity, width: double.infinity,
height: 52.h, height: 52.h,
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () => _showLogoutDialog(context), onPressed: () => _showLogoutDialog(context, l10n),
icon: Icon(Icons.logout_rounded, size: 20.w), icon: Icon(Icons.logout_rounded, size: 20.w),
label: Text( label: Text(
'Logout', l10n.logout,
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600), style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w600),
), ),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
@ -834,16 +912,16 @@ class SettingsPage extends StatelessWidget {
); );
} }
void _showThemeDialog(BuildContext context) { void _showThemeDialog(BuildContext context, AppLocalizations l10n) {
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
Get.dialog( Get.dialog(
AlertDialog( AlertDialog(
title: const Text('Choose Theme'), title: Text(l10n.chooseThemeTitle),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
RadioListTile<ThemeMode>( RadioListTile<ThemeMode>(
title: const Text('Light'), title: Text(l10n.themeLight),
value: ThemeMode.light, value: ThemeMode.light,
groupValue: ThemeController.to.themeMode, groupValue: ThemeController.to.themeMode,
onChanged: (value) { onChanged: (value) {
@ -853,7 +931,7 @@ class SettingsPage extends StatelessWidget {
activeColor: scheme.primary, activeColor: scheme.primary,
), ),
RadioListTile<ThemeMode>( RadioListTile<ThemeMode>(
title: const Text('Dark'), title: Text(l10n.themeDark),
value: ThemeMode.dark, value: ThemeMode.dark,
groupValue: ThemeController.to.themeMode, groupValue: ThemeController.to.themeMode,
onChanged: (value) { onChanged: (value) {
@ -863,7 +941,7 @@ class SettingsPage extends StatelessWidget {
activeColor: scheme.primary, activeColor: scheme.primary,
), ),
RadioListTile<ThemeMode>( RadioListTile<ThemeMode>(
title: const Text('System Default'), title: Text(l10n.themeSystem),
value: ThemeMode.system, value: ThemeMode.system,
groupValue: ThemeController.to.themeMode, groupValue: ThemeController.to.themeMode,
onChanged: (value) { onChanged: (value) {
@ -878,32 +956,31 @@ class SettingsPage extends StatelessWidget {
); );
} }
void _showLogoutDialog(BuildContext context) { void _showLogoutDialog(BuildContext context, AppLocalizations l10n) {
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: const Text('Logout'), title: Text(l10n.logoutConfirmTitle),
content: const Text('Are you sure you want to logout?'), content: Text(l10n.logoutConfirmMessage),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: const Text('Cancel'), child: Text(l10n.cancel),
), ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
AuthService.to.logout(); AuthService.to.logout();
SnackbarUtil.showSuccess('Logged out successfully'); SnackbarUtil.showSuccess(l10n.loggedOutSuccess);
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.error, backgroundColor: Theme.of(context).colorScheme.error,
foregroundColor: Theme.of(context).colorScheme.onError, foregroundColor: Theme.of(context).colorScheme.onError,
), ),
child: const Text('Logout'), child: Text(l10n.logout),
), ),
], ],
), ),
); );
} }
} }

View File

@ -11,16 +11,18 @@ import 'import_existing_group_dialog.dart';
import 'member_selection_dialog.dart'; import 'member_selection_dialog.dart';
import 'group_details_page.dart'; import 'group_details_page.dart';
import 'combined_draw_dialog.dart'; import 'combined_draw_dialog.dart';
import '../../l10n/l10n_x.dart';
class ChitGroupsPage extends StatelessWidget { class ChitGroupsPage extends StatelessWidget {
const ChitGroupsPage({super.key}); const ChitGroupsPage({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l = context.l10n;
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
return AppScaffold( return AppScaffold(
title: 'My Chitfunds', title: l.pageMyChitfunds,
actions: [ actions: [
PopupMenuButton<String>( PopupMenuButton<String>(
icon: Icon(Icons.add, size: 24.w), icon: Icon(Icons.add, size: 24.w),
@ -38,7 +40,7 @@ class ChitGroupsPage extends StatelessWidget {
children: [ children: [
Icon(Icons.add_circle, color: scheme.primary, size: 20.w), Icon(Icons.add_circle, color: scheme.primary, size: 20.w),
SizedBox(width: 12.w), SizedBox(width: 12.w),
const Text('Create New Group'), Text(l.createNewGroupMenu),
], ],
), ),
), ),
@ -48,7 +50,7 @@ class ChitGroupsPage extends StatelessWidget {
children: [ children: [
Icon(Icons.upload, color: scheme.secondary, size: 20.w), Icon(Icons.upload, color: scheme.secondary, size: 20.w),
SizedBox(width: 12.w), SizedBox(width: 12.w),
const Text('Import Existing Group'), Text(l.importExistingGroupMenu),
], ],
), ),
), ),
@ -62,8 +64,6 @@ class ChitGroupsPage extends StatelessWidget {
isLoading: service.isLoading.value, isLoading: service.isLoading.value,
isEmpty: service.chitGroups.isEmpty, isEmpty: service.chitGroups.isEmpty,
emptyType: EmptyStateType.noGroups, emptyType: EmptyStateType.noGroups,
emptyTitle: 'No chit groups yet',
emptyMessage: 'Create a new chit group or import an existing one.',
onRetry: () => service.loadManagerChitGroups(), onRetry: () => service.loadManagerChitGroups(),
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: () => service.loadManagerChitGroups(), onRefresh: () => service.loadManagerChitGroups(),

View File

@ -19,12 +19,15 @@ import 'import_existing_group_dialog.dart';
import 'members_page.dart'; import 'members_page.dart';
import '../../test_animated_draw.dart'; import '../../test_animated_draw.dart';
import '../../features/recordings/recordings_page.dart'; import '../../features/recordings/recordings_page.dart';
import '../../l10n/app_localizations.dart';
import '../../l10n/l10n_x.dart';
class ManagerDashboard extends StatelessWidget { class ManagerDashboard extends StatelessWidget {
const ManagerDashboard({super.key}); const ManagerDashboard({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l = context.l10n;
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
// Initialize services // Initialize services
Get.put(ChitGroupService()); Get.put(ChitGroupService());
@ -40,7 +43,7 @@ class ManagerDashboard extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Dashboard', style: AppText.title(context)), title: Text(l.dashboardTitle, style: AppText.title(context)),
actions: [ actions: [
// Notifications with badge // Notifications with badge
Obx(() { Obx(() {
@ -51,13 +54,13 @@ class ManagerDashboard extends StatelessWidget {
child: Icon(Icons.notifications_rounded, size: 24.w), child: Icon(Icons.notifications_rounded, size: 24.w),
), ),
onPressed: () => Get.to(() => const NotificationCenterPage()), onPressed: () => Get.to(() => const NotificationCenterPage()),
tooltip: 'Notifications', tooltip: l.notificationsTooltip,
); );
}), }),
IconButton( IconButton(
icon: Icon(Icons.videocam, size: 24.w), icon: Icon(Icons.videocam, size: 24.w),
onPressed: () => Get.to(() => const RecordingsPage()), onPressed: () => Get.to(() => const RecordingsPage()),
tooltip: 'View Draw Recordings', tooltip: l.recordingsTooltip,
), ),
IconButton( IconButton(
icon: Icon(Icons.logout, size: 24.w), icon: Icon(Icons.logout, size: 24.w),
@ -81,7 +84,7 @@ class ManagerDashboard extends StatelessWidget {
backgroundColor: scheme.tertiary, backgroundColor: scheme.tertiary,
foregroundColor: scheme.onTertiary, foregroundColor: scheme.onTertiary,
child: const Icon(Icons.casino), child: const Icon(Icons.casino),
tooltip: 'Test Animated Draw', tooltip: l.testDrawTooltip,
), ),
); );
} }
@ -94,8 +97,6 @@ class ManagerDashboard extends StatelessWidget {
isLoading: service.isLoading.value, isLoading: service.isLoading.value,
isEmpty: service.chitGroups.isEmpty, isEmpty: service.chitGroups.isEmpty,
emptyType: EmptyStateType.noGroups, emptyType: EmptyStateType.noGroups,
emptyTitle: 'No chit groups yet',
emptyMessage: 'Create your first group to get started.',
onRetry: () => service.loadManagerChitGroups(), onRetry: () => service.loadManagerChitGroups(),
loading: const SkeletonDashboard(), loading: const SkeletonDashboard(),
child: RefreshIndicator( child: RefreshIndicator(
@ -109,9 +110,9 @@ class ManagerDashboard extends StatelessWidget {
children: [ children: [
_buildWelcomeSection(context), _buildWelcomeSection(context),
SizedBox(height: 24.h), SizedBox(height: 24.h),
_buildRecentActivitiesSection(), _buildRecentActivitiesSection(context),
SizedBox(height: 24.h), SizedBox(height: 24.h),
_buildQuickActionsSection(), _buildQuickActionsSection(context),
], ],
), ),
), ),
@ -142,7 +143,7 @@ class ManagerDashboard extends StatelessWidget {
children: [ children: [
_buildWelcomeSection(context), _buildWelcomeSection(context),
SizedBox(height: 24.h), SizedBox(height: 24.h),
_buildDesktopActivitiesAndActions(), _buildDesktopActivitiesAndActions(context),
], ],
), ),
), ),
@ -176,7 +177,7 @@ class ManagerDashboard extends StatelessWidget {
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
Obx(() => Text( Obx(() => Text(
AuthService.to.currentUser.value?.fullName ?? 'Manager', AuthService.to.currentUser.value?.fullName ?? context.l10n.managerFallbackName,
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -188,7 +189,7 @@ class ManagerDashboard extends StatelessWidget {
)), )),
SizedBox(height: 4.h), SizedBox(height: 4.h),
Text( Text(
'Chit Fund Manager', context.l10n.chitFundManagerRole,
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: scheme.onSurfaceVariant, color: scheme.onSurfaceVariant,
@ -206,38 +207,38 @@ class ManagerDashboard extends StatelessWidget {
children: [ children: [
_buildMenuItem( _buildMenuItem(
icon: Icons.dashboard, icon: Icons.dashboard,
title: 'Dashboard', title: context.l10n.menuDashboard,
isSelected: true, isSelected: true,
onTap: () {}, onTap: () {},
), ),
_buildMenuItem( _buildMenuItem(
icon: Icons.group, icon: Icons.group,
title: 'My Chitfunds', title: context.l10n.menuMyChitfunds,
onTap: () => Get.to(() => const ChitGroupsPage()), onTap: () => Get.to(() => const ChitGroupsPage()),
), ),
_buildMenuItem( _buildMenuItem(
icon: Icons.people, icon: Icons.people,
title: 'Members', title: context.l10n.menuMembers,
onTap: () {}, onTap: () {},
), ),
_buildMenuItem( _buildMenuItem(
icon: Icons.payment, icon: Icons.payment,
title: 'Payments', title: context.l10n.menuPayments,
onTap: () {}, onTap: () {},
), ),
_buildMenuItem( _buildMenuItem(
icon: Icons.casino, icon: Icons.casino,
title: 'Lottery Draws', title: context.l10n.menuLotteryDraws,
onTap: () {}, onTap: () {},
), ),
_buildMenuItem( _buildMenuItem(
icon: Icons.analytics, icon: Icons.analytics,
title: 'Reports', title: context.l10n.menuReports,
onTap: () {}, onTap: () {},
), ),
_buildMenuItem( _buildMenuItem(
icon: Icons.settings, icon: Icons.settings,
title: 'Settings', title: context.l10n.settingsTitle,
onTap: () => Get.to(() => const SettingsPage()), onTap: () => Get.to(() => const SettingsPage()),
), ),
], ],
@ -277,16 +278,17 @@ class ManagerDashboard extends StatelessWidget {
} }
Widget _buildWelcomeSection(BuildContext context) { Widget _buildWelcomeSection(BuildContext context) {
final l = context.l10n;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Welcome back!', l.welcomeBackTitle,
style: AppText.headline(context), style: AppText.headline(context),
), ),
SizedBox(height: 8.h), SizedBox(height: 8.h),
Text( Text(
'Here\'s what\'s happening with your chit funds today.', l.welcomeBackSubtitle,
style: AppText.bodyMuted(context), style: AppText.bodyMuted(context),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -305,7 +307,8 @@ class ManagerDashboard extends StatelessWidget {
return SizedBox.shrink(); return SizedBox.shrink();
} }
Widget _buildQuickActionsSection() { Widget _buildQuickActionsSection(BuildContext context) {
final l = context.l10n;
return Card( return Card(
child: Padding( child: Padding(
padding: EdgeInsets.all(20.w), padding: EdgeInsets.all(20.w),
@ -313,7 +316,7 @@ class ManagerDashboard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
'Quick Actions', l.quickActionsTitle,
style: TextStyle( style: TextStyle(
fontSize: 18.sp, fontSize: 18.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -323,36 +326,36 @@ class ManagerDashboard extends StatelessWidget {
Column( Column(
children: [ children: [
QuickActionCard( QuickActionCard(
title: 'Create New Chitfund', title: l.qaCreateChitfundTitle,
subtitle: 'Start a new chit fund group', subtitle: l.qaCreateChitfundSubtitle,
icon: Icons.group_add, icon: Icons.group_add,
color: Colors.green.shade600, color: Colors.green.shade600,
onTap: () => _showCreateGroupDialog(Get.context!), onTap: () => _showCreateGroupDialog(context),
), ),
QuickActionCard( QuickActionCard(
title: 'Import Existing Chitfund', title: l.qaImportChitfundTitle,
subtitle: 'Add a group that already started', subtitle: l.qaImportChitfundSubtitle,
icon: Icons.upload, icon: Icons.upload,
color: Colors.blue.shade600, color: Colors.blue.shade600,
onTap: () => _showImportGroupDialog(Get.context!), onTap: () => _showImportGroupDialog(context),
), ),
QuickActionCard( QuickActionCard(
title: 'View All Chitfunds', title: l.qaViewAllChitfundsTitle,
subtitle: 'Manage your existing groups', subtitle: l.qaViewAllChitfundsSubtitle,
icon: Icons.list, icon: Icons.list,
color: Colors.teal.shade600, color: Colors.teal.shade600,
onTap: () => _navigateToGroups(), onTap: () => _navigateToGroups(),
), ),
QuickActionCard( QuickActionCard(
title: 'Manage Members', title: l.qaManageMembersTitle,
subtitle: 'Add or remove members', subtitle: l.qaManageMembersSubtitle,
icon: Icons.people, icon: Icons.people,
color: Colors.orange.shade600, color: Colors.orange.shade600,
onTap: () => _navigateToMembers(), onTap: () => _navigateToMembers(),
), ),
QuickActionCard( QuickActionCard(
title: 'Payment Records', title: l.qaPaymentRecordsTitle,
subtitle: 'Track all transactions', subtitle: l.qaPaymentRecordsSubtitle,
icon: Icons.payment, icon: Icons.payment,
color: Colors.purple.shade600, color: Colors.purple.shade600,
onTap: () => _navigateToPayments(), onTap: () => _navigateToPayments(),
@ -365,7 +368,8 @@ class ManagerDashboard extends StatelessWidget {
); );
} }
Widget _buildRecentActivitiesSection() { Widget _buildRecentActivitiesSection(BuildContext context) {
final l = context.l10n;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -373,7 +377,7 @@ class ManagerDashboard extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'My Chitfunds', l.sectionMyChitfunds,
style: TextStyle( style: TextStyle(
fontSize: 18.sp, fontSize: 18.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -382,7 +386,7 @@ class ManagerDashboard extends StatelessWidget {
TextButton.icon( TextButton.icon(
onPressed: _navigateToGroups, onPressed: _navigateToGroups,
icon: Icon(Icons.arrow_forward, size: 18.w), icon: Icon(Icons.arrow_forward, size: 18.w),
label: Text('View All', style: TextStyle(fontSize: 14.sp)), label: Text(l.viewAll, style: TextStyle(fontSize: 14.sp)),
), ),
], ],
), ),
@ -395,7 +399,7 @@ class ManagerDashboard extends StatelessWidget {
padding: EdgeInsets.all(32.w), padding: EdgeInsets.all(32.w),
child: Center( child: Center(
child: Text( child: Text(
'No chit funds yet', l.noChitFundsYetShort,
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: Colors.grey.shade600, color: Colors.grey.shade600,
@ -408,7 +412,7 @@ class ManagerDashboard extends StatelessWidget {
return Column( return Column(
children: groups.map((group) => Padding( children: groups.map((group) => Padding(
padding: EdgeInsets.only(bottom: 12.h), padding: EdgeInsets.only(bottom: 12.h),
child: _buildGroupQuickCard(group), child: _buildGroupQuickCard(context, group),
)).toList(), )).toList(),
); );
}), }),
@ -416,7 +420,8 @@ class ManagerDashboard extends StatelessWidget {
); );
} }
Widget _buildGroupQuickCard(dynamic group) { Widget _buildGroupQuickCard(BuildContext context, dynamic group) {
final l = context.l10n;
final status = group.status ?? 'forming'; final status = group.status ?? 'forming';
final statusColor = _getStatusColor(status); final statusColor = _getStatusColor(status);
@ -447,7 +452,7 @@ class ManagerDashboard extends StatelessWidget {
border: Border.all(color: statusColor, width: 1.5), border: Border.all(color: statusColor, width: 1.5),
), ),
child: Text( child: Text(
_getStatusText(status), _getStatusText(l, status),
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -492,7 +497,7 @@ class ManagerDashboard extends StatelessWidget {
Get.to(() => const ChitGroupsPage()); Get.to(() => const ChitGroupsPage());
}, },
icon: Icon(Icons.payment, size: 18.w), icon: Icon(Icons.payment, size: 18.w),
label: Text('Record', style: TextStyle(fontSize: 13.sp)), label: Text(l.actionRecord, style: TextStyle(fontSize: 13.sp)),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.green.shade600, backgroundColor: Colors.green.shade600,
foregroundColor: Colors.white, foregroundColor: Colors.white,
@ -508,7 +513,7 @@ class ManagerDashboard extends StatelessWidget {
Get.to(() => const ChitGroupsPage()); Get.to(() => const ChitGroupsPage());
}, },
icon: Icon(Icons.casino, size: 18.w), icon: Icon(Icons.casino, size: 18.w),
label: Text('Draw', style: TextStyle(fontSize: 13.sp)), label: Text(l.actionDraw, style: TextStyle(fontSize: 13.sp)),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.purple.shade600, backgroundColor: Colors.purple.shade600,
foregroundColor: Colors.white, foregroundColor: Colors.white,
@ -526,7 +531,7 @@ class ManagerDashboard extends StatelessWidget {
padding: EdgeInsets.symmetric(vertical: 10.h), padding: EdgeInsets.symmetric(vertical: 10.h),
side: BorderSide(color: Colors.grey.shade400), side: BorderSide(color: Colors.grey.shade400),
), ),
child: Text('View', style: TextStyle(fontSize: 13.sp)), child: Text(l.actionView, style: TextStyle(fontSize: 13.sp)),
), ),
), ),
], ],
@ -540,7 +545,7 @@ class ManagerDashboard extends StatelessWidget {
Get.to(() => const ChitGroupsPage()); Get.to(() => const ChitGroupsPage());
}, },
icon: Icon(Icons.arrow_forward, size: 18.w), icon: Icon(Icons.arrow_forward, size: 18.w),
label: Text('Manage Group', style: TextStyle(fontSize: 14.sp)), label: Text(l.actionManageGroup, style: TextStyle(fontSize: 14.sp)),
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
padding: EdgeInsets.symmetric(vertical: 12.h), padding: EdgeInsets.symmetric(vertical: 12.h),
), ),
@ -583,14 +588,14 @@ class ManagerDashboard extends StatelessWidget {
} }
} }
String _getStatusText(String status) { String _getStatusText(AppLocalizations l, String status) {
switch (status.toLowerCase()) { switch (status.toLowerCase()) {
case 'active': case 'active':
return 'Active'; return l.groupStatusActive;
case 'forming': case 'forming':
return 'Forming'; return l.groupStatusForming;
case 'completed': case 'completed':
return 'Completed'; return l.groupStatusCompleted;
default: default:
return status; return status;
} }
@ -656,16 +661,16 @@ class ManagerDashboard extends StatelessWidget {
); );
} }
Widget _buildDesktopActivitiesAndActions() { Widget _buildDesktopActivitiesAndActions(BuildContext context) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// My Chitfunds (full width on desktop) // My Chitfunds (full width on desktop)
_buildRecentActivitiesSection(), _buildRecentActivitiesSection(context),
SizedBox(height: 24.h), SizedBox(height: 24.h),
// Quick Actions (below chitfunds) // Quick Actions (below chitfunds)
_buildQuickActionsSection(), _buildQuickActionsSection(context),
], ],
); );
} }
@ -674,27 +679,30 @@ class ManagerDashboard extends StatelessWidget {
void _showLogoutDialog(BuildContext context) { void _showLogoutDialog(BuildContext context) {
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (dialogContext) {
title: const Text('Logout'), final l = dialogContext.l10n;
content: const Text('Are you sure you want to logout?'), return AlertDialog(
title: Text(l.logoutConfirmTitle),
content: Text(l.logoutConfirmMessage),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(dialogContext).pop(),
child: const Text('Cancel'), child: Text(l.cancel),
), ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(dialogContext).pop();
AuthService.to.logout(); AuthService.to.logout();
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.red, backgroundColor: Colors.red,
foregroundColor: Colors.white, foregroundColor: Colors.white,
), ),
child: const Text('Logout'), child: Text(l.logout),
), ),
], ],
), );
},
); );
} }
@ -712,8 +720,8 @@ class ManagerDashboard extends StatelessWidget {
// Reload groups after successful import // Reload groups after successful import
ChitGroupService.to.loadManagerChitGroups(); ChitGroupService.to.loadManagerChitGroups();
SnackbarUtil.showSuccess( SnackbarUtil.showSuccess(
'Group imported! Now add members and backfill past data.', L10nSvc.of().groupImportedMessage,
title: 'Success', title: L10nSvc.of().groupImportedTitle,
); );
} }
}); });
@ -729,8 +737,8 @@ class ManagerDashboard extends StatelessWidget {
void _navigateToPayments() { void _navigateToPayments() {
SnackbarUtil.showInfo( SnackbarUtil.showInfo(
'Payments page will be implemented next', L10nSvc.of().paymentsPageComingSoon,
title: 'Coming Soon', title: L10nSvc.of().comingSoonTitle,
); );
} }
} }

View File

@ -10,6 +10,8 @@ import '../../shared/widgets/notification_badge.dart';
import '../../features/notifications/notification_center_page.dart'; import '../../features/notifications/notification_center_page.dart';
import 'member_group_details_page.dart'; import 'member_group_details_page.dart';
import 'member_payment_dialog.dart'; import 'member_payment_dialog.dart';
import '../../l10n/app_localizations.dart';
import '../../l10n/l10n_x.dart';
class MemberDashboard extends StatefulWidget { class MemberDashboard extends StatefulWidget {
const MemberDashboard({super.key}); const MemberDashboard({super.key});
@ -38,12 +40,13 @@ class _MemberDashboardState extends State<MemberDashboard> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l = context.l10n;
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
return Scaffold( return Scaffold(
backgroundColor: scheme.surface, backgroundColor: scheme.surface,
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
'My Chitfunds', l.pageMyChitfunds,
style: TextStyle( style: TextStyle(
fontSize: 18.sp, fontSize: 18.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -63,7 +66,7 @@ class _MemberDashboardState extends State<MemberDashboard> {
child: Icon(Icons.notifications_rounded, size: 24.w), child: Icon(Icons.notifications_rounded, size: 24.w),
), ),
onPressed: () => Get.to(() => const NotificationCenterPage()), onPressed: () => Get.to(() => const NotificationCenterPage()),
tooltip: 'Notifications', tooltip: l.notificationsTooltip,
); );
}), }),
IconButton( IconButton(
@ -116,7 +119,9 @@ class _MemberDashboardState extends State<MemberDashboard> {
SizedBox(width: 12.w), SizedBox(width: 12.w),
Expanded( Expanded(
child: Text( child: Text(
'Welcome, ${AuthService.to.currentUser.value?.fullName ?? 'Member'}!', l.memberWelcomeGreeting(
AuthService.to.currentUser.value?.fullName ?? l.memberFallbackName,
),
style: TextStyle( style: TextStyle(
fontSize: 20.sp, fontSize: 20.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -128,9 +133,7 @@ class _MemberDashboardState extends State<MemberDashboard> {
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
Text( Text(
groups.isEmpty groups.isEmpty ? l.memberSubtitleEmpty : l.memberSubtitleHasGroups,
? 'Join a chit fund to start managing your investments.'
: 'Manage your chit fund investments and track your payments.',
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
color: scheme.onPrimaryContainer.withOpacity(0.92), color: scheme.onPrimaryContainer.withOpacity(0.92),
@ -157,7 +160,7 @@ class _MemberDashboardState extends State<MemberDashboard> {
if (!isLoading && groups.isNotEmpty) ...[ if (!isLoading && groups.isNotEmpty) ...[
Text( Text(
'My Chitfunds', l.sectionMyChitfunds,
style: TextStyle( style: TextStyle(
fontSize: 20.sp, fontSize: 20.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -187,31 +190,31 @@ class _MemberDashboardState extends State<MemberDashboard> {
unselectedLabelStyle: TextStyle(fontSize: 12.sp), unselectedLabelStyle: TextStyle(fontSize: 12.sp),
iconSize: 24.w, iconSize: 24.w,
items: [ items: [
const BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.home), icon: const Icon(Icons.home),
label: 'Home', label: l.navHome,
), ),
const BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.payment), icon: const Icon(Icons.payment),
label: 'Payments', label: l.navPayments,
), ),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: NotificationBadge( icon: NotificationBadge(
count: unreadCount, count: unreadCount,
child: const Icon(Icons.notifications), child: const Icon(Icons.notifications),
), ),
label: 'Notifications', label: l.navNotifications,
), ),
const BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.person), icon: const Icon(Icons.person),
label: 'Profile', label: l.navProfile,
), ),
], ],
onTap: (index) { onTap: (index) {
if (index == 2) { if (index == 2) {
Get.to(() => const NotificationCenterPage()); Get.to(() => const NotificationCenterPage());
} else { } else {
SnackbarUtil.showInfo('Feature coming soon'); SnackbarUtil.showInfo(l.featureComingSoonMessage);
} }
}, },
); );
@ -220,6 +223,7 @@ class _MemberDashboardState extends State<MemberDashboard> {
} }
Widget _buildEmptyState(BuildContext context) { Widget _buildEmptyState(BuildContext context) {
final l = context.l10n;
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
return Center( return Center(
child: Padding( child: Padding(
@ -234,7 +238,7 @@ class _MemberDashboardState extends State<MemberDashboard> {
), ),
SizedBox(height: 24.h), SizedBox(height: 24.h),
Text( Text(
'No Chit Funds Yet', l.memberEmptyChitTitle,
style: TextStyle( style: TextStyle(
fontSize: 22.sp, fontSize: 22.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -243,7 +247,7 @@ class _MemberDashboardState extends State<MemberDashboard> {
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
Text( Text(
'You haven\'t joined any chit funds yet.\nContact your manager to get started!', l.memberEmptyChitBody,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
@ -270,7 +274,7 @@ class _MemberDashboardState extends State<MemberDashboard> {
), ),
SizedBox(width: 8.w), SizedBox(width: 8.w),
Text( Text(
'How to get started?', l.memberHowToStartTitle,
style: TextStyle( style: TextStyle(
fontSize: 16.sp, fontSize: 16.sp,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -281,9 +285,7 @@ class _MemberDashboardState extends State<MemberDashboard> {
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
Text( Text(
'1. Your manager will add you to a chit group\n' l.memberHowToStartBody,
'2. You\'ll receive a notification\n'
'3. Start managing your payments here!',
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: scheme.onSurfaceVariant, color: scheme.onSurfaceVariant,
@ -300,9 +302,10 @@ class _MemberDashboardState extends State<MemberDashboard> {
} }
Widget _buildGroupCard(BuildContext context, dynamic group) { Widget _buildGroupCard(BuildContext context, dynamic group) {
final l = context.l10n;
final scheme = Theme.of(context).colorScheme; final scheme = Theme.of(context).colorScheme;
final color = scheme.primary; final color = scheme.primary;
final groupName = group.name ?? 'Unnamed Group'; final groupName = group.name ?? l.unnamedGroupLong;
final totalValue = group.totalValue ?? 0.0; final totalValue = group.totalValue ?? 0.0;
final monthlyInstallment = group.monthlyInstallment ?? 0.0; final monthlyInstallment = group.monthlyInstallment ?? 0.0;
final durationMonths = group.durationMonths ?? 0; final durationMonths = group.durationMonths ?? 0;
@ -343,7 +346,7 @@ class _MemberDashboardState extends State<MemberDashboard> {
border: Border.all(color: _getStatusColor(context, status), width: 1.5), border: Border.all(color: _getStatusColor(context, status), width: 1.5),
), ),
child: Text( child: Text(
_getStatusText(status), _memberStatusLabel(l, status),
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: _getStatusColor(context, status), color: _getStatusColor(context, status),
@ -359,11 +362,15 @@ class _MemberDashboardState extends State<MemberDashboard> {
Row( Row(
children: [ children: [
Expanded( Expanded(
child: _buildGroupInfo(context, 'Total Value', totalValueFormatted), child: _buildGroupInfo(context, l.labelTotalValue, totalValueFormatted),
), ),
SizedBox(width: 8.w), SizedBox(width: 8.w),
Expanded( Expanded(
child: _buildGroupInfo(context, 'Duration', '$durationMonths months'), child: _buildGroupInfo(
context,
l.labelDuration,
'$durationMonths ${l.monthsSuffix}',
),
), ),
], ],
), ),
@ -371,11 +378,11 @@ class _MemberDashboardState extends State<MemberDashboard> {
Row( Row(
children: [ children: [
Expanded( Expanded(
child: _buildGroupInfo(context, 'Installment', installmentValueFormatted), child: _buildGroupInfo(context, l.labelInstallment, installmentValueFormatted),
), ),
SizedBox(width: 8.w), SizedBox(width: 8.w),
Expanded( Expanded(
child: _buildGroupInfo(context, 'Status', _getStatusText(status)), child: _buildGroupInfo(context, l.labelStatus, _memberStatusLabel(l, status)),
), ),
], ],
), ),
@ -393,7 +400,7 @@ class _MemberDashboardState extends State<MemberDashboard> {
onPressed: () => _showPaymentDialog(group), onPressed: () => _showPaymentDialog(group),
icon: Icon(Icons.payment, size: 20.w), icon: Icon(Icons.payment, size: 20.w),
label: Text( label: Text(
'Pay Now', l.payNowButton,
style: TextStyle( style: TextStyle(
fontSize: 15.sp, fontSize: 15.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -424,7 +431,7 @@ class _MemberDashboardState extends State<MemberDashboard> {
), ),
), ),
child: Text( child: Text(
'Details', l.detailsButton,
style: TextStyle( style: TextStyle(
fontSize: 15.sp, fontSize: 15.sp,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -449,7 +456,7 @@ class _MemberDashboardState extends State<MemberDashboard> {
); );
if (myMember == null) { if (myMember == null) {
SnackbarUtil.showError('Member information not found'); SnackbarUtil.showError(context.l10n.memberInfoNotFound);
return; return;
} }
@ -481,9 +488,20 @@ class _MemberDashboardState extends State<MemberDashboard> {
} }
} }
String _getStatusText(String status) { String _memberStatusLabel(AppLocalizations l, String status) {
switch (status.toLowerCase()) {
case 'active':
return l.groupStatusActive;
case 'pending':
case 'forming':
return l.groupStatusPending;
case 'completed':
return l.groupStatusCompleted;
default:
if (status.isEmpty) return status;
return status[0].toUpperCase() + status.substring(1).toLowerCase(); return status[0].toUpperCase() + status.substring(1).toLowerCase();
} }
}
String _formatCurrency(double amount) { String _formatCurrency(double amount) {
// Format number with Indian comma notation // Format number with Indian comma notation
@ -532,27 +550,30 @@ class _MemberDashboardState extends State<MemberDashboard> {
void _showLogoutDialog(BuildContext context) { void _showLogoutDialog(BuildContext context) {
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (dialogContext) {
title: const Text('Logout'), final l = dialogContext.l10n;
content: const Text('Are you sure you want to logout?'), return AlertDialog(
title: Text(l.logoutConfirmTitle),
content: Text(l.logoutConfirmMessage),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(dialogContext).pop(),
child: const Text('Cancel'), child: Text(l.cancel),
), ),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(dialogContext).pop();
AuthService.to.logout(); AuthService.to.logout();
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.error, backgroundColor: Theme.of(dialogContext).colorScheme.error,
foregroundColor: Theme.of(context).colorScheme.onError, foregroundColor: Theme.of(dialogContext).colorScheme.onError,
), ),
child: const Text('Logout'), child: Text(l.logout),
), ),
], ],
), );
},
); );
} }
} }

View File

@ -0,0 +1,69 @@
{
"@@locale": "en",
"appTitle": "LuckyChit",
"settingsTitle": "Settings",
"sectionAppearance": "Appearance",
"sectionLanguage": "Language",
"sectionAccount": "Account",
"sectionPaymentSettings": "Payment Settings",
"sectionNotifications": "Notifications",
"sectionAbout": "About",
"languageTitle": "App language",
"languageSubtitle": "English or Telugu",
"languageEnglish": "English",
"languageTelugu": "Telugu (తెలుగు)",
"chooseLanguageTitle": "Choose language",
"themeTitle": "Theme",
"themeLight": "Light",
"themeDark": "Dark",
"themeSystem": "System default",
"chooseThemeTitle": "Choose theme",
"darkModeTitle": "Dark mode",
"darkModeSubtitle": "Override system settings",
"changePasswordTitle": "Change password",
"changePasswordSubtitle": "Update your password",
"pushNotificationsTitle": "Push notifications",
"pushNotificationsSubtitle": "Receive push notifications",
"paymentRemindersTitle": "Payment reminders",
"paymentRemindersSubtitle": "Reminders for upcoming payments",
"drawNotificationsTitle": "Draw notifications",
"drawNotificationsSubtitle": "Alerts for lottery draws",
"upiIdTitle": "UPI ID",
"loading": "Loading…",
"active": "Active",
"notConfigured": "Not configured",
"configureBackend": "Configure in backend/.env",
"copyUpiIdTooltip": "Copy UPI ID",
"upiCopiedClipboard": "UPI ID copied to clipboard",
"paymentStatisticsTitle": "Payment statistics",
"paymentStatisticsSubtitle": "View payment insights",
"transactionFeesTitle": "Transaction fees",
"transactionFeesSubtitle": "0% fees • Save lakhs per year!",
"free": "FREE",
"appVersionTitle": "App version",
"privacyPolicyTitle": "Privacy policy",
"termsOfServiceTitle": "Terms of service",
"logout": "Logout",
"logoutConfirmTitle": "Logout",
"logoutConfirmMessage": "Are you sure you want to logout?",
"cancel": "Cancel",
"close": "Close",
"copied": "Copied!",
"profileComingSoon": "Profile page coming soon",
"changePasswordComingSoon": "Change password feature coming soon",
"notificationComingSoon": "Notification settings coming soon",
"paymentStatsComingSoon": "Payment statistics coming soon",
"privacyComingSoon": "Privacy policy page coming soon",
"termsComingSoon": "Terms of service page coming soon",
"loggedOutSuccess": "Logged out successfully",
"upiPaymentSettingsTitle": "UPI payment settings",
"currentUpiId": "Current UPI ID",
"upiNotConfiguredMessage": "UPI ID not configured. Update backend/.env file.",
"howToUpdateUpi": "How to update UPI ID",
"stepOpenEnv": "Open backend/.env file",
"stepUpdatePhonepe": "Update PHONEPE_UPI_ID=your_upi@paytm",
"stepRestartBackend": "Restart backend server",
"stepRefreshScreen": "Refresh this screen",
"proTipTitle": "Pro tip",
"proTipBody": "Members can pay using ANY UPI app (PhonePe, GPay, Paytm) directly to your personal UPI ID with 0% transaction fees!"
}

View File

@ -0,0 +1,694 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart' as intl;
import 'app_localizations_en.dart';
import 'app_localizations_te.dart';
// ignore_for_file: type=lint
/// Callers can lookup localized strings with an instance of AppLocalizations
/// returned by `AppLocalizations.of(context)`.
///
/// Applications need to include `AppLocalizations.delegate()` in their app's
/// `localizationDelegates` list, and the locales they support in the app's
/// `supportedLocales` list. For example:
///
/// ```dart
/// import 'l10n/app_localizations.dart';
///
/// return MaterialApp(
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
/// supportedLocales: AppLocalizations.supportedLocales,
/// home: MyApplicationHome(),
/// );
/// ```
///
/// ## Update pubspec.yaml
///
/// Please make sure to update your pubspec.yaml to include the following
/// packages:
///
/// ```yaml
/// dependencies:
/// # Internationalization support.
/// flutter_localizations:
/// sdk: flutter
/// intl: any # Use the pinned version from flutter_localizations
///
/// # Rest of dependencies
/// ```
///
/// ## iOS Applications
///
/// iOS applications define key application metadata, including supported
/// locales, in an Info.plist file that is built into the application bundle.
/// To configure the locales supported by your app, youll need to edit this
/// file.
///
/// First, open your projects ios/Runner.xcworkspace Xcode workspace file.
/// Then, in the Project Navigator, open the Info.plist file under the Runner
/// projects Runner folder.
///
/// Next, select the Information Property List item, select Add Item from the
/// Editor menu, then select Localizations from the pop-up menu.
///
/// Select and expand the newly-created Localizations item then, for each
/// locale your application supports, add a new item and select the locale
/// you wish to add from the pop-up menu in the Value field. This list should
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
/// property.
abstract class AppLocalizations {
AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString());
final String localeName;
static AppLocalizations? of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
/// A list of this localizations delegate along with the default localizations
/// delegates.
///
/// Returns a list of localizations delegates containing this delegate along with
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
/// and GlobalWidgetsLocalizations.delegate.
///
/// Additional delegates can be added by appending to this list in
/// MaterialApp. This list does not have to be used at all if a custom list
/// of delegates is preferred or required.
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[
Locale('en'),
Locale('te')
];
/// No description provided for @appTitle.
///
/// In en, this message translates to:
/// **'LuckyChit'**
String get appTitle;
/// No description provided for @settingsTitle.
///
/// In en, this message translates to:
/// **'Settings'**
String get settingsTitle;
/// No description provided for @sectionAppearance.
///
/// In en, this message translates to:
/// **'Appearance'**
String get sectionAppearance;
/// No description provided for @sectionLanguage.
///
/// In en, this message translates to:
/// **'Language'**
String get sectionLanguage;
/// No description provided for @sectionAccount.
///
/// In en, this message translates to:
/// **'Account'**
String get sectionAccount;
/// No description provided for @sectionPaymentSettings.
///
/// In en, this message translates to:
/// **'Payment Settings'**
String get sectionPaymentSettings;
/// No description provided for @sectionNotifications.
///
/// In en, this message translates to:
/// **'Notifications'**
String get sectionNotifications;
/// No description provided for @sectionAbout.
///
/// In en, this message translates to:
/// **'About'**
String get sectionAbout;
/// No description provided for @languageTitle.
///
/// In en, this message translates to:
/// **'App language'**
String get languageTitle;
/// No description provided for @languageSubtitle.
///
/// In en, this message translates to:
/// **'English or Telugu'**
String get languageSubtitle;
/// No description provided for @languageEnglish.
///
/// In en, this message translates to:
/// **'English'**
String get languageEnglish;
/// No description provided for @languageTelugu.
///
/// In en, this message translates to:
/// **'Telugu (తెలుగు)'**
String get languageTelugu;
/// No description provided for @chooseLanguageTitle.
///
/// In en, this message translates to:
/// **'Choose language'**
String get chooseLanguageTitle;
/// No description provided for @themeTitle.
///
/// In en, this message translates to:
/// **'Theme'**
String get themeTitle;
/// No description provided for @themeLight.
///
/// In en, this message translates to:
/// **'Light'**
String get themeLight;
/// No description provided for @themeDark.
///
/// In en, this message translates to:
/// **'Dark'**
String get themeDark;
/// No description provided for @themeSystem.
///
/// In en, this message translates to:
/// **'System default'**
String get themeSystem;
/// No description provided for @chooseThemeTitle.
///
/// In en, this message translates to:
/// **'Choose theme'**
String get chooseThemeTitle;
/// No description provided for @darkModeTitle.
///
/// In en, this message translates to:
/// **'Dark mode'**
String get darkModeTitle;
/// No description provided for @darkModeSubtitle.
///
/// In en, this message translates to:
/// **'Override system settings'**
String get darkModeSubtitle;
/// No description provided for @changePasswordTitle.
///
/// In en, this message translates to:
/// **'Change password'**
String get changePasswordTitle;
/// No description provided for @changePasswordSubtitle.
///
/// In en, this message translates to:
/// **'Update your password'**
String get changePasswordSubtitle;
/// No description provided for @pushNotificationsTitle.
///
/// In en, this message translates to:
/// **'Push notifications'**
String get pushNotificationsTitle;
/// No description provided for @pushNotificationsSubtitle.
///
/// In en, this message translates to:
/// **'Receive push notifications'**
String get pushNotificationsSubtitle;
/// No description provided for @paymentRemindersTitle.
///
/// In en, this message translates to:
/// **'Payment reminders'**
String get paymentRemindersTitle;
/// No description provided for @paymentRemindersSubtitle.
///
/// In en, this message translates to:
/// **'Reminders for upcoming payments'**
String get paymentRemindersSubtitle;
/// No description provided for @drawNotificationsTitle.
///
/// In en, this message translates to:
/// **'Draw notifications'**
String get drawNotificationsTitle;
/// No description provided for @drawNotificationsSubtitle.
///
/// In en, this message translates to:
/// **'Alerts for lottery draws'**
String get drawNotificationsSubtitle;
/// No description provided for @upiIdTitle.
///
/// In en, this message translates to:
/// **'UPI ID'**
String get upiIdTitle;
/// No description provided for @loading.
///
/// In en, this message translates to:
/// **'Loading…'**
String get loading;
/// No description provided for @active.
///
/// In en, this message translates to:
/// **'Active'**
String get active;
/// No description provided for @notConfigured.
///
/// In en, this message translates to:
/// **'Not configured'**
String get notConfigured;
/// No description provided for @configureBackend.
///
/// In en, this message translates to:
/// **'Configure in backend/.env'**
String get configureBackend;
/// No description provided for @copyUpiIdTooltip.
///
/// In en, this message translates to:
/// **'Copy UPI ID'**
String get copyUpiIdTooltip;
/// No description provided for @upiCopiedClipboard.
///
/// In en, this message translates to:
/// **'UPI ID copied to clipboard'**
String get upiCopiedClipboard;
/// No description provided for @paymentStatisticsTitle.
///
/// In en, this message translates to:
/// **'Payment statistics'**
String get paymentStatisticsTitle;
/// No description provided for @paymentStatisticsSubtitle.
///
/// In en, this message translates to:
/// **'View payment insights'**
String get paymentStatisticsSubtitle;
/// No description provided for @transactionFeesTitle.
///
/// In en, this message translates to:
/// **'Transaction fees'**
String get transactionFeesTitle;
/// No description provided for @transactionFeesSubtitle.
///
/// In en, this message translates to:
/// **'0% fees • Save lakhs per year!'**
String get transactionFeesSubtitle;
/// No description provided for @free.
///
/// In en, this message translates to:
/// **'FREE'**
String get free;
/// No description provided for @appVersionTitle.
///
/// In en, this message translates to:
/// **'App version'**
String get appVersionTitle;
/// No description provided for @privacyPolicyTitle.
///
/// In en, this message translates to:
/// **'Privacy policy'**
String get privacyPolicyTitle;
/// No description provided for @termsOfServiceTitle.
///
/// In en, this message translates to:
/// **'Terms of service'**
String get termsOfServiceTitle;
/// No description provided for @logout.
///
/// In en, this message translates to:
/// **'Logout'**
String get logout;
/// No description provided for @logoutConfirmTitle.
///
/// In en, this message translates to:
/// **'Logout'**
String get logoutConfirmTitle;
/// No description provided for @logoutConfirmMessage.
///
/// In en, this message translates to:
/// **'Are you sure you want to logout?'**
String get logoutConfirmMessage;
/// No description provided for @cancel.
///
/// In en, this message translates to:
/// **'Cancel'**
String get cancel;
/// No description provided for @close.
///
/// In en, this message translates to:
/// **'Close'**
String get close;
/// No description provided for @copied.
///
/// In en, this message translates to:
/// **'Copied!'**
String get copied;
/// No description provided for @profileComingSoon.
///
/// In en, this message translates to:
/// **'Profile page coming soon'**
String get profileComingSoon;
/// No description provided for @changePasswordComingSoon.
///
/// In en, this message translates to:
/// **'Change password feature coming soon'**
String get changePasswordComingSoon;
/// No description provided for @notificationComingSoon.
///
/// In en, this message translates to:
/// **'Notification settings coming soon'**
String get notificationComingSoon;
/// No description provided for @paymentStatsComingSoon.
///
/// In en, this message translates to:
/// **'Payment statistics coming soon'**
String get paymentStatsComingSoon;
/// No description provided for @privacyComingSoon.
///
/// In en, this message translates to:
/// **'Privacy policy page coming soon'**
String get privacyComingSoon;
/// No description provided for @termsComingSoon.
///
/// In en, this message translates to:
/// **'Terms of service page coming soon'**
String get termsComingSoon;
/// No description provided for @loggedOutSuccess.
///
/// In en, this message translates to:
/// **'Logged out successfully'**
String get loggedOutSuccess;
/// No description provided for @upiPaymentSettingsTitle.
///
/// In en, this message translates to:
/// **'UPI payment settings'**
String get upiPaymentSettingsTitle;
/// No description provided for @currentUpiId.
///
/// In en, this message translates to:
/// **'Current UPI ID'**
String get currentUpiId;
/// No description provided for @upiNotConfiguredMessage.
///
/// In en, this message translates to:
/// **'UPI ID not configured. Update backend/.env file.'**
String get upiNotConfiguredMessage;
/// No description provided for @howToUpdateUpi.
///
/// In en, this message translates to:
/// **'How to update UPI ID'**
String get howToUpdateUpi;
/// No description provided for @stepOpenEnv.
///
/// In en, this message translates to:
/// **'Open backend/.env file'**
String get stepOpenEnv;
/// No description provided for @stepUpdatePhonepe.
///
/// In en, this message translates to:
/// **'Update PHONEPE_UPI_ID=your_upi@paytm'**
String get stepUpdatePhonepe;
/// No description provided for @stepRestartBackend.
///
/// In en, this message translates to:
/// **'Restart backend server'**
String get stepRestartBackend;
/// No description provided for @stepRefreshScreen.
///
/// In en, this message translates to:
/// **'Refresh this screen'**
String get stepRefreshScreen;
/// No description provided for @proTipTitle.
///
/// In en, this message translates to:
/// **'Pro tip'**
String get proTipTitle;
/// No description provided for @proTipBody.
///
/// In en, this message translates to:
/// **'Members can pay using ANY UPI app (PhonePe, GPay, Paytm) directly to your personal UPI ID with 0% transaction fees!'**
String get proTipBody;
String get ok;
String get save;
String get retry;
String get delete;
String get userLabel;
String get managerFallbackName;
String get snackTitleError;
String get snackTitleSuccess;
String get operationFailedShort;
String get failedLoadChitGroups;
String get chitfundCreatedSuccess;
String get failedCreateChitGroup;
String get failedUpdateChitGroup;
String get chitGroupDeletedSuccess;
String get failedDeleteChitGroup;
String get failedLoadGroupDetails;
String get failedLoadGroupMembers;
String get memberAddedSuccess;
String get failedAddMember;
String get memberRemovedSuccess;
String get failedRemoveMember;
String get memberStatusUpdatedSuccess;
String get failedUpdateMemberStatus;
String get failedLoadPayments;
String get paymentRecordedSuccess;
String get failedRecordPayment;
String get failedLoadGroupStatistics;
String get chitfundStartedSuccess;
String get failedStartChitGroup;
String get failedLoadMonthlyDraws;
String get failedCreateMonthlyDraw;
String get failedLoadDrawStatistics;
String get failedLoadFinancialData;
String get signupFailedTitle;
String get signupFailedGeneric;
String get loginFailedTitle;
String get loginFailedGeneric;
String get passwordChangedSuccess;
String get failedChangePassword;
String get stateSomethingWentWrong;
String get emptyNoGroupsTitle;
String get emptyNoGroupsMessage;
String get emptyNoGroupsAction;
String get emptyNoMembersTitle;
String get emptyNoMembersMessage;
String get emptyNoMembersAction;
String get emptyNoPaymentsTitle;
String get emptyNoPaymentsMessage;
String get emptyNoPaymentsAction;
String get emptyNoActivitiesTitle;
String get emptyNoActivitiesMessage;
String get emptyNoActivitiesAction;
String get emptyNoResultsTitle;
String get emptyNoResultsMessage;
String get emptyNoResultsAction;
String get emptyErrorTitle;
String get emptyErrorMessage;
String get emptyErrorAction;
String get emptyNoInternetTitle;
String get emptyNoInternetMessage;
String get emptyNoInternetAction;
String get dashboardTitle;
String get notificationsTooltip;
String get recordingsTooltip;
String get testDrawTooltip;
String get chitFundManagerRole;
String get menuDashboard;
String get menuMyChitfunds;
String get menuMembers;
String get menuPayments;
String get menuLotteryDraws;
String get menuReports;
String get welcomeBackTitle;
String get welcomeBackSubtitle;
String get quickActionsTitle;
String get qaCreateChitfundTitle;
String get qaCreateChitfundSubtitle;
String get qaImportChitfundTitle;
String get qaImportChitfundSubtitle;
String get qaViewAllChitfundsTitle;
String get qaViewAllChitfundsSubtitle;
String get qaManageMembersTitle;
String get qaManageMembersSubtitle;
String get qaPaymentRecordsTitle;
String get qaPaymentRecordsSubtitle;
String get sectionMyChitfunds;
String get viewAll;
String get noChitFundsYetShort;
String get groupStatusActive;
String get groupStatusForming;
String get groupStatusCompleted;
String get unnamedGroup;
String get actionRecord;
String get actionDraw;
String get actionView;
String get actionManageGroup;
String get groupImportedMessage;
String get groupImportedTitle;
String get paymentsPageComingSoon;
String get comingSoonTitle;
String get pageMyChitfunds;
String get createNewGroupMenu;
String get importExistingGroupMenu;
String get appDisplayName;
String get authLoginTagline;
String get authSignupScreenTitle;
String get authSignupTagline;
String get labelMobileNumber;
String get labelMobileNumberRequired;
String get labelPassword;
String get labelPasswordRequired;
String get labelFullNameRequired;
String get labelEmailOptional;
String get labelAddressOptional;
String get labelEmergencyContactOptional;
String get labelConfirmPasswordRequired;
String get validatorEnterMobile;
String get validatorMobileTenDigits;
String get validatorMobileDigitsOnly;
String get validatorEnterFullName;
String get validatorValidEmail;
String get validatorEmergencyTenDigits;
String get validatorEmergencyDigitsOnly;
String get validatorEnterPasswordAuth;
String get validatorPasswordMinSixAuth;
String get validatorConfirmPassword;
String get validatorPasswordsMismatch;
String get tooltipShowPassword;
String get tooltipHidePassword;
String get signInButton;
String get createAccountButton;
String get alreadyHaveAccount;
String get loginLink;
String get loginInvalidCredentials;
String get signupSuccessWelcome;
String get signupFailedGenericUi;
String get featureComingSoonMessage;
String memberWelcomeGreeting(String name);
String get memberFallbackName;
String get memberSubtitleEmpty;
String get memberSubtitleHasGroups;
String get navHome;
String get navPayments;
String get navNotifications;
String get navProfile;
String get memberEmptyChitTitle;
String get memberEmptyChitBody;
String get memberHowToStartTitle;
String get memberHowToStartBody;
String get unnamedGroupLong;
String get labelTotalValue;
String get labelDuration;
String get monthsSuffix;
String get labelInstallment;
String get labelStatus;
String get groupStatusPending;
String get payNowButton;
String get detailsButton;
String get memberInfoNotFound;
}
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
@override
Future<AppLocalizations> load(Locale locale) {
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
}
@override
bool isSupported(Locale locale) => <String>['en', 'te'].contains(locale.languageCode);
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
AppLocalizations lookupAppLocalizations(Locale locale) {
// Lookup logic when only language code is specified.
switch (locale.languageCode) {
case 'en': return AppLocalizationsEn();
case 'te': return AppLocalizationsTe();
}
throw FlutterError(
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.'
);
}

View File

@ -0,0 +1,706 @@
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for English (`en`).
class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale);
@override
String get appTitle => 'LuckyChit';
@override
String get settingsTitle => 'Settings';
@override
String get sectionAppearance => 'Appearance';
@override
String get sectionLanguage => 'Language';
@override
String get sectionAccount => 'Account';
@override
String get sectionPaymentSettings => 'Payment Settings';
@override
String get sectionNotifications => 'Notifications';
@override
String get sectionAbout => 'About';
@override
String get languageTitle => 'App language';
@override
String get languageSubtitle => 'English or Telugu';
@override
String get languageEnglish => 'English';
@override
String get languageTelugu => 'Telugu (తెలుగు)';
@override
String get chooseLanguageTitle => 'Choose language';
@override
String get themeTitle => 'Theme';
@override
String get themeLight => 'Light';
@override
String get themeDark => 'Dark';
@override
String get themeSystem => 'System default';
@override
String get chooseThemeTitle => 'Choose theme';
@override
String get darkModeTitle => 'Dark mode';
@override
String get darkModeSubtitle => 'Override system settings';
@override
String get changePasswordTitle => 'Change password';
@override
String get changePasswordSubtitle => 'Update your password';
@override
String get pushNotificationsTitle => 'Push notifications';
@override
String get pushNotificationsSubtitle => 'Receive push notifications';
@override
String get paymentRemindersTitle => 'Payment reminders';
@override
String get paymentRemindersSubtitle => 'Reminders for upcoming payments';
@override
String get drawNotificationsTitle => 'Draw notifications';
@override
String get drawNotificationsSubtitle => 'Alerts for lottery draws';
@override
String get upiIdTitle => 'UPI ID';
@override
String get loading => 'Loading…';
@override
String get active => 'Active';
@override
String get notConfigured => 'Not configured';
@override
String get configureBackend => 'Configure in backend/.env';
@override
String get copyUpiIdTooltip => 'Copy UPI ID';
@override
String get upiCopiedClipboard => 'UPI ID copied to clipboard';
@override
String get paymentStatisticsTitle => 'Payment statistics';
@override
String get paymentStatisticsSubtitle => 'View payment insights';
@override
String get transactionFeesTitle => 'Transaction fees';
@override
String get transactionFeesSubtitle => '0% fees • Save lakhs per year!';
@override
String get free => 'FREE';
@override
String get appVersionTitle => 'App version';
@override
String get privacyPolicyTitle => 'Privacy policy';
@override
String get termsOfServiceTitle => 'Terms of service';
@override
String get logout => 'Logout';
@override
String get logoutConfirmTitle => 'Logout';
@override
String get logoutConfirmMessage => 'Are you sure you want to logout?';
@override
String get cancel => 'Cancel';
@override
String get close => 'Close';
@override
String get copied => 'Copied!';
@override
String get profileComingSoon => 'Profile page coming soon';
@override
String get changePasswordComingSoon => 'Change password feature coming soon';
@override
String get notificationComingSoon => 'Notification settings coming soon';
@override
String get paymentStatsComingSoon => 'Payment statistics coming soon';
@override
String get privacyComingSoon => 'Privacy policy page coming soon';
@override
String get termsComingSoon => 'Terms of service page coming soon';
@override
String get loggedOutSuccess => 'Logged out successfully';
@override
String get upiPaymentSettingsTitle => 'UPI payment settings';
@override
String get currentUpiId => 'Current UPI ID';
@override
String get upiNotConfiguredMessage => 'UPI ID not configured. Update backend/.env file.';
@override
String get howToUpdateUpi => 'How to update UPI ID';
@override
String get stepOpenEnv => 'Open backend/.env file';
@override
String get stepUpdatePhonepe => 'Update PHONEPE_UPI_ID=your_upi@paytm';
@override
String get stepRestartBackend => 'Restart backend server';
@override
String get stepRefreshScreen => 'Refresh this screen';
@override
String get proTipTitle => 'Pro tip';
@override
String get proTipBody => 'Members can pay using ANY UPI app (PhonePe, GPay, Paytm) directly to your personal UPI ID with 0% transaction fees!';
@override
String get ok => 'OK';
@override
String get save => 'Save';
@override
String get retry => 'Retry';
@override
String get delete => 'Delete';
@override
String get userLabel => 'User';
@override
String get managerFallbackName => 'Manager';
@override
String get snackTitleError => 'Error';
@override
String get snackTitleSuccess => 'Success';
@override
String get operationFailedShort => 'Failed';
@override
String get failedLoadChitGroups => 'Failed to load chit groups';
@override
String get chitfundCreatedSuccess => 'Chitfund created successfully';
@override
String get failedCreateChitGroup => 'Failed to create chit group';
@override
String get failedUpdateChitGroup => 'Failed to update chit group';
@override
String get chitGroupDeletedSuccess => 'Chit group deleted successfully';
@override
String get failedDeleteChitGroup => 'Failed to delete chit group';
@override
String get failedLoadGroupDetails => 'Failed to load group details';
@override
String get failedLoadGroupMembers => 'Failed to load group members';
@override
String get memberAddedSuccess => 'Member added successfully';
@override
String get failedAddMember => 'Failed to add member';
@override
String get memberRemovedSuccess => 'Member removed successfully';
@override
String get failedRemoveMember => 'Failed to remove member';
@override
String get memberStatusUpdatedSuccess => 'Member status updated successfully';
@override
String get failedUpdateMemberStatus => 'Failed to update member status';
@override
String get failedLoadPayments => 'Failed to load payments';
@override
String get paymentRecordedSuccess => 'Payment recorded successfully';
@override
String get failedRecordPayment => 'Failed to record payment';
@override
String get failedLoadGroupStatistics => 'Failed to load group statistics';
@override
String get chitfundStartedSuccess => 'Chitfund started successfully';
@override
String get failedStartChitGroup => 'Failed to start chit group';
@override
String get failedLoadMonthlyDraws => 'Failed to load monthly draws';
@override
String get failedCreateMonthlyDraw => 'Failed to create monthly draw';
@override
String get failedLoadDrawStatistics => 'Failed to load draw statistics';
@override
String get failedLoadFinancialData => 'Failed to load financial data';
@override
String get signupFailedTitle => 'Signup Failed';
@override
String get signupFailedGeneric => 'Signup failed. Please try again.';
@override
String get loginFailedTitle => 'Login Failed';
@override
String get loginFailedGeneric => 'Login failed. Please try again.';
@override
String get passwordChangedSuccess => 'Password changed successfully';
@override
String get failedChangePassword => 'Failed to change password';
@override
String get stateSomethingWentWrong => 'Something went wrong';
@override
String get emptyNoGroupsTitle => 'No Chit Groups Yet';
@override
String get emptyNoGroupsMessage =>
'You haven\'t created any chit groups yet.\nCreate your first group or import an existing one!';
@override
String get emptyNoGroupsAction => 'Create Group';
@override
String get emptyNoMembersTitle => 'No Members Yet';
@override
String get emptyNoMembersMessage =>
'This group doesn\'t have any members yet.\nAdd members to get started!';
@override
String get emptyNoMembersAction => 'Add Members';
@override
String get emptyNoPaymentsTitle => 'No Payments Yet';
@override
String get emptyNoPaymentsMessage =>
'No payment records found.\nPayments will appear here once recorded.';
@override
String get emptyNoPaymentsAction => 'Record Payment';
@override
String get emptyNoActivitiesTitle => 'No Recent Activities';
@override
String get emptyNoActivitiesMessage =>
'Your recent activities will appear here.\nStart using the app to see updates!';
@override
String get emptyNoActivitiesAction => 'Refresh';
@override
String get emptyNoResultsTitle => 'No Results Found';
@override
String get emptyNoResultsMessage =>
'We couldn\'t find what you\'re looking for.\nTry adjusting your search or filters.';
@override
String get emptyNoResultsAction => 'Clear Filters';
@override
String get emptyErrorTitle => 'Oops! Something Went Wrong';
@override
String get emptyErrorMessage =>
'We encountered an error while loading data.\nPlease try again.';
@override
String get emptyErrorAction => 'Retry';
@override
String get emptyNoInternetTitle => 'No Internet Connection';
@override
String get emptyNoInternetMessage =>
'Please check your internet connection\nand try again.';
@override
String get emptyNoInternetAction => 'Retry';
@override
String get dashboardTitle => 'Dashboard';
@override
String get notificationsTooltip => 'Notifications';
@override
String get recordingsTooltip => 'View draw recordings';
@override
String get testDrawTooltip => 'Test animated draw';
@override
String get chitFundManagerRole => 'Chit Fund Manager';
@override
String get menuDashboard => 'Dashboard';
@override
String get menuMyChitfunds => 'My Chitfunds';
@override
String get menuMembers => 'Members';
@override
String get menuPayments => 'Payments';
@override
String get menuLotteryDraws => 'Lottery Draws';
@override
String get menuReports => 'Reports';
@override
String get welcomeBackTitle => 'Welcome back!';
@override
String get welcomeBackSubtitle =>
'Here\'s what\'s happening with your chit funds today.';
@override
String get quickActionsTitle => 'Quick Actions';
@override
String get qaCreateChitfundTitle => 'Create New Chitfund';
@override
String get qaCreateChitfundSubtitle => 'Start a new chit fund group';
@override
String get qaImportChitfundTitle => 'Import Existing Chitfund';
@override
String get qaImportChitfundSubtitle => 'Add a group that already started';
@override
String get qaViewAllChitfundsTitle => 'View All Chitfunds';
@override
String get qaViewAllChitfundsSubtitle => 'Manage your existing groups';
@override
String get qaManageMembersTitle => 'Manage Members';
@override
String get qaManageMembersSubtitle => 'Add or remove members';
@override
String get qaPaymentRecordsTitle => 'Payment Records';
@override
String get qaPaymentRecordsSubtitle => 'Track all transactions';
@override
String get sectionMyChitfunds => 'My Chitfunds';
@override
String get viewAll => 'View All';
@override
String get noChitFundsYetShort => 'No chit funds yet';
@override
String get groupStatusActive => 'Active';
@override
String get groupStatusForming => 'Forming';
@override
String get groupStatusCompleted => 'Completed';
@override
String get unnamedGroup => 'Unnamed';
@override
String get actionRecord => 'Record';
@override
String get actionDraw => 'Draw';
@override
String get actionView => 'View';
@override
String get actionManageGroup => 'Manage Group';
@override
String get groupImportedMessage =>
'Group imported! Now add members and backfill past data.';
@override
String get groupImportedTitle => 'Success';
@override
String get paymentsPageComingSoon => 'Payments page will be implemented next';
@override
String get comingSoonTitle => 'Coming Soon';
@override
String get pageMyChitfunds => 'My Chitfunds';
@override
String get createNewGroupMenu => 'Create New Group';
@override
String get importExistingGroupMenu => 'Import Existing Group';
@override
String get appDisplayName => 'LuckyChit';
@override
String get authLoginTagline =>
'Chit fund management that feels effortless.';
@override
String get authSignupScreenTitle => 'Create account';
@override
String get authSignupTagline => 'Set up your profile in under a minute.';
@override
String get labelMobileNumber => 'Mobile number';
@override
String get labelMobileNumberRequired => 'Mobile number *';
@override
String get labelPassword => 'Password';
@override
String get labelPasswordRequired => 'Password *';
@override
String get labelFullNameRequired => 'Full name *';
@override
String get labelEmailOptional => 'Email (optional)';
@override
String get labelAddressOptional => 'Address (optional)';
@override
String get labelEmergencyContactOptional => 'Emergency contact (optional)';
@override
String get labelConfirmPasswordRequired => 'Confirm password *';
@override
String get validatorEnterMobile => 'Please enter mobile number';
@override
String get validatorMobileTenDigits => 'Mobile number must be 10 digits';
@override
String get validatorMobileDigitsOnly =>
'Mobile number must contain only digits';
@override
String get validatorEnterFullName => 'Please enter your full name';
@override
String get validatorValidEmail => 'Please enter a valid email address';
@override
String get validatorEmergencyTenDigits =>
'Emergency contact must be 10 digits';
@override
String get validatorEmergencyDigitsOnly =>
'Emergency contact must contain only digits';
@override
String get validatorEnterPasswordAuth => 'Please enter password';
@override
String get validatorPasswordMinSixAuth =>
'Password must be at least 6 characters';
@override
String get validatorConfirmPassword => 'Please confirm password';
@override
String get validatorPasswordsMismatch => 'Passwords do not match';
@override
String get tooltipShowPassword => 'Show password';
@override
String get tooltipHidePassword => 'Hide password';
@override
String get signInButton => 'Sign in';
@override
String get createAccountButton => 'Create account';
@override
String get alreadyHaveAccount => 'Already have an account? ';
@override
String get loginLink => 'Login';
@override
String get loginInvalidCredentials =>
'Invalid mobile number or password. Please try again.';
@override
String get signupSuccessWelcome =>
'Account created successfully! Welcome to LuckyChit.';
@override
String get signupFailedGenericUi =>
'Failed to create account. Please try again.';
@override
String get featureComingSoonMessage => 'Feature coming soon';
@override
String memberWelcomeGreeting(String name) => 'Welcome, $name!';
@override
String get memberFallbackName => 'Member';
@override
String get memberSubtitleEmpty =>
'Join a chit fund to start managing your investments.';
@override
String get memberSubtitleHasGroups =>
'Manage your chit fund investments and track your payments.';
@override
String get navHome => 'Home';
@override
String get navPayments => 'Payments';
@override
String get navNotifications => 'Notifications';
@override
String get navProfile => 'Profile';
@override
String get memberEmptyChitTitle => 'No Chit Funds Yet';
@override
String get memberEmptyChitBody =>
'You haven\'t joined any chit funds yet.\nContact your manager to get started!';
@override
String get memberHowToStartTitle => 'How to get started?';
@override
String get memberHowToStartBody =>
'1. Your manager will add you to a chit group\n'
'2. You\'ll receive a notification\n'
'3. Start managing your payments here!';
@override
String get unnamedGroupLong => 'Unnamed Group';
@override
String get labelTotalValue => 'Total Value';
@override
String get labelDuration => 'Duration';
@override
String get monthsSuffix => 'months';
@override
String get labelInstallment => 'Installment';
@override
String get labelStatus => 'Status';
@override
String get groupStatusPending => 'Pending';
@override
String get payNowButton => 'Pay Now';
@override
String get detailsButton => 'Details';
@override
String get memberInfoNotFound => 'Member information not found';
}

View File

@ -0,0 +1,714 @@
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for Telugu (`te`).
class AppLocalizationsTe extends AppLocalizations {
AppLocalizationsTe([String locale = 'te']) : super(locale);
@override
String get appTitle => 'లక్కీచిట్';
@override
String get settingsTitle => 'సెట్టింగ్స్';
@override
String get sectionAppearance => 'రూపం';
@override
String get sectionLanguage => 'భాష';
@override
String get sectionAccount => 'ఖాతా';
@override
String get sectionPaymentSettings => 'చెల్లింపు సెట్టింగ్స్';
@override
String get sectionNotifications => 'నోటిఫికేషన్లు';
@override
String get sectionAbout => 'గురించి';
@override
String get languageTitle => 'యాప్ భాష';
@override
String get languageSubtitle => 'ఇంగ్లీషు లేదా తెలుగు';
@override
String get languageEnglish => 'ఇంగ్లీషు';
@override
String get languageTelugu => 'తెలుగు';
@override
String get chooseLanguageTitle => 'భాష ఎంచుకోండి';
@override
String get themeTitle => 'థీమ్';
@override
String get themeLight => 'లైట్';
@override
String get themeDark => 'డార్క్';
@override
String get themeSystem => 'సిస్టమ్ డిఫాల్ట్';
@override
String get chooseThemeTitle => 'థీమ్ ఎంచుకోండి';
@override
String get darkModeTitle => 'డార్క్ మోడ్';
@override
String get darkModeSubtitle => 'సిస్టమ్ సెట్టింగ్స్‌ను ఓవర్‌రైడ్ చేయండి';
@override
String get changePasswordTitle => 'పాస్‌వర్డ్ మార్చండి';
@override
String get changePasswordSubtitle => 'మీ పాస్‌వర్డ్‌ను నవీకరించండి';
@override
String get pushNotificationsTitle => 'పుష్ నోటిఫికేషన్లు';
@override
String get pushNotificationsSubtitle => 'పుష్ నోటిఫికేషన్లు అందుకోండి';
@override
String get paymentRemindersTitle => 'చెల్లింపు రిమైండర్లు';
@override
String get paymentRemindersSubtitle => 'రాబోయే చెల్లింపుల కోసం రిమైండర్లు';
@override
String get drawNotificationsTitle => 'డ్రా నోటిఫికేషన్లు';
@override
String get drawNotificationsSubtitle => 'లాటరీ డ్రాల కోసం అలర్ట్‌లు';
@override
String get upiIdTitle => 'UPI ID';
@override
String get loading => 'లోడ్ అవుతోంది…';
@override
String get active => 'సక్రియం';
@override
String get notConfigured => 'కాన్ఫిగర్ చేయలేదు';
@override
String get configureBackend => 'backend/.env లో కాన్ఫిగర్ చేయండి';
@override
String get copyUpiIdTooltip => 'UPI ID కాపీ చేయండి';
@override
String get upiCopiedClipboard => 'UPI ID క్లిప్‌బోర్డ్‌కు కాపీ అయింది';
@override
String get paymentStatisticsTitle => 'చెల్లింపు గణాంకాలు';
@override
String get paymentStatisticsSubtitle => 'చెల్లింపు అంతర్దృష్టులను చూడండి';
@override
String get transactionFeesTitle => 'లావాదేవీ ఫీజులు';
@override
String get transactionFeesSubtitle => '0% ఫీజులు • సంవత్సరానికి లక్షలు ఆదా!';
@override
String get free => 'ఉచితం';
@override
String get appVersionTitle => 'యాప్ వెర్షన్';
@override
String get privacyPolicyTitle => 'గోప్యతా విధానం';
@override
String get termsOfServiceTitle => 'సేవా నిబంధనలు';
@override
String get logout => 'లాగ్ అవుట్';
@override
String get logoutConfirmTitle => 'లాగ్ అవుట్';
@override
String get logoutConfirmMessage => 'మీరు ఖచ్చితంగా లాగ్ అవుట్ కావాలనుకుంటున్నారా?';
@override
String get cancel => 'రద్దు';
@override
String get close => 'మూసివేయి';
@override
String get copied => 'కాపీ అయింది!';
@override
String get profileComingSoon => 'ప్రొఫైల్ పేజీ త్వరలో వస్తుంది';
@override
String get changePasswordComingSoon => 'పాస్‌వర్డ్ మార్పు ఫీచర్ త్వరలో వస్తుంది';
@override
String get notificationComingSoon => 'నోటిఫికేషన్ సెట్టింగ్స్ త్వరలో వస్తాయి';
@override
String get paymentStatsComingSoon => 'చెల్లింపు గణాంకాలు త్వరలో వస్తాయి';
@override
String get privacyComingSoon => 'గోప్యతా విధానం పేజీ త్వరలో వస్తుంది';
@override
String get termsComingSoon => 'సేవా నిబంధనల పేజీ త్వరలో వస్తుంది';
@override
String get loggedOutSuccess => 'విజయవంతంగా లాగ్ అవుట్ అయ్యారు';
@override
String get upiPaymentSettingsTitle => 'UPI చెల్లింపు సెట్టింగ్స్';
@override
String get currentUpiId => 'ప్రస్తుత UPI ID';
@override
String get upiNotConfiguredMessage => 'UPI ID కాన్ఫిగర్ చేయలేదు. backend/.env ఫైల్‌ను నవీకరించండి.';
@override
String get howToUpdateUpi => 'UPI ID ఎలా నవీకరించాలి';
@override
String get stepOpenEnv => 'backend/.env ఫైల్‌ను తెరవండి';
@override
String get stepUpdatePhonepe => 'PHONEPE_UPI_ID=your_upi@paytm ను నవీకరించండి';
@override
String get stepRestartBackend => 'బ్యాకెండ్ సర్వర్‌ను రీస్టార్ట్ చేయండి';
@override
String get stepRefreshScreen => 'ఈ స్క్రీన్‌ను రిఫ్రెష్ చేయండి';
@override
String get proTipTitle => 'ప్రో టిప్';
@override
String get proTipBody => 'సభ్యులు ఏ UPI యాప్‌తోనైనా (PhonePe, GPay, Paytm) మీ వ్యక్తిగత UPI IDకు నేరుగా 0% లావాదేవీ ఫీజుతో చెల్లించవచ్చు!';
@override
String get ok => 'సరే';
@override
String get save => 'సేవ్';
@override
String get retry => 'మళ్లీ ప్రయత్నించండి';
@override
String get delete => 'తొలగించు';
@override
String get userLabel => 'వినియోగదారు';
@override
String get managerFallbackName => 'మేనేజర్';
@override
String get snackTitleError => 'లోపం';
@override
String get snackTitleSuccess => 'విజయం';
@override
String get operationFailedShort => 'విఫలమైంది';
@override
String get failedLoadChitGroups => 'చిట్ గ్రూప్‌లను లోడ్ చేయడంలో విఫలమైంది';
@override
String get chitfundCreatedSuccess => 'చిట్‌ఫండ్ విజయవంతంగా సృష్టించబడింది';
@override
String get failedCreateChitGroup => 'చిట్ గ్రూప్ సృష్టించడంలో విఫలమైంది';
@override
String get failedUpdateChitGroup => 'చిట్ గ్రూప్ నవీకరించడంలో విఫలమైంది';
@override
String get chitGroupDeletedSuccess => 'చిట్ గ్రూప్ విజయవంతంగా తొలగించబడింది';
@override
String get failedDeleteChitGroup => 'చిట్ గ్రూప్ తొలగించడంలో విఫలమైంది';
@override
String get failedLoadGroupDetails => 'గ్రూప్ వివరాలను లోడ్ చేయడంలో విఫలమైంది';
@override
String get failedLoadGroupMembers => 'గ్రూప్ సభ్యులను లోడ్ చేయడంలో విఫలమైంది';
@override
String get memberAddedSuccess => 'సభ్యుడు విజయవంతంగా జోడించబడ్డారు';
@override
String get failedAddMember => 'సభ్యుడిని జోడించడంలో విఫలమైంది';
@override
String get memberRemovedSuccess => 'సభ్యుడు విజయవంతంగా తొలగించబడ్డారు';
@override
String get failedRemoveMember => 'సభ్యుడిని తొలగించడంలో విఫలమైంది';
@override
String get memberStatusUpdatedSuccess => 'సభ్య స్థితి విజయవంతంగా నవీకరించబడింది';
@override
String get failedUpdateMemberStatus => 'సభ్య స్థితిని నవీకరించడంలో విఫలమైంది';
@override
String get failedLoadPayments => 'చెల్లింపులను లోడ్ చేయడంలో విఫలమైంది';
@override
String get paymentRecordedSuccess => 'చెల్లింపు విజయవంతంగా నమోదు చేయబడింది';
@override
String get failedRecordPayment => 'చెల్లింపు నమోదు చేయడంలో విఫలమైంది';
@override
String get failedLoadGroupStatistics => 'గ్రూప్ గణాంకాలను లోడ్ చేయడంలో విఫలమైంది';
@override
String get chitfundStartedSuccess => 'చిట్‌ఫండ్ విజయవంతంగా ప్రారంభమైంది';
@override
String get failedStartChitGroup => 'చిట్ గ్రూప్ ప్రారంభించడంలో విఫలమైంది';
@override
String get failedLoadMonthlyDraws => 'నెలవారీ డ్రాలను లోడ్ చేయడంలో విఫలమైంది';
@override
String get failedCreateMonthlyDraw => 'నెలవారీ డ్రా సృష్టించడంలో విఫలమైంది';
@override
String get failedLoadDrawStatistics => 'డ్రా గణాంకాలను లోడ్ చేయడంలో విఫలమైంది';
@override
String get failedLoadFinancialData => 'ఆర్థిక డేటాను లోడ్ చేయడంలో విఫలమైంది';
@override
String get signupFailedTitle => 'నమోదు విఫలమైంది';
@override
String get signupFailedGeneric => 'నమోదు విఫలమైంది. దయచేసి మళ్లీ ప్రయత్నించండి.';
@override
String get loginFailedTitle => 'లాగిన్ విఫలమైంది';
@override
String get loginFailedGeneric => 'లాగిన్ విఫలమైంది. దయచేసి మళ్లీ ప్రయత్నించండి.';
@override
String get passwordChangedSuccess => 'పాస్‌వర్డ్ విజయవంతంగా మార్చబడింది';
@override
String get failedChangePassword => 'పాస్‌వర్డ్ మార్చడంలో విఫలమైంది';
@override
String get stateSomethingWentWrong => 'ఏదో తప్పు జరిగింది';
@override
String get emptyNoGroupsTitle => 'ఇంకా చిట్ గ్రూప్‌లు లేవు';
@override
String get emptyNoGroupsMessage =>
'మీరు ఇంకా చిట్ గ్రూప్‌లు సృష్టించలేదు.\nమొదటి గ్రూప్ సృష్టించండి లేదా ఉన్నదాన్ని దిగుమతి చేయండి!';
@override
String get emptyNoGroupsAction => 'గ్రూప్ సృష్టించు';
@override
String get emptyNoMembersTitle => 'ఇంకా సభ్యులు లేరు';
@override
String get emptyNoMembersMessage =>
'ఈ గ్రూప్‌కు ఇంకా సభ్యులు లేరు.\nప్రారంభించడానికి సభ్యులను జోడించండి!';
@override
String get emptyNoMembersAction => 'సభ్యులను జోడించు';
@override
String get emptyNoPaymentsTitle => 'ఇంకా చెల్లింపులు లేవు';
@override
String get emptyNoPaymentsMessage =>
'చెల్లింపు రికార్డ్‌లు కనుగొనబడలేదు.\nనమోదు చేసిన తర్వాత ఇక్కడ కనిపిస్తాయి.';
@override
String get emptyNoPaymentsAction => 'చెల్లింపు నమోదు';
@override
String get emptyNoActivitiesTitle => 'ఇటీవలి కార్యకలాపాలు లేవు';
@override
String get emptyNoActivitiesMessage =>
'మీ ఇటీవలి కార్యకలాపాలు ఇక్కడ కనిపిస్తాయి.\nనవీకరణల కోసం యాప్‌ను ఉపయోగించండి!';
@override
String get emptyNoActivitiesAction => 'రిఫ్రెష్';
@override
String get emptyNoResultsTitle => 'ఫలితాలు లేవు';
@override
String get emptyNoResultsMessage =>
'మీరు వెతుకుతున్నది కనుగొనలేకపోయాము.\nశోధన లేదా ఫిల్టర్‌లను మార్చి ప్రయత్నించండి.';
@override
String get emptyNoResultsAction => 'ఫిల్టర్‌లు క్లియర్';
@override
String get emptyErrorTitle => 'అయ్యో! ఏదో తప్పు జరిగింది';
@override
String get emptyErrorMessage =>
'డేటా లోడ్ చేస్తూ లోపం వచ్చింది.\nదయచేసి మళ్లీ ప్రయత్నించండి.';
@override
String get emptyErrorAction => 'మళ్లీ ప్రయత్నించు';
@override
String get emptyNoInternetTitle => 'ఇంటర్నెట్ కనెక్షన్ లేదు';
@override
String get emptyNoInternetMessage =>
'దయచేసి మీ ఇంటర్నెట్ కనెక్షన్‌ను తనిఖీ చేసి\nమళ్లీ ప్రయత్నించండి.';
@override
String get emptyNoInternetAction => 'మళ్లీ ప్రయత్నించు';
@override
String get dashboardTitle => 'డాష్‌బోర్డ్';
@override
String get notificationsTooltip => 'నోటిఫికేషన్‌లు';
@override
String get recordingsTooltip => 'డ్రా రికార్డింగ్‌లు చూడండి';
@override
String get testDrawTooltip => 'యానిమేటెడ్ డ్రా పరీక్ష';
@override
String get chitFundManagerRole => 'చిట్ ఫండ్ మేనేజర్';
@override
String get menuDashboard => 'డాష్‌బోర్డ్';
@override
String get menuMyChitfunds => 'నా చిట్‌ఫండ్‌లు';
@override
String get menuMembers => 'సభ్యులు';
@override
String get menuPayments => 'చెల్లింపులు';
@override
String get menuLotteryDraws => 'లాటరీ డ్రాలు';
@override
String get menuReports => 'నివేదికలు';
@override
String get welcomeBackTitle => 'మళ్లీ స్వాగతం!';
@override
String get welcomeBackSubtitle =>
'ఈ రోజు మీ చిట్ ఫండ్‌లతో ఏమి జరుగుతోందో ఇక్కడ ఉంది.';
@override
String get quickActionsTitle => 'త్వరిత చర్యలు';
@override
String get qaCreateChitfundTitle => 'కొత్త చిట్‌ఫండ్ సృష్టించు';
@override
String get qaCreateChitfundSubtitle => 'కొత్త చిట్ ఫండ్ గ్రూప్ ప్రారంభించండి';
@override
String get qaImportChitfundTitle => 'ఉన్న చిట్‌ఫండ్ దిగుమతి';
@override
String get qaImportChitfundSubtitle => 'ఇప్పటికే ప్రారంభమైన గ్రూప్ జోడించండి';
@override
String get qaViewAllChitfundsTitle => 'అన్ని చిట్‌ఫండ్‌లు చూడండి';
@override
String get qaViewAllChitfundsSubtitle => 'మీ గ్రూప్‌లను నిర్వహించండి';
@override
String get qaManageMembersTitle => 'సభ్యులను నిర్వహించు';
@override
String get qaManageMembersSubtitle => 'సభ్యులను జోడించండి లేదా తొలగించండి';
@override
String get qaPaymentRecordsTitle => 'చెల్లింపు రికార్డ్‌లు';
@override
String get qaPaymentRecordsSubtitle => 'అన్ని లావాదేవీలను ట్రాక్ చేయండి';
@override
String get sectionMyChitfunds => 'నా చిట్‌ఫండ్‌లు';
@override
String get viewAll => 'అన్నీ చూడండి';
@override
String get noChitFundsYetShort => 'ఇంకా చిట్ ఫండ్‌లు లేవు';
@override
String get groupStatusActive => 'సక్రియం';
@override
String get groupStatusForming => 'ఏర్పాటులో';
@override
String get groupStatusCompleted => 'పూర్తయింది';
@override
String get unnamedGroup => 'పేరులేని';
@override
String get actionRecord => 'నమోదు';
@override
String get actionDraw => 'డ్రా';
@override
String get actionView => 'చూడండి';
@override
String get actionManageGroup => 'గ్రూప్ నిర్వహణ';
@override
String get groupImportedMessage =>
'గ్రూప్ దిగుమతి అయ్యింది! ఇప్పుడు సభ్యులను జోడించి గత డేటా నింపండి.';
@override
String get groupImportedTitle => 'విజయం';
@override
String get paymentsPageComingSoon =>
'చెల్లింపుల పేజీ తదుపరి అమలు చేయబడుతుంది';
@override
String get comingSoonTitle => 'త్వరలో';
@override
String get pageMyChitfunds => 'నా చిట్‌ఫండ్‌లు';
@override
String get createNewGroupMenu => 'కొత్త గ్రూప్ సృష్టించు';
@override
String get importExistingGroupMenu => 'ఉన్న గ్రూప్ దిగుమతి';
@override
String get appDisplayName => 'LuckyChit';
@override
String get authLoginTagline =>
'చిట్ ఫండ్ నిర్వహణ సులభంగా అనిపించేలా.';
@override
String get authSignupScreenTitle => 'ఖాతా సృష్టించు';
@override
String get authSignupTagline =>
'ఒక నిమిషంలో మీ ప్రొఫైల్ సెటప్ చేయండి.';
@override
String get labelMobileNumber => 'మొబైల్ నంబర్';
@override
String get labelMobileNumberRequired => 'మొబైల్ నంబర్ *';
@override
String get labelPassword => 'పాస్‌వర్డ్';
@override
String get labelPasswordRequired => 'పాస్‌వర్డ్ *';
@override
String get labelFullNameRequired => 'పూర్తి పేరు *';
@override
String get labelEmailOptional => 'ఇమెయిల్ (ఐచ్ఛికం)';
@override
String get labelAddressOptional => 'చిరునామా (ఐచ్ఛికం)';
@override
String get labelEmergencyContactOptional =>
'అత్యవసర సంప్రదింపు (ఐచ్ఛికం)';
@override
String get labelConfirmPasswordRequired => 'పాస్‌వర్డ్ నిర్ధారించు *';
@override
String get validatorEnterMobile => 'దయచేసి మొబైల్ నంబర్ నమోదు చేయండి';
@override
String get validatorMobileTenDigits =>
'మొబైల్ నంబర్ 10 అంకెలు ఉండాలి';
@override
String get validatorMobileDigitsOnly =>
'మొబైల్ నంబర్‌లో అంకెలు మాత్రమే ఉండాలి';
@override
String get validatorEnterFullName => 'దయచేసి మీ పూర్తి పేరు నమోదు చేయండి';
@override
String get validatorValidEmail =>
'దయచేసి సరైన ఇమెయిల్ చిరునామా నమోదు చేయండి';
@override
String get validatorEmergencyTenDigits =>
'అత్యవసర సంప్రదింపు 10 అంకెలు ఉండాలి';
@override
String get validatorEmergencyDigitsOnly =>
'అత్యవసర సంప్రదింపులో అంకెలు మాత్రమే ఉండాలి';
@override
String get validatorEnterPasswordAuth =>
'దయచేసి పాస్‌వర్డ్ నమోదు చేయండి';
@override
String get validatorPasswordMinSixAuth =>
'పాస్‌వర్డ్ కనీసం 6 అక్షరాలు ఉండాలి';
@override
String get validatorConfirmPassword =>
'దయచేసి పాస్‌వర్డ్ నిర్ధారించండి';
@override
String get validatorPasswordsMismatch =>
'పాస్‌వర్డ్‌లు సరిపోలడం లేదు';
@override
String get tooltipShowPassword => 'పాస్‌వర్డ్ చూపు';
@override
String get tooltipHidePassword => 'పాస్‌వర్డ్ దాచు';
@override
String get signInButton => 'సైన్ ఇన్';
@override
String get createAccountButton => 'ఖాతా సృష్టించు';
@override
String get alreadyHaveAccount => 'ఇప్పటికే ఖాతా ఉందా? ';
@override
String get loginLink => 'లాగిన్';
@override
String get loginInvalidCredentials =>
'తప్పు మొబైల్ నంబర్ లేదా పాస్‌వర్డ్. మళ్లీ ప్రయత్నించండి.';
@override
String get signupSuccessWelcome =>
'ఖాతా విజయవంతంగా సృష్టించబడింది! LuckyChitకు స్వాగతం.';
@override
String get signupFailedGenericUi =>
'ఖాతా సృష్టించడంలో విఫలమైంది. మళ్లీ ప్రయత్నించండి.';
@override
String get featureComingSoonMessage => 'ఫీచర్ త్వరలో వస్తుంది';
@override
String memberWelcomeGreeting(String name) => 'స్వాగతం, $name!';
@override
String get memberFallbackName => 'సభ్యుడు';
@override
String get memberSubtitleEmpty =>
'మీ పెట్టుబడులను నిర్వహించడానికి చిట్ ఫండ్‌లో చేరండి.';
@override
String get memberSubtitleHasGroups =>
'మీ చిట్ ఫండ్ పెట్టుబడులు మరియు చెల్లింపులను ట్రాక్ చేయండి.';
@override
String get navHome => 'హోమ్';
@override
String get navPayments => 'చెల్లింపులు';
@override
String get navNotifications => 'నోటిఫికేషన్‌లు';
@override
String get navProfile => 'ప్రొఫైల్';
@override
String get memberEmptyChitTitle => 'ఇంకా చిట్ ఫండ్‌లు లేవు';
@override
String get memberEmptyChitBody =>
'మీరు ఇంకా ఏ చిట్ ఫండ్‌లోనూ చేరలేదు.\nప్రారంభించడానికి మీ మేనేజర్‌ను సంప్రదించండి!';
@override
String get memberHowToStartTitle => 'ఎలా ప్రారంభించాలి?';
@override
String get memberHowToStartBody =>
'1. మీ మేనేజర్ మిమ్మల్ని చిట్ గ్రూప్‌కు జోడిస్తారు\n'
'2. మీకు నోటిఫికేషన్ వస్తుంది\n'
'3. ఇక్కడ మీ చెల్లింపులను నిర్వహించండి!';
@override
String get unnamedGroupLong => 'పేరులేని గ్రూప్';
@override
String get labelTotalValue => 'మొత్తం విలువ';
@override
String get labelDuration => 'కాలపరిమితి';
@override
String get monthsSuffix => 'నెలలు';
@override
String get labelInstallment => 'వాయిదా';
@override
String get labelStatus => 'స్థితి';
@override
String get groupStatusPending => 'పెండింగ్';
@override
String get payNowButton => 'ఇప్పుడు చెల్లించు';
@override
String get detailsButton => 'వివరాలు';
@override
String get memberInfoNotFound => 'సభ్య సమాచారం కనుగొనబడలేదు';
}

View File

@ -0,0 +1,69 @@
{
"@@locale": "te",
"appTitle": "లక్కీచిట్",
"settingsTitle": "సెట్టింగ్స్",
"sectionAppearance": "రూపం",
"sectionLanguage": "భాష",
"sectionAccount": "ఖాతా",
"sectionPaymentSettings": "చెల్లింపు సెట్టింగ్స్",
"sectionNotifications": "నోటిఫికేషన్లు",
"sectionAbout": "గురించి",
"languageTitle": "యాప్ భాష",
"languageSubtitle": "ఇంగ్లీషు లేదా తెలుగు",
"languageEnglish": "ఇంగ్లీషు",
"languageTelugu": "తెలుగు",
"chooseLanguageTitle": "భాష ఎంచుకోండి",
"themeTitle": "థీమ్",
"themeLight": "లైట్",
"themeDark": "డార్క్",
"themeSystem": "సిస్టమ్ డిఫాల్ట్",
"chooseThemeTitle": "థీమ్ ఎంచుకోండి",
"darkModeTitle": "డార్క్ మోడ్",
"darkModeSubtitle": "సిస్టమ్ సెట్టింగ్స్‌ను ఓవర్‌రైడ్ చేయండి",
"changePasswordTitle": "పాస్‌వర్డ్ మార్చండి",
"changePasswordSubtitle": "మీ పాస్‌వర్డ్‌ను నవీకరించండి",
"pushNotificationsTitle": "పుష్ నోటిఫికేషన్లు",
"pushNotificationsSubtitle": "పుష్ నోటిఫికేషన్లు అందుకోండి",
"paymentRemindersTitle": "చెల్లింపు రిమైండర్లు",
"paymentRemindersSubtitle": "రాబోయే చెల్లింపుల కోసం రిమైండర్లు",
"drawNotificationsTitle": "డ్రా నోటిఫికేషన్లు",
"drawNotificationsSubtitle": "లాటరీ డ్రాల కోసం అలర్ట్‌లు",
"upiIdTitle": "UPI ID",
"loading": "లోడ్ అవుతోంది…",
"active": "Active",
"notConfigured": "కాన్ఫిగర్ చేయలేదు",
"configureBackend": "backend/.env లో కాన్ఫిగర్ చేయండి",
"copyUpiIdTooltip": "UPI ID కాపీ చేయండి",
"upiCopiedClipboard": "UPI ID క్లిప్‌బోర్డ్‌కు కాపీ అయింది",
"paymentStatisticsTitle": "చెల్లింపు గణాంకాలు",
"paymentStatisticsSubtitle": "చెల్లింపు అంతర్దృష్టులను చూడండి",
"transactionFeesTitle": "లావాదేవీ ఫీజులు",
"transactionFeesSubtitle": "0% ఫీజులు • సంవత్సరానికి లక్షలు ఆదా!",
"free": "ఉచితం",
"appVersionTitle": "యాప్ వెర్షన్",
"privacyPolicyTitle": "గోప్యతా విధానం",
"termsOfServiceTitle": "సేవా నిబంధనలు",
"logout": "లాగ్ అవుట్",
"logoutConfirmTitle": "లాగ్ అవుట్",
"logoutConfirmMessage": "మీరు ఖచ్చితంగా లాగ్ అవుట్ కావాలనుకుంటున్నారా?",
"cancel": "రద్దు",
"close": "మూసివేయి",
"copied": "కాపీ అయింది!",
"profileComingSoon": "ప్రొఫైల్ పేజీ త్వరలో వస్తుంది",
"changePasswordComingSoon": "పాస్‌వర్డ్ మార్పు ఫీచర్ త్వరలో వస్తుంది",
"notificationComingSoon": "నోటిఫికేషన్ సెట్టింగ్స్ త్వరలో వస్తాయి",
"paymentStatsComingSoon": "చెల్లింపు గణాంకాలు త్వరలో వస్తాయి",
"privacyComingSoon": "గోప్యతా విధానం పేజీ త్వరలో వస్తుంది",
"termsComingSoon": "సేవా నిబంధనల పేజీ త్వరలో వస్తుంది",
"loggedOutSuccess": "విజయవంతంగా లాగ్ అవుట్ అయ్యారు",
"upiPaymentSettingsTitle": "UPI చెల్లింపు సెట్టింగ్స్",
"currentUpiId": "ప్రస్తుత UPI ID",
"upiNotConfiguredMessage": "UPI ID కాన్ఫిగర్ చేయలేదు. backend/.env ఫైల్‌ను నవీకరించండి.",
"howToUpdateUpi": "UPI ID ఎలా నవీకరించాలి",
"stepOpenEnv": "backend/.env ఫైల్‌ను తెరవండి",
"stepUpdatePhonepe": "PHONEPE_UPI_ID=your_upi@paytm ను నవీకరించండి",
"stepRestartBackend": "బ్యాకెండ్ సర్వర్‌ను రీస్టార్ట్ చేయండి",
"stepRefreshScreen": "ఈ స్క్రీన్‌ను రిఫ్రెష్ చేయండి",
"proTipTitle": "ప్రో టిప్",
"proTipBody": "సభ్యులు ఏ UPI యాప్‌తోనైనా (PhonePe, GPay, Paytm) మీ వ్యక్తిగత UPI IDకు నేరుగా 0% లావాదేవీ ఫీజుతో చెల్లించవచ్చు!"
}

View File

@ -0,0 +1,26 @@
import 'package:flutter/widgets.dart';
import 'package:get/get.dart';
import 'package:luckychit/l10n/app_localizations.dart';
import 'package:luckychit/l10n/app_localizations_en.dart';
/// Localizations from a [BuildContext] (widgets).
extension AppL10n on BuildContext {
AppLocalizations get l10n {
final l = AppLocalizations.of(this);
assert(l != null, 'AppLocalizations not found — check GetMaterialApp delegates');
return l!;
}
}
/// Localizations when there is no [BuildContext] (GetX services, etc.).
/// Falls back to English if [Get.context] is missing or not localized yet.
final class L10nSvc {
L10nSvc._();
static AppLocalizations of() {
final ctx = Get.context;
final l = ctx != null ? AppLocalizations.of(ctx) : null;
return l ?? AppLocalizationsEn();
}
}

View File

@ -4,11 +4,14 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'app.dart'; import 'app.dart';
import 'core/themes/app_theme.dart'; import 'core/themes/app_theme.dart';
import 'core/controllers/theme_controller.dart'; import 'core/controllers/theme_controller.dart';
import 'core/controllers/locale_controller.dart';
import 'l10n/app_localizations.dart';
void main() { Future<void> main() async {
// Initialize theme controller WidgetsFlutterBinding.ensureInitialized();
Get.put(ThemeController()); Get.put(ThemeController());
final localeController = Get.put(LocaleController());
await localeController.ensureLoaded();
runApp(const LuckyChitApp()); runApp(const LuckyChitApp());
} }
@ -18,17 +21,22 @@ class LuckyChitApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ScreenUtilInit( return ScreenUtilInit(
designSize: const Size(375, 812), // Mobile-first design size (iPhone X) designSize: const Size(375, 812),
minTextAdapt: true, minTextAdapt: true,
splitScreenMode: true, splitScreenMode: true,
builder: (context, child) { builder: (context, child) {
return GetMaterialApp( return Obx(
() => GetMaterialApp(
title: 'LuckyChit', title: 'LuckyChit',
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: AppTheme.lightTheme, theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme, darkTheme: AppTheme.darkTheme,
themeMode: ThemeController.to.themeMode, themeMode: ThemeController.to.themeMode,
locale: LocaleController.to.locale.value,
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
home: const App(), home: const App(),
),
); );
}, },
); );

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../l10n/l10n_x.dart';
enum EmptyStateType { enum EmptyStateType {
noGroups, noGroups,
noMembers, noMembers,
@ -31,7 +33,7 @@ class EmptyStateWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final config = _getEmptyStateConfig(type); final config = _getEmptyStateConfig(context, type);
return Center( return Center(
child: SingleChildScrollView( child: SingleChildScrollView(
@ -164,17 +166,17 @@ class EmptyStateWidget extends StatelessWidget {
); );
} }
_EmptyStateConfig _getEmptyStateConfig(EmptyStateType type) { _EmptyStateConfig _getEmptyStateConfig(BuildContext context, EmptyStateType type) {
final l = context.l10n;
switch (type) { switch (type) {
case EmptyStateType.noGroups: case EmptyStateType.noGroups:
return _EmptyStateConfig( return _EmptyStateConfig(
icon: Icons.group_add_rounded, icon: Icons.group_add_rounded,
iconColor: Colors.green.shade600, iconColor: Colors.green.shade600,
backgroundColor: Colors.green.shade50, backgroundColor: Colors.green.shade50,
title: 'No Chit Groups Yet', title: l.emptyNoGroupsTitle,
message: message: l.emptyNoGroupsMessage,
'You haven\'t created any chit groups yet.\nStart by creating your first group!', actionLabel: l.emptyNoGroupsAction,
actionLabel: 'Create Group',
actionIcon: Icons.add_circle_outline, actionIcon: Icons.add_circle_outline,
buttonColor: Colors.green.shade600, buttonColor: Colors.green.shade600,
); );
@ -184,10 +186,9 @@ class EmptyStateWidget extends StatelessWidget {
icon: Icons.people_outline_rounded, icon: Icons.people_outline_rounded,
iconColor: Colors.blue.shade600, iconColor: Colors.blue.shade600,
backgroundColor: Colors.blue.shade50, backgroundColor: Colors.blue.shade50,
title: 'No Members Yet', title: l.emptyNoMembersTitle,
message: message: l.emptyNoMembersMessage,
'This group doesn\'t have any members yet.\nAdd members to get started!', actionLabel: l.emptyNoMembersAction,
actionLabel: 'Add Members',
actionIcon: Icons.person_add, actionIcon: Icons.person_add,
buttonColor: Colors.blue.shade600, buttonColor: Colors.blue.shade600,
); );
@ -197,10 +198,9 @@ class EmptyStateWidget extends StatelessWidget {
icon: Icons.payment_rounded, icon: Icons.payment_rounded,
iconColor: Colors.orange.shade600, iconColor: Colors.orange.shade600,
backgroundColor: Colors.orange.shade50, backgroundColor: Colors.orange.shade50,
title: 'No Payments Yet', title: l.emptyNoPaymentsTitle,
message: message: l.emptyNoPaymentsMessage,
'No payment records found.\nPayments will appear here once recorded.', actionLabel: l.emptyNoPaymentsAction,
actionLabel: 'Record Payment',
actionIcon: Icons.add, actionIcon: Icons.add,
buttonColor: Colors.orange.shade600, buttonColor: Colors.orange.shade600,
); );
@ -210,10 +210,9 @@ class EmptyStateWidget extends StatelessWidget {
icon: Icons.history_rounded, icon: Icons.history_rounded,
iconColor: Colors.purple.shade600, iconColor: Colors.purple.shade600,
backgroundColor: Colors.purple.shade50, backgroundColor: Colors.purple.shade50,
title: 'No Recent Activities', title: l.emptyNoActivitiesTitle,
message: message: l.emptyNoActivitiesMessage,
'Your recent activities will appear here.\nStart using the app to see updates!', actionLabel: l.emptyNoActivitiesAction,
actionLabel: 'Refresh',
actionIcon: Icons.refresh, actionIcon: Icons.refresh,
buttonColor: Colors.purple.shade600, buttonColor: Colors.purple.shade600,
); );
@ -223,10 +222,9 @@ class EmptyStateWidget extends StatelessWidget {
icon: Icons.search_off_rounded, icon: Icons.search_off_rounded,
iconColor: Colors.grey.shade600, iconColor: Colors.grey.shade600,
backgroundColor: Colors.grey.shade100, backgroundColor: Colors.grey.shade100,
title: 'No Results Found', title: l.emptyNoResultsTitle,
message: message: l.emptyNoResultsMessage,
'We couldn\'t find what you\'re looking for.\nTry adjusting your search or filters.', actionLabel: l.emptyNoResultsAction,
actionLabel: 'Clear Filters',
actionIcon: Icons.clear_all, actionIcon: Icons.clear_all,
buttonColor: Colors.grey.shade600, buttonColor: Colors.grey.shade600,
); );
@ -236,10 +234,9 @@ class EmptyStateWidget extends StatelessWidget {
icon: Icons.error_outline_rounded, icon: Icons.error_outline_rounded,
iconColor: Colors.red.shade600, iconColor: Colors.red.shade600,
backgroundColor: Colors.red.shade50, backgroundColor: Colors.red.shade50,
title: 'Oops! Something Went Wrong', title: l.emptyErrorTitle,
message: message: l.emptyErrorMessage,
'We encountered an error while loading data.\nPlease try again.', actionLabel: l.emptyErrorAction,
actionLabel: 'Retry',
actionIcon: Icons.refresh, actionIcon: Icons.refresh,
buttonColor: Colors.red.shade600, buttonColor: Colors.red.shade600,
); );
@ -249,10 +246,9 @@ class EmptyStateWidget extends StatelessWidget {
icon: Icons.wifi_off_rounded, icon: Icons.wifi_off_rounded,
iconColor: Colors.red.shade600, iconColor: Colors.red.shade600,
backgroundColor: Colors.red.shade50, backgroundColor: Colors.red.shade50,
title: 'No Internet Connection', title: l.emptyNoInternetTitle,
message: message: l.emptyNoInternetMessage,
'Please check your internet connection\nand try again.', actionLabel: l.emptyNoInternetAction,
actionLabel: 'Retry',
actionIcon: Icons.refresh, actionIcon: Icons.refresh,
buttonColor: Colors.red.shade600, buttonColor: Colors.red.shade600,
); );

View File

@ -270,6 +270,11 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.0" version: "7.2.0"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:

View File

@ -30,6 +30,8 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_localizations:
sdk: flutter
# UI and Icons # UI and Icons