diff --git a/lib/l10n/app_am.arb b/lib/l10n/app_am.arb index a1ee383..7b49618 100644 --- a/lib/l10n/app_am.arb +++ b/lib/l10n/app_am.arb @@ -25,6 +25,7 @@ "secureSetup": "ደህንነት ማዋቀር", "masterPassword": "ዋና የይለፍ ቃል", + "createPinLabel": "ባለ 4-አሃዝ PIN ይፍጠሩ", "recoveryQuestions": "የማግኛ ጥያቄዎች", "birthCity": "የትውልድ ከተማ", "openingYear": "የመክፈቻ ዓመት (ኢትዮጵያ)", @@ -32,6 +33,8 @@ "dataSecureNote": "መረጃዎ በዚህ መሣሪያ ላይ ብቻ ተከማችቷል።", "passwordRequired": "የይለፍ ቃል ያስፈልጋል", "passwordMinLength": "ቢያንስ 4 ቁምፊዎች መሆን አለበት", + "pinExactDigits": "PIN በትክክል 4 አሃዞች መሆን አለበት", + "pinNumericOnly": "PIN ቁጥር ብቻ መሆን አለበት", "birthCityRequired": "የትውልድ ከተማ ያስፈልጋል", "yearRequired": "ዓመት ያስፈልጋል", "yearExactDigits": "በትክክል 4 አሃዞች መሆን አለበት", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 02742de..510c66e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -25,6 +25,7 @@ "secureSetup": "SECURE SETUP", "masterPassword": "Master Password", + "createPinLabel": "Create 4-Digit PIN", "recoveryQuestions": "RECOVERY QUESTIONS", "birthCity": "Birth City", "openingYear": "Opening Year (Ethiopian)", @@ -32,6 +33,8 @@ "dataSecureNote": "Your data is stored securely on this device only.", "passwordRequired": "Password is required", "passwordMinLength": "Must be at least 4 characters", + "pinExactDigits": "PIN must be exactly 4 digits", + "pinNumericOnly": "PIN must be numeric", "birthCityRequired": "Birth city is required", "yearRequired": "Year is required", "yearExactDigits": "Must be exactly 4 digits", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 4452a16..c37fbd8 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -226,6 +226,12 @@ abstract class AppLocalizations { /// **'Master Password'** String get masterPassword; + /// No description provided for @createPinLabel. + /// + /// In en, this message translates to: + /// **'Create 4-Digit PIN'** + String get createPinLabel; + /// No description provided for @recoveryQuestions. /// /// In en, this message translates to: @@ -268,6 +274,18 @@ abstract class AppLocalizations { /// **'Must be at least 4 characters'** String get passwordMinLength; + /// No description provided for @pinExactDigits. + /// + /// In en, this message translates to: + /// **'PIN must be exactly 4 digits'** + String get pinExactDigits; + + /// No description provided for @pinNumericOnly. + /// + /// In en, this message translates to: + /// **'PIN must be numeric'** + String get pinNumericOnly; + /// No description provided for @birthCityRequired. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_am.dart b/lib/l10n/app_localizations_am.dart index 823debf..62ff6e8 100644 --- a/lib/l10n/app_localizations_am.dart +++ b/lib/l10n/app_localizations_am.dart @@ -71,6 +71,9 @@ class AppLocalizationsAm extends AppLocalizations { @override String get masterPassword => 'ዋና የይለፍ ቃል'; + @override + String get createPinLabel => 'ባለ 4-አሃዝ PIN ይፍጠሩ'; + @override String get recoveryQuestions => 'የማግኛ ጥያቄዎች'; @@ -92,6 +95,12 @@ class AppLocalizationsAm extends AppLocalizations { @override String get passwordMinLength => 'ቢያንስ 4 ቁምፊዎች መሆን አለበት'; + @override + String get pinExactDigits => 'PIN በትክክል 4 አሃዞች መሆን አለበት'; + + @override + String get pinNumericOnly => 'PIN ቁጥር ብቻ መሆን አለበት'; + @override String get birthCityRequired => 'የትውልድ ከተማ ያስፈልጋል'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 844e124..98f05c6 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -71,6 +71,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get masterPassword => 'Master Password'; + @override + String get createPinLabel => 'Create 4-Digit PIN'; + @override String get recoveryQuestions => 'RECOVERY QUESTIONS'; @@ -93,6 +96,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get passwordMinLength => 'Must be at least 4 characters'; + @override + String get pinExactDigits => 'PIN must be exactly 4 digits'; + + @override + String get pinNumericOnly => 'PIN must be numeric'; + @override String get birthCityRequired => 'Birth city is required'; diff --git a/lib/l10n/app_localizations_om.dart b/lib/l10n/app_localizations_om.dart index d833384..8ea50fb 100644 --- a/lib/l10n/app_localizations_om.dart +++ b/lib/l10n/app_localizations_om.dart @@ -71,6 +71,9 @@ class AppLocalizationsOm extends AppLocalizations { @override String get masterPassword => 'Jecha Darbii Jalqabaa'; + @override + String get createPinLabel => 'PIN Lakkoofsa 4 Uumi'; + @override String get recoveryQuestions => 'GAAFFILEE DEEBISUU'; @@ -93,6 +96,12 @@ class AppLocalizationsOm extends AppLocalizations { @override String get passwordMinLength => 'Yoo xiqqaate arfii 4 ta\'uu qaba'; + @override + String get pinExactDigits => 'PIN lakkoofsa 4 qofa ta\'uu qaba'; + + @override + String get pinNumericOnly => 'PIN lakkoofsa qofa ta\'uu qaba'; + @override String get birthCityRequired => 'Magaalaan dhalootaa barbaachisaadha'; diff --git a/lib/l10n/app_om.arb b/lib/l10n/app_om.arb index 195eecd..1837bf8 100644 --- a/lib/l10n/app_om.arb +++ b/lib/l10n/app_om.arb @@ -25,6 +25,7 @@ "secureSetup": "QINDAA'INA NAGEENYA", "masterPassword": "Jecha Darbii Jalqabaa", + "createPinLabel": "PIN Lakkoofsa 4 Uumi", "recoveryQuestions": "GAAFFILEE DEEBISUU", "birthCity": "Magaalaa Dhalootaa", "openingYear": "Bara Banamsaa (Itoophiyaa)", @@ -32,6 +33,8 @@ "dataSecureNote": "Daataan keessan meeshaa kana qofa irratti kuufame.", "passwordRequired": "Jechni darbii barbaachisaadha", "passwordMinLength": "Yoo xiqqaate arfii 4 ta'uu qaba", + "pinExactDigits": "PIN lakkoofsa 4 qofa ta'uu qaba", + "pinNumericOnly": "PIN lakkoofsa qofa ta'uu qaba", "birthCityRequired": "Magaalaan dhalootaa barbaachisaadha", "yearRequired": "Barri barbaachisaadha", "yearExactDigits": "Lakkoofsa 4 qofa ta'uu qaba", diff --git a/lib/screens/splash_setup_screen.dart b/lib/screens/splash_setup_screen.dart index fbdece1..a832b31 100644 --- a/lib/screens/splash_setup_screen.dart +++ b/lib/screens/splash_setup_screen.dart @@ -25,7 +25,6 @@ class _SplashSetupScreenState extends State with SingleTicker late AnimationController _animationController; late Animation _fadeAnimation; - @override void initState() { super.initState(); @@ -46,7 +45,8 @@ class _SplashSetupScreenState extends State with SingleTicker _animationController.forward(); } else { if (mounted) { - Navigator.of(context).pushReplacement( + Navigator.pushReplacement( + context, MaterialPageRoute(builder: (_) => const LoginScreen()), ); } @@ -73,7 +73,8 @@ class _SplashSetupScreenState extends State with SingleTicker _q2Controller.text, ); if (mounted) { - Navigator.of(context).pushReplacement( + Navigator.pushReplacement( + context, PageRouteBuilder( transitionDuration: const Duration(milliseconds: 600), pageBuilder: (_, __, ___) => const DashboardScreen(), @@ -234,133 +235,127 @@ class _SplashSetupScreenState extends State with SingleTicker ); } - Widget _buildInitializeButton(AppLocalizations l) { - return Container( - width: double.infinity, - height: 56, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - gradient: LinearGradient( - colors: [AppTheme.primaryBlue, AppTheme.primaryBlue.withBlue(255)], - ), - boxShadow: [ - BoxShadow( - color: AppTheme.primaryBlue.withOpacity(0.3), - blurRadius: 15, - offset: const Offset(0, 8), - ) - ], - ), - child: ElevatedButton( - onPressed: _submitSetup, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(l.setUpMyShop, style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 16)), - const SizedBox(width: 12), - const Icon(Icons.arrow_forward_rounded, size: 20), + Widget _buildInitializeButton(AppLocalizations l) => Container( + width: double.infinity, + height: 56, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + gradient: LinearGradient( + colors: [AppTheme.primaryBlue, AppTheme.primaryBlue.withBlue(255)], + ), + boxShadow: [ + BoxShadow( + color: AppTheme.primaryBlue.withOpacity(0.3), + blurRadius: 15, + offset: const Offset(0, 8), + ) ], ), - ), - ); - } + child: ElevatedButton( + onPressed: _submitSetup, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(l.setUpMyShop, style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 16)), + const SizedBox(width: 12), + const Icon(Icons.arrow_forward_rounded, size: 20), + ], + ), + ), + ); - Widget _buildLogo(AppLocalizations l) { - return Column( - children: [ - Container( - padding: const EdgeInsets.all(28), + Widget _buildLogo(AppLocalizations l) => Column( + children: [ + Container( + padding: const EdgeInsets.all(28), decoration: BoxDecoration( - color: AppTheme.primaryBlue.withOpacity(0.1), - borderRadius: BorderRadius.circular(32), - border: Border.all(color: AppTheme.primaryBlue, width: 2), + color: AppTheme.primaryBlue.withOpacity(0.1), + borderRadius: BorderRadius.circular(32), + border: Border.all(color: AppTheme.primaryBlue, width: 2), ), - child: const Icon(Icons.wallet_rounded, size: 80, color: AppTheme.primaryBlue), - ), - const SizedBox(height: 24), - Text( - l.appName, - style: const TextStyle(fontSize: 32, fontWeight: FontWeight.w900, color: AppTheme.textPrimary, letterSpacing: 6), - ), - const SizedBox(height: 4), - Text( - l.appTagline, - style: TextStyle(fontSize: 13, color: AppTheme.textSecondary, letterSpacing: 1.2, fontWeight: FontWeight.w900), - ), - ], - ); - } + child: const Icon(Icons.wallet_rounded, size: 80, color: AppTheme.primaryBlue), + ), + const SizedBox(height: 24), + Text( + l.appName, + style: const TextStyle(fontSize: 32, fontWeight: FontWeight.w900, color: AppTheme.textPrimary, letterSpacing: 6), + ), + const SizedBox(height: 4), + Text( + l.appTagline, + style: TextStyle(fontSize: 13, color: AppTheme.textSecondary, letterSpacing: 1.2, fontWeight: FontWeight.w900), + ), + ], + ); - Widget _buildInputSection(AppLocalizations l) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container(width: 3, height: 16, color: AppTheme.primaryBlue), - const SizedBox(width: 8), - Text(l.secureSetup, style: TextStyle(color: AppTheme.textSecondary.withOpacity(0.5), fontSize: 11, fontWeight: FontWeight.w900, letterSpacing: 2)), - ], - ), - const SizedBox(height: 16), - _buildField( - _passwordController, - 'Create 4-Digit PIN', - Icons.password_rounded, - true, - isNumber: true, - validator: (v) { - if (v == null || v.isEmpty) return l.passwordRequired; - if (v.length != 4) return 'PIN must be exactly 4 digits'; - if (int.tryParse(v) == null) return 'PIN must be numeric'; - return null; - }, - ), - const SizedBox(height: 32), - Row( - children: [ - Container(width: 3, height: 16, color: AppTheme.primaryBlue), - const SizedBox(width: 8), - Text(l.recoveryQuestions, style: TextStyle(color: AppTheme.textSecondary.withOpacity(0.5), fontSize: 11, fontWeight: FontWeight.w900, letterSpacing: 2)), - ], - ), - const SizedBox(height: 16), - _buildField( - _q1Controller, - l.birthCity, - Icons.location_on_rounded, - false, - validator: (v) => v!.isEmpty ? l.birthCityRequired : null, - ), - const SizedBox(height: 16), - _buildField( - _q2Controller, - l.openingYear, - Icons.event_available_rounded, - false, - isNumber: true, - validator: (v) { - if (v == null || v.isEmpty) return l.yearRequired; - if (v.length != 4) return l.yearExactDigits; - - final year = int.tryParse(v); - if (year == null) return l.invalidYearFormat; - - final currentYear = _currentEthiopianYear; - if (year > currentYear) return l.yearFuture(currentYear); - if (year < currentYear - 50) return l.yearTooOld(currentYear - 50); - - return null; - }, - ), - ], - ); - } + Widget _buildInputSection(AppLocalizations l) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container(width: 3, height: 16, color: AppTheme.primaryBlue), + const SizedBox(width: 8), + Text(l.secureSetup, style: TextStyle(color: AppTheme.textSecondary.withOpacity(0.5), fontSize: 11, fontWeight: FontWeight.w900, letterSpacing: 2)), + ], + ), + const SizedBox(height: 16), + _buildField( + _passwordController, + l.createPinLabel, + Icons.lock_rounded, + true, + isNumber: true, + validator: (v) { + if (v == null || v.isEmpty) return l.passwordRequired; + if (v.length != 4) return l.pinExactDigits; + if (int.tryParse(v) == null) return l.pinNumericOnly; + return null; + }, + ), + const SizedBox(height: 32), + Row( + children: [ + Container(width: 3, height: 16, color: AppTheme.primaryBlue), + const SizedBox(width: 8), + Text(l.recoveryQuestions, style: TextStyle(color: AppTheme.textSecondary.withOpacity(0.5), fontSize: 11, fontWeight: FontWeight.w900, letterSpacing: 2)), + ], + ), + const SizedBox(height: 16), + _buildField( + _q1Controller, + l.birthCity, + Icons.location_city_rounded, + false, + validator: (v) => v!.isEmpty ? l.birthCityRequired : null, + ), + const SizedBox(height: 16), + _buildField( + _q2Controller, + l.openingYear, + Icons.event_available_rounded, + false, + isNumber: true, + validator: (v) { + if (v == null || v.isEmpty) return l.yearRequired; + if (v.length != 4) return l.yearExactDigits; + + final year = int.tryParse(v); + if (year == null) return l.invalidYearFormat; + + final currentYear = _currentEthiopianYear; + if (year > currentYear) return l.yearFuture(currentYear); + if (year < currentYear - 50) return l.yearTooOld(currentYear - 50); + + return null; + }, + ), + ], + ); Widget _buildField( TextEditingController controller, @@ -369,33 +364,32 @@ class _SplashSetupScreenState extends State with SingleTicker bool obscure, { bool isNumber = false, String? Function(String?)? validator, - }) { - return TextFormField( - controller: controller, - obscureText: obscure, - keyboardType: isNumber ? TextInputType.number : TextInputType.text, - style: const TextStyle(color: AppTheme.textPrimary, fontWeight: FontWeight.w600), - decoration: InputDecoration( - labelText: label, - prefixIcon: Icon(icon, color: AppTheme.primaryBlue, size: 20), - filled: true, - fillColor: const Color(0xFFF8FAFC), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: const BorderSide(color: Color(0xFFD0D5E0), width: 1), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: const BorderSide(color: Color(0xFFD0D5E0), width: 1), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(16), - borderSide: const BorderSide(color: AppTheme.primaryBlue, width: 2), + }) => + TextFormField( + controller: controller, + obscureText: obscure, + keyboardType: isNumber ? TextInputType.number : TextInputType.text, + style: const TextStyle(color: AppTheme.textPrimary, fontWeight: FontWeight.w600), + decoration: InputDecoration( + labelText: label, + prefixIcon: Icon(icon, color: AppTheme.primaryBlue, size: 20), + filled: true, + fillColor: const Color(0xFFF8FAFC), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: const BorderSide(color: Color(0xFFD0D5E0), width: 1), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: const BorderSide(color: Color(0xFFD0D5E0), width: 1), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: const BorderSide(color: AppTheme.primaryBlue, width: 2), + ), ), - ), - validator: validator, - ); - } + validator: validator, + ); @override void dispose() {