From 5e14ea858c3b7a5c7fcc19f66ea3dcb739517efc Mon Sep 17 00:00:00 2001 From: denid88 Date: Mon, 13 Nov 2023 10:10:08 +0200 Subject: [PATCH 01/37] [Auth] - added auth route, created elevated button widget --- lib/generated/intl/messages_en.dart | 5 ++ lib/generated/intl/messages_uk.dart | 4 + lib/generated/l10n.dart | 30 +++++++ lib/l10n/intl_en.arb | 5 +- lib/l10n/intl_uk.arb | 5 +- lib/src/app.dart | 2 + .../features/auth/screens/auth_screen.dart | 85 +++++++++++++++++++ .../splash/screens/splash_screen.dart | 2 +- .../presentation/navigation/app_router.dart | 8 ++ .../presentation/navigation/app_routes.dart | 1 + .../presentation/ui_kit/theme/app_colors.dart | 4 + .../presentation/ui_kit/theme/app_sizes.dart | 4 + .../presentation/ui_kit/theme/app_styles.dart | 10 +++ .../ui_kit/theme/themes/dark_theme.dart | 5 ++ .../ui_kit/theme/themes/light_theme.dart | 18 +++- lib/src/presentation/ui_kit/ui.dart | 3 +- .../buttons/elevated_button_widget.dart | 47 ++++++++++ .../presentation/ui_kit/widgets/widgets.dart | 1 + 18 files changed, 232 insertions(+), 7 deletions(-) create mode 100644 lib/src/presentation/features/auth/screens/auth_screen.dart create mode 100644 lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart create mode 100644 lib/src/presentation/ui_kit/widgets/widgets.dart diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 6931fd2..77fbeed 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -22,6 +22,11 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { + "authScreenButtonLabel": MessageLookupByLibrary.simpleMessage("Next"), + "authScreenDescription": MessageLookupByLibrary.simpleMessage( + "Enter your email and get dynamically generated code"), + "authScreenTitle": + MessageLookupByLibrary.simpleMessage("Authentication"), "labelContacts": MessageLookupByLibrary.simpleMessage("Contacts") }; } diff --git a/lib/generated/intl/messages_uk.dart b/lib/generated/intl/messages_uk.dart index e1ba0a4..d321978 100644 --- a/lib/generated/intl/messages_uk.dart +++ b/lib/generated/intl/messages_uk.dart @@ -22,6 +22,10 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { + "authScreenButtonLabel": MessageLookupByLibrary.simpleMessage("Далі"), + "authScreenDescription": MessageLookupByLibrary.simpleMessage( + "Введіть адрес вашої електроної пошти та отримайте код авторизації"), + "authScreenTitle": MessageLookupByLibrary.simpleMessage("Авторизація"), "labelContacts": MessageLookupByLibrary.simpleMessage("Контакти") }; } diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 6fd7872..4a79511 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -59,6 +59,36 @@ class S { args: [], ); } + + /// `Authentication` + String get authScreenTitle { + return Intl.message( + 'Authentication', + name: 'authScreenTitle', + desc: '', + args: [], + ); + } + + /// `Enter your email and get dynamically generated code` + String get authScreenDescription { + return Intl.message( + 'Enter your email and get dynamically generated code', + name: 'authScreenDescription', + desc: '', + args: [], + ); + } + + /// `Next` + String get authScreenButtonLabel { + return Intl.message( + 'Next', + name: 'authScreenButtonLabel', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 732389d..c470d28 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1,4 +1,7 @@ { "@@locale": "en", - "labelContacts": "Contacts" + "labelContacts": "Contacts", + "authScreenTitle": "Authentication", + "authScreenDescription": "Enter your email and get dynamically generated code", + "authScreenButtonLabel": "Next" } \ No newline at end of file diff --git a/lib/l10n/intl_uk.arb b/lib/l10n/intl_uk.arb index cdb226f..3230af1 100644 --- a/lib/l10n/intl_uk.arb +++ b/lib/l10n/intl_uk.arb @@ -1,4 +1,7 @@ { "@@locale": "uk", - "labelContacts": "Контакти" + "labelContacts": "Контакти", + "authScreenTitle": "Авторизація", + "authScreenDescription": "Введіть адрес вашої електроної пошти та отримайте код авторизації", + "authScreenButtonLabel": "Далі" } \ No newline at end of file diff --git a/lib/src/app.dart b/lib/src/app.dart index 80f13d3..d0c64a9 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:golub/generated/l10n.dart'; import 'package:golub/src/presentation/navigation/app_router.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:golub/src/presentation/ui_kit/theme/themes/light_theme.dart'; class App extends StatelessWidget { const App({super.key}); @@ -13,6 +14,7 @@ class App extends StatelessWidget { // themeMode: themeState.currentThemeMode, // theme: themeState.currentTheme, // darkTheme: themeState.currentThemeDark, + theme: lightTheme, localizationsDelegates: const [ S.delegate, GlobalMaterialLocalizations.delegate, diff --git a/lib/src/presentation/features/auth/screens/auth_screen.dart b/lib/src/presentation/features/auth/screens/auth_screen.dart new file mode 100644 index 0000000..f0a3d2e --- /dev/null +++ b/lib/src/presentation/features/auth/screens/auth_screen.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:golub/generated/l10n.dart'; +import 'package:golub/src/presentation/ui_kit/ui.dart'; + +class AuthScreen extends StatelessWidget { + const AuthScreen({super.key}); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Scaffold( + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 16.0, horizontal: 32.0, + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + s.authScreenTitle, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 12.0), + Text( + s.authScreenDescription, + style: Theme.of(context).textTheme.bodyMedium, + ), + + Padding( + padding: const EdgeInsets.only( + top: 60.0, + bottom: 32.0, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20.0, + height: 20.0, + child: Checkbox( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6.0) + ), + side: const BorderSide( + color: Colors.black, + width: 1.0, + ), + checkColor: Colors.white, + //fillColor: MaterialStateProperty.resolveWith(getColor), + value: false, + onChanged: (bool? value) { + print('value'); + print(value); + }, + ), + ), + const SizedBox(width: 12.0), + Expanded( + child: Text( + 'I agree to Terms & Conditions' + ' and Privacy Policy', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + height: 1 + ), + ), + ), + ], + ), + ), + ElevatedButtonWidget( + onPressed: () { + print('onPressed'); + }, + buttonLabel: s.authScreenButtonLabel, + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/presentation/features/splash/screens/splash_screen.dart b/lib/src/presentation/features/splash/screens/splash_screen.dart index 9ce7198..c7a4bb3 100644 --- a/lib/src/presentation/features/splash/screens/splash_screen.dart +++ b/lib/src/presentation/features/splash/screens/splash_screen.dart @@ -18,7 +18,7 @@ class _SplashScreenState extends State { void initState() { super.initState(); _initialization().then((_) { - context.goNamed(AppRoutes.chats); + context.goNamed(AppRoutes.auth); }); } diff --git a/lib/src/presentation/navigation/app_router.dart b/lib/src/presentation/navigation/app_router.dart index 70f028b..c9f2b9b 100644 --- a/lib/src/presentation/navigation/app_router.dart +++ b/lib/src/presentation/navigation/app_router.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:golub/src/presentation/features/auth/screens/auth_screen.dart'; import 'package:golub/src/presentation/features/chats/screens/chats_screen.dart'; import 'package:golub/src/presentation/features/contacts/screens/contacts_screen.dart'; import 'package:golub/src/presentation/features/profile/screens/profile_screen.dart'; @@ -24,6 +25,13 @@ final routerConfig = GoRouter( return const SplashScreen(); }, ), + GoRoute( + name: AppRoutes.auth, + path: AppRoutes.getPath(AppRoutes.auth), + builder: (BuildContext context, GoRouterState state) { + return const AuthScreen(); + }, + ), StatefulShellRoute.indexedStack( builder: (BuildContext context, GoRouterState state, StatefulNavigationShell navigationShell) { diff --git a/lib/src/presentation/navigation/app_routes.dart b/lib/src/presentation/navigation/app_routes.dart index 3fde44b..0d38252 100644 --- a/lib/src/presentation/navigation/app_routes.dart +++ b/lib/src/presentation/navigation/app_routes.dart @@ -1,6 +1,7 @@ /// App routes class AppRoutes { static const String splash = 'splash'; + static const String auth = 'auth'; static const String contacts = 'contacts'; static const String chats = 'chats'; static const String profile = 'profile'; diff --git a/lib/src/presentation/ui_kit/theme/app_colors.dart b/lib/src/presentation/ui_kit/theme/app_colors.dart index 5caf679..dfb6565 100644 --- a/lib/src/presentation/ui_kit/theme/app_colors.dart +++ b/lib/src/presentation/ui_kit/theme/app_colors.dart @@ -24,4 +24,8 @@ class AppColors { static const Color mixBlueGray = Color(0xFFF3F2F8); static const Color mixBlueGray1 = Color(0xFFD6DAEB); + + static const Color gradientIndigo = Color(0xFF92A0DE); + static const Color gradientBlue = Color(0xFF2D6DCD); + } diff --git a/lib/src/presentation/ui_kit/theme/app_sizes.dart b/lib/src/presentation/ui_kit/theme/app_sizes.dart index 242ee73..8614d9b 100644 --- a/lib/src/presentation/ui_kit/theme/app_sizes.dart +++ b/lib/src/presentation/ui_kit/theme/app_sizes.dart @@ -4,4 +4,8 @@ class AppSizes { static double bottomNavigationBarItemSize = 32.0; static double bottomNavigationBarActiveItemSize = 36.0; static double bottomNavigationBarItemContainerSize = 72.0; + + /// Elevated button + static double elevatedButtonHeight = 48.0; + static double elevatedButtonBorderRadius = 16.0; } diff --git a/lib/src/presentation/ui_kit/theme/app_styles.dart b/lib/src/presentation/ui_kit/theme/app_styles.dart index 56d830e..a8b2376 100644 --- a/lib/src/presentation/ui_kit/theme/app_styles.dart +++ b/lib/src/presentation/ui_kit/theme/app_styles.dart @@ -23,6 +23,7 @@ class AppTextStyles { static final TextStyle titleLarge = _ubuntu.copyWith( fontSize: 28.0, fontWeight: FontWeight.w500, + height: 1.25, ); static final TextStyle displaySmall = _ubuntu.copyWith( fontSize: 11.0, @@ -31,6 +32,7 @@ class AppTextStyles { static final TextStyle displayMedium = _roboto.copyWith( fontSize: 12.0, fontWeight: FontWeight.w400, + height: 1.25, ); static final TextStyle bodyMedium = _roboto.copyWith( fontSize: 17.0, @@ -39,6 +41,7 @@ class AppTextStyles { static final TextStyle bodySmall = _roboto.copyWith( fontSize: 15.0, fontWeight: FontWeight.w400, + height: 1.33, ); } @@ -50,4 +53,11 @@ class AppStyles { ], radius: 1.5, ); + + static const LinearGradient elevatedButtonGradient = LinearGradient( + colors: [ + AppColors.gradientIndigo, + AppColors.gradientBlue, + ], + ); } \ No newline at end of file diff --git a/lib/src/presentation/ui_kit/theme/themes/dark_theme.dart b/lib/src/presentation/ui_kit/theme/themes/dark_theme.dart index d262d0c..66ccf1d 100644 --- a/lib/src/presentation/ui_kit/theme/themes/dark_theme.dart +++ b/lib/src/presentation/ui_kit/theme/themes/dark_theme.dart @@ -1,8 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; final darkTheme = ThemeData( + appBarTheme: const AppBarTheme( + systemOverlayStyle: SystemUiOverlayStyle.light, + ), + scaffoldBackgroundColor: Colors.white, textTheme: TextTheme( titleLarge: AppTextStyles.titleLarge.copyWith(color: AppColors.baseWhite), titleMedium: AppTextStyles.titleMedium.copyWith(color: AppColors.baseWhite), diff --git a/lib/src/presentation/ui_kit/theme/themes/light_theme.dart b/lib/src/presentation/ui_kit/theme/themes/light_theme.dart index bb727df..33b321f 100644 --- a/lib/src/presentation/ui_kit/theme/themes/light_theme.dart +++ b/lib/src/presentation/ui_kit/theme/themes/light_theme.dart @@ -1,15 +1,27 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; final lightTheme = ThemeData( + scaffoldBackgroundColor: Colors.white, + appBarTheme: const AppBarTheme( + systemOverlayStyle: SystemUiOverlayStyle.light, + ), textTheme: TextTheme( - titleLarge: AppTextStyles.titleLarge, + titleLarge: AppTextStyles.titleLarge.copyWith( + color: AppColors.baseBlack, + ), titleMedium: AppTextStyles.titleMedium, titleSmall: AppTextStyles.titleSmall, displayLarge: AppTextStyles.displayLarge, displayMedium: AppTextStyles.displayMedium, displaySmall: AppTextStyles.displaySmall, - bodyMedium: AppTextStyles.bodyMedium, - bodySmall: AppTextStyles.bodySmall, + bodyMedium: AppTextStyles.bodyMedium.copyWith( + color: AppColors.baseGray6, + ), + bodySmall: AppTextStyles.bodySmall.copyWith( + color: AppColors.baseBlack, + ), ), ); diff --git a/lib/src/presentation/ui_kit/ui.dart b/lib/src/presentation/ui_kit/ui.dart index 399ba2b..dd2e64d 100644 --- a/lib/src/presentation/ui_kit/ui.dart +++ b/lib/src/presentation/ui_kit/ui.dart @@ -1,4 +1,5 @@ library ui_kit; export 'theme/app_assets.dart'; -export 'theme/app_sizes.dart'; \ No newline at end of file +export 'theme/app_sizes.dart'; +export 'widgets/buttons/elevated_button_widget.dart'; \ No newline at end of file diff --git a/lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart b/lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart new file mode 100644 index 0000000..daf4950 --- /dev/null +++ b/lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_sizes.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; + +class ElevatedButtonWidget extends StatelessWidget { + final double? width; + final double? height; + final double? borderRadius; + final VoidCallback onPressed; + final String buttonLabel; + + const ElevatedButtonWidget({ + required this.onPressed, + this.width, + this.height, + this.borderRadius, + this.buttonLabel = '', + super.key + }); + + @override + Widget build(BuildContext context) { + return Container( + constraints: BoxConstraints( + minWidth: width ?? double.infinity, + minHeight: height ?? AppSizes.elevatedButtonHeight, + ), + decoration: BoxDecoration( + gradient: AppStyles.elevatedButtonGradient, + borderRadius: BorderRadius.circular( + borderRadius ?? AppSizes.elevatedButtonBorderRadius), + ), + child: ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular( + borderRadius ?? AppSizes.elevatedButtonBorderRadius)), + ), + child: Text( + buttonLabel, + ), + ) + ); + } +} diff --git a/lib/src/presentation/ui_kit/widgets/widgets.dart b/lib/src/presentation/ui_kit/widgets/widgets.dart new file mode 100644 index 0000000..9c25cd4 --- /dev/null +++ b/lib/src/presentation/ui_kit/widgets/widgets.dart @@ -0,0 +1 @@ +export 'buttons/elevated_button_widget.dart'; \ No newline at end of file From 2f0c5bf50bfa38b9d3e4d0ec7931131fac8959e6 Mon Sep 17 00:00:00 2001 From: denid88 Date: Wed, 15 Nov 2023 10:01:54 +0200 Subject: [PATCH 02/37] [Auth] - created authentication screen --- lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart diff --git a/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart b/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart new file mode 100644 index 0000000..e69de29 From d2e43fe64a10e3d845050d5a4d45acda5a0bcdec Mon Sep 17 00:00:00 2001 From: denid88 Date: Wed, 15 Nov 2023 11:43:01 +0200 Subject: [PATCH 03/37] [Auth] - created TextFieldWidget --- .../features/auth/screens/auth_screen.dart | 143 ++++++++++-------- .../presentation/ui_kit/theme/app_colors.dart | 2 +- .../presentation/ui_kit/theme/app_styles.dart | 15 +- .../ui_kit/theme/themes/light_theme.dart | 14 ++ .../buttons/elevated_button_widget.dart | 22 ++- .../widgets/input/text_field_widget.dart | 27 ++++ 6 files changed, 151 insertions(+), 72 deletions(-) diff --git a/lib/src/presentation/features/auth/screens/auth_screen.dart b/lib/src/presentation/features/auth/screens/auth_screen.dart index f0a3d2e..980eacd 100644 --- a/lib/src/presentation/features/auth/screens/auth_screen.dart +++ b/lib/src/presentation/features/auth/screens/auth_screen.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:golub/generated/l10n.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; import 'package:golub/src/presentation/ui_kit/ui.dart'; +import 'package:golub/src/presentation/ui_kit/widgets/input/text_field_widget.dart'; class AuthScreen extends StatelessWidget { const AuthScreen({super.key}); @@ -8,74 +10,93 @@ class AuthScreen extends StatelessWidget { @override Widget build(BuildContext context) { final s = S.of(context); - return Scaffold( - body: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 16.0, horizontal: 32.0, - ), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - s.authScreenTitle, - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 12.0), - Text( - s.authScreenDescription, - style: Theme.of(context).textTheme.bodyMedium, - ), + final size = MediaQuery.of(context).size; - Padding( - padding: const EdgeInsets.only( - top: 60.0, - bottom: 32.0, + return Scaffold( + body: Container( + constraints: const BoxConstraints.expand(), + decoration: const BoxDecoration( + gradient: AppStyles.blueWhiteGradient, + ), + child: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 16.0, + horizontal: 32.0, + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only( + top: size.height / 6, + ), + child: Text( + s.authScreenTitle, + style: Theme.of(context).textTheme.titleLarge, + ), ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20.0, - height: 20.0, - child: Checkbox( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6.0) - ), - side: const BorderSide( - color: Colors.black, - width: 1.0, + const SizedBox(height: 12.0), + Text( + s.authScreenDescription, + style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 16.0), + TextFieldWidget( + hintText: 'Email', + ), + Padding( + padding: const EdgeInsets.only( + top: 60.0, + bottom: 32.0, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20.0, + height: 20.0, + child: Checkbox( + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6.0) + ), + side: const BorderSide( + color: Colors.black, + width: 1.0, + ), + checkColor: Colors.white, + value: false, + onChanged: (bool? value) { + print('value'); + print(value); + }, ), - checkColor: Colors.white, - //fillColor: MaterialStateProperty.resolveWith(getColor), - value: false, - onChanged: (bool? value) { - print('value'); - print(value); - }, ), - ), - const SizedBox(width: 12.0), - Expanded( - child: Text( - 'I agree to Terms & Conditions' - ' and Privacy Policy', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - height: 1 + const SizedBox(width: 12.0), + Expanded( + child: Text( + 'I agree to Terms & Conditions' + ' and Privacy Policy', + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(height: 1.25), ), ), - ), - ], + ], + ), + ), + ElevatedButtonWidget( + onPressed: () { + print('onPressed'); + }, + buttonLabel: s.authScreenButtonLabel, ), - ), - ElevatedButtonWidget( - onPressed: () { - print('onPressed'); - }, - buttonLabel: s.authScreenButtonLabel, - ), - ], + ], + ), ), ), ), diff --git a/lib/src/presentation/ui_kit/theme/app_colors.dart b/lib/src/presentation/ui_kit/theme/app_colors.dart index dfb6565..e7ec6f4 100644 --- a/lib/src/presentation/ui_kit/theme/app_colors.dart +++ b/lib/src/presentation/ui_kit/theme/app_colors.dart @@ -27,5 +27,5 @@ class AppColors { static const Color gradientIndigo = Color(0xFF92A0DE); static const Color gradientBlue = Color(0xFF2D6DCD); - + static const Color gradientWhite = Color(0XFFE0EDFF); } diff --git a/lib/src/presentation/ui_kit/theme/app_styles.dart b/lib/src/presentation/ui_kit/theme/app_styles.dart index a8b2376..68253f0 100644 --- a/lib/src/presentation/ui_kit/theme/app_styles.dart +++ b/lib/src/presentation/ui_kit/theme/app_styles.dart @@ -54,10 +54,21 @@ class AppStyles { radius: 1.5, ); - static const LinearGradient elevatedButtonGradient = LinearGradient( + static const LinearGradient bluePurpleGradient = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, colors: [ AppColors.gradientIndigo, AppColors.gradientBlue, ], ); -} \ No newline at end of file + + static const LinearGradient blueWhiteGradient = LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.bottomRight, + colors: [ + AppColors.gradientWhite, + AppColors.baseWhite, + ], + ); +} diff --git a/lib/src/presentation/ui_kit/theme/themes/light_theme.dart b/lib/src/presentation/ui_kit/theme/themes/light_theme.dart index 33b321f..a159a54 100644 --- a/lib/src/presentation/ui_kit/theme/themes/light_theme.dart +++ b/lib/src/presentation/ui_kit/theme/themes/light_theme.dart @@ -24,4 +24,18 @@ final lightTheme = ThemeData( color: AppColors.baseBlack, ), ), + inputDecorationTheme: InputDecorationTheme( + hintStyle: AppTextStyles.bodyMedium, + fillColor: AppColors.baseWhite, + contentPadding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), + filled: true, + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.baseWhite), + borderRadius: BorderRadius.circular(16.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.baseWhite), + borderRadius: BorderRadius.circular(16.0), + ), + ), ); diff --git a/lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart b/lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart index daf4950..ff9d030 100644 --- a/lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart +++ b/lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart @@ -26,22 +26,28 @@ class ElevatedButtonWidget extends StatelessWidget { minHeight: height ?? AppSizes.elevatedButtonHeight, ), decoration: BoxDecoration( - gradient: AppStyles.elevatedButtonGradient, + gradient: AppStyles.bluePurpleGradient, borderRadius: BorderRadius.circular( - borderRadius ?? AppSizes.elevatedButtonBorderRadius), + borderRadius ?? AppSizes.elevatedButtonBorderRadius + ), ), child: ElevatedButton( onPressed: onPressed, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.transparent, - shadowColor: Colors.transparent, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular( - borderRadius ?? AppSizes.elevatedButtonBorderRadius)), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Colors.transparent), + shadowColor: MaterialStateProperty.all(Colors.transparent), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + borderRadius ?? AppSizes.elevatedButtonBorderRadius + ), + ), + ), ), child: Text( buttonLabel, ), - ) + ), ); } } diff --git a/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart b/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart index e69de29..00924ea 100644 --- a/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart +++ b/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; + +class TextFieldWidget extends StatelessWidget { + final String? hintText; + + const TextFieldWidget({this.hintText, super.key}); + + @override + Widget build(BuildContext context) { + return Container( + constraints: const BoxConstraints( + minWidth: double.infinity, + maxHeight: 52.0, + ), + child: TextField( + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).brightness == Brightness.light ? + AppColors.baseBlack : AppColors.baseWhite, + ), + decoration: InputDecoration( + hintText: hintText, + ), + ), + ); + } +} From 757310fcaa6c50d70478ff85b30f658384602b05 Mon Sep 17 00:00:00 2001 From: denid88 Date: Tue, 21 Nov 2023 10:07:50 +0200 Subject: [PATCH 04/37] [Auth] - added BLoC --- ios/Podfile.lock | 22 ++ ios/Runner.xcodeproj/project.pbxproj | 134 +++++++++-- .../contents.xcworkspacedata | 3 + lib/generated/intl/messages_en.dart | 10 +- lib/generated/intl/messages_uk.dart | 10 +- lib/generated/l10n.dart | 50 +++++ lib/l10n/intl_en.arb | 7 +- lib/l10n/intl_uk.arb | 7 +- lib/src/domain/blocs/auth/auth_bloc.dart | 26 +++ .../domain/blocs/auth/auth_bloc.freezed.dart | 201 +++++++++++++++++ lib/src/domain/blocs/auth/auth_event.dart | 15 ++ lib/src/domain/blocs/auth/auth_state.dart | 20 ++ .../features/auth/screens/auth_screen.dart | 212 +++++++++++------- .../presentation/ui_kit/theme/app_colors.dart | 1 + .../ui_kit/theme/themes/light_theme.dart | 7 + .../buttons/elevated_button_widget.dart | 21 +- .../widgets/input/text_field_widget.dart | 13 +- lib/src/presentation/utils/link_launcher.dart | 8 + linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 108 ++++++++- pubspec.yaml | 4 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 25 files changed, 787 insertions(+), 103 deletions(-) create mode 100644 ios/Podfile.lock create mode 100644 lib/src/domain/blocs/auth/auth_bloc.dart create mode 100644 lib/src/domain/blocs/auth/auth_bloc.freezed.dart create mode 100644 lib/src/domain/blocs/auth/auth_event.dart create mode 100644 lib/src/domain/blocs/auth/auth_state.dart create mode 100644 lib/src/presentation/utils/link_launcher.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..3d81393 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - Flutter (1.0.0) + - url_launcher_ios (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `Flutter`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: Flutter + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + +SPEC CHECKSUMS: + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b + +PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189 + +COCOAPODS: 1.11.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 1ac8d01..71e169b 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -7,13 +7,15 @@ objects = { /* Begin PBXBuildFile section */ + 057D6EE5BB80267DE9A309AA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 03A891C613C2EFEAD176D53B /* Pods_Runner.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 68F71A49BBCF9E4F15814E75 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EBB174393ECDA22EA32D646 /* Pods_RunnerTests.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -40,9 +42,16 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 03A891C613C2EFEAD176D53B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 0838C6D9BB79BBF30B30F3C9 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 36EE64B9A5FD16B7801F8CFE /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 3AD801084A3A630BCCB5FCBE /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 6EBB174393ECDA22EA32D646 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -53,30 +62,52 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + CC231CDD254204C24741D157 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + E43A4490609820A20ABBB892 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + EC4F3BF1DF395A7A97C42544 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 08E30F5925EDFADEE4A2ED9E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 68F71A49BBCF9E4F15814E75 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 057D6EE5BB80267DE9A309AA /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 9740EEB11CF90186004384FC /* Flutter */ = { + 13F8F8B5EE5AE5965780B8D9 /* Frameworks */ = { isa = PBXGroup; children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, + 03A891C613C2EFEAD176D53B /* Pods_Runner.framework */, + 6EBB174393ECDA22EA32D646 /* Pods_RunnerTests.framework */, ); - name = Flutter; + name = Frameworks; + sourceTree = ""; + }; + 26B95A65C0ED1248809BBA5D /* Pods */ = { + isa = PBXGroup; + children = ( + 0838C6D9BB79BBF30B30F3C9 /* Pods-Runner.debug.xcconfig */, + EC4F3BF1DF395A7A97C42544 /* Pods-Runner.release.xcconfig */, + CC231CDD254204C24741D157 /* Pods-Runner.profile.xcconfig */, + 36EE64B9A5FD16B7801F8CFE /* Pods-RunnerTests.debug.xcconfig */, + E43A4490609820A20ABBB892 /* Pods-RunnerTests.release.xcconfig */, + 3AD801084A3A630BCCB5FCBE /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; sourceTree = ""; }; 331C8082294A63A400263BE5 /* RunnerTests */ = { @@ -87,6 +118,17 @@ path = RunnerTests; sourceTree = ""; }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( @@ -94,6 +136,8 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + 26B95A65C0ED1248809BBA5D /* Pods */, + 13F8F8B5EE5AE5965780B8D9 /* Frameworks */, ); sourceTree = ""; }; @@ -128,9 +172,10 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( + 15069A8F7A1A01DD662AA45E /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, - 331C807E294A63A400263BE5 /* Frameworks */, 331C807F294A63A400263BE5 /* Resources */, + 08E30F5925EDFADEE4A2ED9E /* Frameworks */, ); buildRules = ( ); @@ -146,12 +191,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + CDFE34D4CA13DB196E682F5E /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 83A7B1F3EC4453E10234DFDB /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -223,6 +270,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 15069A8F7A1A01DD662AA45E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -239,6 +308,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 83A7B1F3EC4453E10234DFDB /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -254,6 +340,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + CDFE34D4CA13DB196E682F5E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -378,7 +486,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 36EE64B9A5FD16B7801F8CFE /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -396,7 +504,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = E43A4490609820A20ABBB892 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -412,7 +520,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 3AD801084A3A630BCCB5FCBE /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 77fbeed..d1ce81a 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -22,11 +22,19 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { + "andLabel": MessageLookupByLibrary.simpleMessage(" and "), "authScreenButtonLabel": MessageLookupByLibrary.simpleMessage("Next"), "authScreenDescription": MessageLookupByLibrary.simpleMessage( "Enter your email and get dynamically generated code"), + "authScreenEmailPlaceholder": + MessageLookupByLibrary.simpleMessage("Email"), "authScreenTitle": MessageLookupByLibrary.simpleMessage("Authentication"), - "labelContacts": MessageLookupByLibrary.simpleMessage("Contacts") + "iAgreeLabel": MessageLookupByLibrary.simpleMessage("I agree to "), + "labelContacts": MessageLookupByLibrary.simpleMessage("Contacts"), + "privacyPolicyLabel": + MessageLookupByLibrary.simpleMessage("Privacy Policy"), + "termsAndConditionsLabel": + MessageLookupByLibrary.simpleMessage("Terms & Conditions") }; } diff --git a/lib/generated/intl/messages_uk.dart b/lib/generated/intl/messages_uk.dart index d321978..3a19b10 100644 --- a/lib/generated/intl/messages_uk.dart +++ b/lib/generated/intl/messages_uk.dart @@ -22,10 +22,18 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { + "andLabel": MessageLookupByLibrary.simpleMessage(" та "), "authScreenButtonLabel": MessageLookupByLibrary.simpleMessage("Далі"), "authScreenDescription": MessageLookupByLibrary.simpleMessage( "Введіть адрес вашої електроної пошти та отримайте код авторизації"), + "authScreenEmailPlaceholder": + MessageLookupByLibrary.simpleMessage("Емайл"), "authScreenTitle": MessageLookupByLibrary.simpleMessage("Авторизація"), - "labelContacts": MessageLookupByLibrary.simpleMessage("Контакти") + "iAgreeLabel": MessageLookupByLibrary.simpleMessage("Я погоджуюсь з "), + "labelContacts": MessageLookupByLibrary.simpleMessage("Контакти"), + "privacyPolicyLabel": + MessageLookupByLibrary.simpleMessage("Політикою приватності"), + "termsAndConditionsLabel": + MessageLookupByLibrary.simpleMessage("Умовами та положеннями") }; } diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 4a79511..9e3499b 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -89,6 +89,56 @@ class S { args: [], ); } + + /// `Email` + String get authScreenEmailPlaceholder { + return Intl.message( + 'Email', + name: 'authScreenEmailPlaceholder', + desc: '', + args: [], + ); + } + + /// `I agree to ` + String get iAgreeLabel { + return Intl.message( + 'I agree to ', + name: 'iAgreeLabel', + desc: '', + args: [], + ); + } + + /// `Privacy Policy` + String get privacyPolicyLabel { + return Intl.message( + 'Privacy Policy', + name: 'privacyPolicyLabel', + desc: '', + args: [], + ); + } + + /// `Terms & Conditions` + String get termsAndConditionsLabel { + return Intl.message( + 'Terms & Conditions', + name: 'termsAndConditionsLabel', + desc: '', + args: [], + ); + } + + /// ` and ` + String get andLabel { + return Intl.message( + ' and ', + name: 'andLabel', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index c470d28..8f06e57 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -3,5 +3,10 @@ "labelContacts": "Contacts", "authScreenTitle": "Authentication", "authScreenDescription": "Enter your email and get dynamically generated code", - "authScreenButtonLabel": "Next" + "authScreenButtonLabel": "Next", + "authScreenEmailPlaceholder": "Email", + "iAgreeLabel": "I agree to ", + "privacyPolicyLabel": "Privacy Policy", + "termsAndConditionsLabel": "Terms & Conditions", + "andLabel": " and " } \ No newline at end of file diff --git a/lib/l10n/intl_uk.arb b/lib/l10n/intl_uk.arb index 3230af1..068bfcb 100644 --- a/lib/l10n/intl_uk.arb +++ b/lib/l10n/intl_uk.arb @@ -3,5 +3,10 @@ "labelContacts": "Контакти", "authScreenTitle": "Авторизація", "authScreenDescription": "Введіть адрес вашої електроної пошти та отримайте код авторизації", - "authScreenButtonLabel": "Далі" + "authScreenButtonLabel": "Далі", + "authScreenEmailPlaceholder": "Емайл", + "iAgreeLabel": "Я погоджуюсь з ", + "privacyPolicyLabel": "Політикою приватності", + "termsAndConditionsLabel": "Умовами та положеннями", + "andLabel": " та " } \ No newline at end of file diff --git a/lib/src/domain/blocs/auth/auth_bloc.dart b/lib/src/domain/blocs/auth/auth_bloc.dart new file mode 100644 index 0000000..0448409 --- /dev/null +++ b/lib/src/domain/blocs/auth/auth_bloc.dart @@ -0,0 +1,26 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'auth_event.dart'; +part 'auth_state.dart'; +part 'auth_bloc.freezed.dart'; + +class AuthBloc extends Bloc { + AuthBloc() : super(AuthState.initial()) { + on(_changeEmailFieldEvent); + on(_changePrivacyPolicyStatusEvent); + } + + _changeEmailFieldEvent( + ChangeEmailFieldEvent event, Emitter emit) async { + emit(state.copyWith(email: event.value)); + } + + _changePrivacyPolicyStatusEvent( + ChangePrivacyPolicyStatusEvent event, Emitter emit) async { + emit(state.copyWith(privacyPolicyAccepted: event.value)); + } + + /// Getters + bool get isButtonDisabled => !state.privacyPolicyAccepted; +} \ No newline at end of file diff --git a/lib/src/domain/blocs/auth/auth_bloc.freezed.dart b/lib/src/domain/blocs/auth/auth_bloc.freezed.dart new file mode 100644 index 0000000..40db6f3 --- /dev/null +++ b/lib/src/domain/blocs/auth/auth_bloc.freezed.dart @@ -0,0 +1,201 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'auth_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$AuthState { + AuthStatus get status => throw _privateConstructorUsedError; + String get email => throw _privateConstructorUsedError; + String? get error => throw _privateConstructorUsedError; + bool get privacyPolicyAccepted => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $AuthStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AuthStateCopyWith<$Res> { + factory $AuthStateCopyWith(AuthState value, $Res Function(AuthState) then) = + _$AuthStateCopyWithImpl<$Res, AuthState>; + @useResult + $Res call( + {AuthStatus status, + String email, + String? error, + bool privacyPolicyAccepted}); +} + +/// @nodoc +class _$AuthStateCopyWithImpl<$Res, $Val extends AuthState> + implements $AuthStateCopyWith<$Res> { + _$AuthStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? email = null, + Object? error = freezed, + Object? privacyPolicyAccepted = null, + }) { + return _then(_value.copyWith( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as AuthStatus, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + error: freezed == error + ? _value.error + : error // ignore: cast_nullable_to_non_nullable + as String?, + privacyPolicyAccepted: null == privacyPolicyAccepted + ? _value.privacyPolicyAccepted + : privacyPolicyAccepted // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AuthStateImplCopyWith<$Res> + implements $AuthStateCopyWith<$Res> { + factory _$$AuthStateImplCopyWith( + _$AuthStateImpl value, $Res Function(_$AuthStateImpl) then) = + __$$AuthStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {AuthStatus status, + String email, + String? error, + bool privacyPolicyAccepted}); +} + +/// @nodoc +class __$$AuthStateImplCopyWithImpl<$Res> + extends _$AuthStateCopyWithImpl<$Res, _$AuthStateImpl> + implements _$$AuthStateImplCopyWith<$Res> { + __$$AuthStateImplCopyWithImpl( + _$AuthStateImpl _value, $Res Function(_$AuthStateImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? email = null, + Object? error = freezed, + Object? privacyPolicyAccepted = null, + }) { + return _then(_$AuthStateImpl( + status: null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as AuthStatus, + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + error: freezed == error + ? _value.error + : error // ignore: cast_nullable_to_non_nullable + as String?, + privacyPolicyAccepted: null == privacyPolicyAccepted + ? _value.privacyPolicyAccepted + : privacyPolicyAccepted // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$AuthStateImpl implements _AuthState { + const _$AuthStateImpl( + {this.status = AuthStatus.initial, + this.email = '', + this.error = null, + this.privacyPolicyAccepted = false}); + + @override + @JsonKey() + final AuthStatus status; + @override + @JsonKey() + final String email; + @override + @JsonKey() + final String? error; + @override + @JsonKey() + final bool privacyPolicyAccepted; + + @override + String toString() { + return 'AuthState(status: $status, email: $email, error: $error, privacyPolicyAccepted: $privacyPolicyAccepted)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AuthStateImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.email, email) || other.email == email) && + (identical(other.error, error) || other.error == error) && + (identical(other.privacyPolicyAccepted, privacyPolicyAccepted) || + other.privacyPolicyAccepted == privacyPolicyAccepted)); + } + + @override + int get hashCode => + Object.hash(runtimeType, status, email, error, privacyPolicyAccepted); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$AuthStateImplCopyWith<_$AuthStateImpl> get copyWith => + __$$AuthStateImplCopyWithImpl<_$AuthStateImpl>(this, _$identity); +} + +abstract class _AuthState implements AuthState { + const factory _AuthState( + {final AuthStatus status, + final String email, + final String? error, + final bool privacyPolicyAccepted}) = _$AuthStateImpl; + + @override + AuthStatus get status; + @override + String get email; + @override + String? get error; + @override + bool get privacyPolicyAccepted; + @override + @JsonKey(ignore: true) + _$$AuthStateImplCopyWith<_$AuthStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/src/domain/blocs/auth/auth_event.dart b/lib/src/domain/blocs/auth/auth_event.dart new file mode 100644 index 0000000..12b7564 --- /dev/null +++ b/lib/src/domain/blocs/auth/auth_event.dart @@ -0,0 +1,15 @@ +part of 'auth_bloc.dart'; + +abstract class AuthEvent {} + +class ChangeEmailFieldEvent extends AuthEvent { + final String value; + + ChangeEmailFieldEvent(this.value); +} + +class ChangePrivacyPolicyStatusEvent extends AuthEvent { + final bool value; + + ChangePrivacyPolicyStatusEvent(this.value); +} \ No newline at end of file diff --git a/lib/src/domain/blocs/auth/auth_state.dart b/lib/src/domain/blocs/auth/auth_state.dart new file mode 100644 index 0000000..5c6b002 --- /dev/null +++ b/lib/src/domain/blocs/auth/auth_state.dart @@ -0,0 +1,20 @@ +part of 'auth_bloc.dart'; + +enum AuthStatus { + initial, + loading, + success, + error, +} + +@freezed +class AuthState with _$AuthState { + const factory AuthState({ + @Default(AuthStatus.initial) AuthStatus status, + @Default('') String email, + @Default(null) String? error, + @Default(false) bool privacyPolicyAccepted, + }) = _AuthState; + + factory AuthState.initial() => const AuthState(); +} \ No newline at end of file diff --git a/lib/src/presentation/features/auth/screens/auth_screen.dart b/lib/src/presentation/features/auth/screens/auth_screen.dart index 980eacd..c8ab48b 100644 --- a/lib/src/presentation/features/auth/screens/auth_screen.dart +++ b/lib/src/presentation/features/auth/screens/auth_screen.dart @@ -1,101 +1,157 @@ +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:golub/generated/l10n.dart'; +import 'package:golub/src/domain/blocs/auth/auth_bloc.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; import 'package:golub/src/presentation/ui_kit/ui.dart'; import 'package:golub/src/presentation/ui_kit/widgets/input/text_field_widget.dart'; +import 'package:golub/src/presentation/utils/link_launcher.dart'; -class AuthScreen extends StatelessWidget { +class AuthScreen extends StatefulWidget { const AuthScreen({super.key}); + @override + State createState() => _AuthScreenState(); +} + +class _AuthScreenState extends State { + final AuthBloc _authBloc = AuthBloc(); + + final TapGestureRecognizer _termsConditionsTapRecognizer = + TapGestureRecognizer(); + final TapGestureRecognizer _privacyPolicyTapRecognizer = + TapGestureRecognizer(); + + @override + void dispose() { + _termsConditionsTapRecognizer.dispose(); + _privacyPolicyTapRecognizer.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final s = S.of(context); final size = MediaQuery.of(context).size; - return Scaffold( - body: Container( - constraints: const BoxConstraints.expand(), - decoration: const BoxDecoration( - gradient: AppStyles.blueWhiteGradient, - ), - child: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 16.0, - horizontal: 32.0, - ), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only( - top: size.height / 6, + return BlocProvider( + create: (_) => _authBloc, + child: Scaffold( + body: Container( + constraints: const BoxConstraints.expand(), + decoration: const BoxDecoration( + gradient: AppStyles.blueWhiteGradient, + ), + child: SafeArea( + child: Padding( + padding: const EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only( + top: size.height / 6, + ), + child: Text( + s.authScreenTitle, + style: Theme.of(context).textTheme.titleLarge, + ), ), - child: Text( - s.authScreenTitle, - style: Theme.of(context).textTheme.titleLarge, + const SizedBox(height: 12.0), + Text( + s.authScreenDescription, + style: Theme.of(context).textTheme.bodyMedium, ), - ), - const SizedBox(height: 12.0), - Text( - s.authScreenDescription, - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 16.0), - TextFieldWidget( - hintText: 'Email', - ), - Padding( - padding: const EdgeInsets.only( - top: 60.0, - bottom: 32.0, + const SizedBox(height: 16.0), + TextFieldWidget( + hintText: s.authScreenEmailPlaceholder, + error: 'Error', ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20.0, - height: 20.0, - child: Checkbox( - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6.0) + Padding( + padding: const EdgeInsets.only( + top: 60.0, + bottom: 32.0, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20.0, + height: 20.0, + child: BlocBuilder( + bloc: _authBloc, + builder: (BuildContext context, AuthState state) { + return Checkbox( + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.0) + ), + side: const BorderSide( + color: Colors.black, + width: 1.0, + ), + checkColor: Colors.white, + activeColor: AppColors.brightLightPurple, + value: state.privacyPolicyAccepted, + onChanged: (bool? value) => _authBloc.add( + ChangePrivacyPolicyStatusEvent(value ?? false), + ), + ); + }, ), - side: const BorderSide( - color: Colors.black, - width: 1.0, - ), - checkColor: Colors.white, - value: false, - onChanged: (bool? value) { - print('value'); - print(value); - }, ), - ), - const SizedBox(width: 12.0), - Expanded( - child: Text( - 'I agree to Terms & Conditions' - ' and Privacy Policy', - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(height: 1.25), + const SizedBox(width: 12.0), + Expanded( + child: RichText( + maxLines: 2, + text: TextSpan( + text: s.iAgreeLabel, + style: Theme.of(context).textTheme.bodySmall, + children: [ + TextSpan( + recognizer: _termsConditionsTapRecognizer + ..onTap = () => + launchLink('https://google.com'), + text: s.termsAndConditionsLabel, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppColors.brightBlue + ), + ), + TextSpan(text: s.andLabel), + TextSpan( + recognizer: _privacyPolicyTapRecognizer + ..onTap = () => + launchLink('https://google.com'), + text: s.privacyPolicyLabel, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppColors.brightBlue + ), + ), + ], + ), + ), ), - ), - ], + ], + ), + ), + BlocBuilder( + bloc: _authBloc, + builder: (BuildContext context, AuthState state) { + return ElevatedButtonWidget( + isDisabled: _authBloc.isButtonDisabled, + onPressed: () { + print('onPressed'); + }, + buttonLabel: s.authScreenButtonLabel, + ); + }, ), - ), - ElevatedButtonWidget( - onPressed: () { - print('onPressed'); - }, - buttonLabel: s.authScreenButtonLabel, - ), - ], + ], + ), ), ), ), diff --git a/lib/src/presentation/ui_kit/theme/app_colors.dart b/lib/src/presentation/ui_kit/theme/app_colors.dart index e7ec6f4..f78a177 100644 --- a/lib/src/presentation/ui_kit/theme/app_colors.dart +++ b/lib/src/presentation/ui_kit/theme/app_colors.dart @@ -21,6 +21,7 @@ class AppColors { static const Color brightBlue = Color(0xFF3571CF); static const Color brightIndigo = Color(0xFF7F92DC); static const Color brightPurple = Color(0xFF9052DE); + static const Color brightLightPurple = Color(0xFF7F8DE9); static const Color mixBlueGray = Color(0xFFF3F2F8); static const Color mixBlueGray1 = Color(0xFFD6DAEB); diff --git a/lib/src/presentation/ui_kit/theme/themes/light_theme.dart b/lib/src/presentation/ui_kit/theme/themes/light_theme.dart index a159a54..fb07352 100644 --- a/lib/src/presentation/ui_kit/theme/themes/light_theme.dart +++ b/lib/src/presentation/ui_kit/theme/themes/light_theme.dart @@ -37,5 +37,12 @@ final lightTheme = ThemeData( borderSide: const BorderSide(color: AppColors.baseWhite), borderRadius: BorderRadius.circular(16.0), ), + disabledBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.baseWhite), + borderRadius: BorderRadius.circular(16.0), + ), + ), + progressIndicatorTheme: const ProgressIndicatorThemeData( + color: AppColors.baseWhite, ), ); diff --git a/lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart b/lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart index ff9d030..ee32fc7 100644 --- a/lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart +++ b/lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_sizes.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; @@ -8,6 +9,8 @@ class ElevatedButtonWidget extends StatelessWidget { final double? borderRadius; final VoidCallback onPressed; final String buttonLabel; + final bool isLoading; + final bool isDisabled; const ElevatedButtonWidget({ required this.onPressed, @@ -15,6 +18,8 @@ class ElevatedButtonWidget extends StatelessWidget { this.height, this.borderRadius, this.buttonLabel = '', + this.isLoading = false, + this.isDisabled = false, super.key }); @@ -26,13 +31,14 @@ class ElevatedButtonWidget extends StatelessWidget { minHeight: height ?? AppSizes.elevatedButtonHeight, ), decoration: BoxDecoration( - gradient: AppStyles.bluePurpleGradient, + gradient: !isDisabled ? AppStyles.bluePurpleGradient : null, + color: isDisabled ? AppColors.baseGray3 : null, borderRadius: BorderRadius.circular( borderRadius ?? AppSizes.elevatedButtonBorderRadius ), ), child: ElevatedButton( - onPressed: onPressed, + onPressed: !isDisabled ? onPressed : null, style: ButtonStyle( backgroundColor: MaterialStateProperty.all(Colors.transparent), shadowColor: MaterialStateProperty.all(Colors.transparent), @@ -44,8 +50,17 @@ class ElevatedButtonWidget extends StatelessWidget { ), ), ), - child: Text( + child: isLoading ? Center( + child: CircularProgressIndicator( + color: Theme.of(context).progressIndicatorTheme.color, + strokeWidth: 2.0, + ), + ) : + Text( buttonLabel, + style: Theme.of(context).textTheme.titleSmall?.copyWith( + color: isDisabled ? AppColors.baseGray4 : AppColors.baseWhite, + ), ), ), ); diff --git a/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart b/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart index 00924ea..15b28a4 100644 --- a/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart +++ b/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart @@ -3,15 +3,20 @@ import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; class TextFieldWidget extends StatelessWidget { final String? hintText; + final String? error; - const TextFieldWidget({this.hintText, super.key}); + const TextFieldWidget({ + this.hintText, + this.error, + super.key + }); @override Widget build(BuildContext context) { return Container( constraints: const BoxConstraints( minWidth: double.infinity, - maxHeight: 52.0, + minHeight: 52.0, ), child: TextField( style: Theme.of(context).textTheme.bodyMedium?.copyWith( @@ -20,6 +25,10 @@ class TextFieldWidget extends StatelessWidget { ), decoration: InputDecoration( hintText: hintText, + helperText: error, + helperStyle: Theme.of(context).textTheme.displaySmall?.copyWith( + color: AppColors.brightRed, + ), ), ), ); diff --git a/lib/src/presentation/utils/link_launcher.dart b/lib/src/presentation/utils/link_launcher.dart new file mode 100644 index 0000000..6784904 --- /dev/null +++ b/lib/src/presentation/utils/link_launcher.dart @@ -0,0 +1,8 @@ +import 'package:url_launcher/url_launcher.dart'; + +Future launchLink(String link) async { + final Uri url = Uri.parse(link); + if (!await launchUrl(url)) { + throw Exception('Could not launch $url'); + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..f6f23bf 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2e1de87..f16b4c3 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..8236f57 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 0ed7ea0..fd1d049 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49" + url: "https://pub.dev" + source: hosted + version: "8.1.2" boolean_selector: dependency: transitive description: @@ -214,6 +222,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae + url: "https://pub.dev" + source: hosted + version: "8.1.3" flutter_lints: dependency: "direct dev" description: @@ -254,7 +270,7 @@ packages: source: hosted version: "2.4.5" freezed_annotation: - dependency: transitive + dependency: "direct main" description: name: freezed_annotation sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d @@ -405,6 +421,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" package_config: dependency: transitive description: @@ -437,6 +461,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + url: "https://pub.dev" + source: hosted + version: "2.1.7" pointycastle: dependency: transitive description: @@ -453,6 +485,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + provider: + dependency: transitive + description: + name: provider + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" + url: "https://pub.dev" + source: hosted + version: "6.1.1" pub_semver: dependency: transitive description: @@ -570,6 +610,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba + url: "https://pub.dev" + source: hosted + version: "6.2.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 + url: "https://pub.dev" + source: hosted + version: "6.2.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + url: "https://pub.dev" + source: hosted + version: "3.1.0" vector_graphics: dependency: transitive description: @@ -644,4 +748,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.1.3 <4.0.0" - flutter: ">=3.7.0" + flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index 859385b..1125ffc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,9 @@ dependencies: flutter_svg: 2.0.9 intl: 0.18.1 freezed: 2.4.5 - + freezed_annotation: 2.4.1 + flutter_bloc: 8.1.3 + url_launcher: 6.2.1 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..4f78848 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..88b22e5 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST From 769b3335f41f5daf262c92c1a94b32b1a822bedf Mon Sep 17 00:00:00 2001 From: denid88 Date: Sat, 25 Nov 2023 16:05:06 +0200 Subject: [PATCH 05/37] [Auth] - added authentication logic, repository, source for auth --- .env | 0 lib/generated/intl/messages_en.dart | 2 + lib/generated/intl/messages_uk.dart | 2 + lib/generated/l10n.dart | 10 ++ lib/l10n/intl_en.arb | 1 + lib/l10n/intl_uk.arb | 1 + lib/main.dart | 3 + lib/src/core/di/injectable.config.dart | 41 ++++++++ lib/src/core/di/injectable.dart | 22 +++++ .../repositories/auth_repository_impl.dart | 15 +++ lib/src/data/services/dio/dio_client.dart | 97 +++++++++++++++++++ .../data/services/dio/dio_interceptor.dart | 27 ++++++ .../sources/remote/auth_remote_source.dart | 19 ++++ lib/src/domain/blocs/auth/auth_bloc.dart | 44 +++++++-- .../domain/blocs/auth/auth_bloc.freezed.dart | 28 +++++- lib/src/domain/blocs/auth/auth_event.dart | 10 +- lib/src/domain/blocs/auth/auth_state.dart | 1 + .../extenstions/string_validation_ext.dart | 5 + .../domain/repositories/auth_repository.dart | 3 + .../usecases/auth_by_email_usecase.dart | 13 +++ .../features/auth/screens/auth_screen.dart | 70 ++++++++----- .../widgets/input/text_field_widget.dart | 9 ++ pubspec.lock | 48 +++++++++ pubspec.yaml | 5 + 24 files changed, 436 insertions(+), 40 deletions(-) create mode 100644 .env create mode 100644 lib/src/core/di/injectable.config.dart create mode 100644 lib/src/core/di/injectable.dart create mode 100644 lib/src/data/repositories/auth_repository_impl.dart create mode 100644 lib/src/data/services/dio/dio_client.dart create mode 100644 lib/src/data/services/dio/dio_interceptor.dart create mode 100644 lib/src/data/sources/remote/auth_remote_source.dart create mode 100644 lib/src/domain/extenstions/string_validation_ext.dart create mode 100644 lib/src/domain/repositories/auth_repository.dart create mode 100644 lib/src/domain/usecases/auth_by_email_usecase.dart diff --git a/.env b/.env new file mode 100644 index 0000000..e69de29 diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index d1ce81a..5836b92 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -30,6 +30,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Email"), "authScreenTitle": MessageLookupByLibrary.simpleMessage("Authentication"), + "authScreenValidationEmailError": + MessageLookupByLibrary.simpleMessage("Please enter a valid email"), "iAgreeLabel": MessageLookupByLibrary.simpleMessage("I agree to "), "labelContacts": MessageLookupByLibrary.simpleMessage("Contacts"), "privacyPolicyLabel": diff --git a/lib/generated/intl/messages_uk.dart b/lib/generated/intl/messages_uk.dart index 3a19b10..97b6cf8 100644 --- a/lib/generated/intl/messages_uk.dart +++ b/lib/generated/intl/messages_uk.dart @@ -29,6 +29,8 @@ class MessageLookup extends MessageLookupByLibrary { "authScreenEmailPlaceholder": MessageLookupByLibrary.simpleMessage("Емайл"), "authScreenTitle": MessageLookupByLibrary.simpleMessage("Авторизація"), + "authScreenValidationEmailError": + MessageLookupByLibrary.simpleMessage("Введіть коректний емайл"), "iAgreeLabel": MessageLookupByLibrary.simpleMessage("Я погоджуюсь з "), "labelContacts": MessageLookupByLibrary.simpleMessage("Контакти"), "privacyPolicyLabel": diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 9e3499b..74affcc 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -100,6 +100,16 @@ class S { ); } + /// `Please enter a valid email` + String get authScreenValidationEmailError { + return Intl.message( + 'Please enter a valid email', + name: 'authScreenValidationEmailError', + desc: '', + args: [], + ); + } + /// `I agree to ` String get iAgreeLabel { return Intl.message( diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 8f06e57..fa49dc0 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -5,6 +5,7 @@ "authScreenDescription": "Enter your email and get dynamically generated code", "authScreenButtonLabel": "Next", "authScreenEmailPlaceholder": "Email", + "authScreenValidationEmailError": "Please enter a valid email", "iAgreeLabel": "I agree to ", "privacyPolicyLabel": "Privacy Policy", "termsAndConditionsLabel": "Terms & Conditions", diff --git a/lib/l10n/intl_uk.arb b/lib/l10n/intl_uk.arb index 068bfcb..2995b48 100644 --- a/lib/l10n/intl_uk.arb +++ b/lib/l10n/intl_uk.arb @@ -5,6 +5,7 @@ "authScreenDescription": "Введіть адрес вашої електроної пошти та отримайте код авторизації", "authScreenButtonLabel": "Далі", "authScreenEmailPlaceholder": "Емайл", + "authScreenValidationEmailError": "Введіть коректний емайл", "iAgreeLabel": "Я погоджуюсь з ", "privacyPolicyLabel": "Політикою приватності", "termsAndConditionsLabel": "Умовами та положеннями", diff --git a/lib/main.dart b/lib/main.dart index 4d6b501..0309b1f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:golub/src/app.dart'; +import 'package:golub/src/core/di/injectable.dart'; +import 'package:golub/src/data/services/dio/dio_client.dart'; import 'package:golub/src/presentation/ui_kit/ui.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await _precacheLogoImage(); + configureDependencies(); runApp(const App()); } diff --git a/lib/src/core/di/injectable.config.dart b/lib/src/core/di/injectable.config.dart new file mode 100644 index 0000000..91e9735 --- /dev/null +++ b/lib/src/core/di/injectable.config.dart @@ -0,0 +1,41 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// InjectableConfigGenerator +// ************************************************************************** + +// ignore_for_file: type=lint +// coverage:ignore-file + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:get_it/get_it.dart' as _i1; +import 'package:golub/src/data/repositories/auth_repository_impl.dart' as _i6; +import 'package:golub/src/data/services/dio/dio_client.dart' as _i3; +import 'package:golub/src/data/sources/remote/auth_remote_source.dart' as _i4; +import 'package:golub/src/domain/blocs/auth/auth_bloc.dart' as _i8; +import 'package:golub/src/domain/repositories/auth_repository.dart' as _i5; +import 'package:golub/src/domain/usecases/auth_by_email_usecase.dart' as _i7; +import 'package:injectable/injectable.dart' as _i2; + +extension GetItInjectableX on _i1.GetIt { +// initializes the registration of main-scope dependencies inside of GetIt + _i1.GetIt init({ + String? environment, + _i2.EnvironmentFilter? environmentFilter, + }) { + final gh = _i2.GetItHelper( + this, + environment, + environmentFilter, + ); + gh.singleton<_i3.DioClient>(_i3.DioClient(baseUrl: gh())); + gh.singleton<_i4.AuthRemoteSource>( + _i4.AuthRemoteSource(dioClient: gh<_i3.DioClient>())); + gh.factory<_i5.AuthRepository>( + () => _i6.AuthRepositoryImpl(gh<_i4.AuthRemoteSource>())); + gh.factory<_i7.AuthByEmailUseCase>( + () => _i7.AuthByEmailUseCase(gh<_i5.AuthRepository>())); + gh.factory<_i8.AuthBloc>(() => _i8.AuthBloc(gh<_i7.AuthByEmailUseCase>())); + return this; + } +} diff --git a/lib/src/core/di/injectable.dart b/lib/src/core/di/injectable.dart new file mode 100644 index 0000000..926245e --- /dev/null +++ b/lib/src/core/di/injectable.dart @@ -0,0 +1,22 @@ +import 'package:get_it/get_it.dart'; +import 'package:golub/src/data/repositories/auth_repository_impl.dart'; +import 'package:golub/src/data/services/dio/dio_client.dart'; +import 'package:golub/src/data/sources/remote/auth_remote_source.dart'; +import 'package:golub/src/domain/blocs/auth/auth_bloc.dart'; +import 'package:golub/src/domain/repositories/auth_repository.dart'; +import 'package:golub/src/domain/usecases/auth_by_email_usecase.dart'; +import 'package:injectable/injectable.dart'; + +final getIt = GetIt.instance; + +@InjectableInit() +void configureDependencies() { + getIt.registerSingleton(DioClient(baseUrl: 'https://google.com')); + getIt.registerSingleton( + AuthRemoteSource(dioClient: getIt())); + getIt.registerSingleton( + AuthRepositoryImpl(getIt())); + getIt.registerSingleton( + AuthByEmailUseCase(getIt())); + getIt.registerFactory(() => AuthBloc(getIt())); +} diff --git a/lib/src/data/repositories/auth_repository_impl.dart b/lib/src/data/repositories/auth_repository_impl.dart new file mode 100644 index 0000000..d88e397 --- /dev/null +++ b/lib/src/data/repositories/auth_repository_impl.dart @@ -0,0 +1,15 @@ +import 'package:golub/src/data/sources/remote/auth_remote_source.dart'; +import 'package:golub/src/domain/repositories/auth_repository.dart'; +import 'package:injectable/injectable.dart'; + +@Injectable(as: AuthRepository) +class AuthRepositoryImpl extends AuthRepository { + final AuthRemoteSource _authRemoteSource; + + AuthRepositoryImpl(this._authRemoteSource); + + @override + Future authenticateByEmail(String email) async { + return _authRemoteSource.authenticateByEmail(email); + } +} diff --git a/lib/src/data/services/dio/dio_client.dart b/lib/src/data/services/dio/dio_client.dart new file mode 100644 index 0000000..5038950 --- /dev/null +++ b/lib/src/data/services/dio/dio_client.dart @@ -0,0 +1,97 @@ +import 'dart:developer'; +import 'package:dio/dio.dart'; +import 'package:injectable/injectable.dart'; + +part 'dio_interceptor.dart'; + +@singleton +class DioClient { + final String baseUrl; + late Dio _dio; + + DioClient({required this.baseUrl}) { + _dio = Dio(BaseOptions( + baseUrl: baseUrl, + connectTimeout: const Duration(milliseconds: 30000), + sendTimeout: const Duration(milliseconds: 30000), + receiveTimeout: const Duration(milliseconds: 30000), + responseType: ResponseType.json, + )); + + /// Add logging interceptor + _dio.interceptors.add(DioInterceptor()); + } + + /// Get method + Future get( + String path, { + Map? headers, + Map? queryParameters, + }) async { + return _dio.get( + path, + options: Options(headers: headers), + queryParameters: queryParameters + ); + } + + /// Post method + Future post( + String path, { + Map? headers, + Map? queryParameters, + dynamic data, + }) async { + return _dio.post( + path, + options: Options(headers: headers), + queryParameters: queryParameters, + data: data, + ); + } + + /// Put method + Future put( + String path, { + Map? headers, + Map? queryParameters, + dynamic data, + }) async { + return _dio.put( + path, + options: Options(headers: headers), + queryParameters: queryParameters, + data: data, + ); + } + + /// Delete method + Future delete( + String path, { + Map? headers, + Map? queryParameters, + dynamic data, + }) async { + return _dio.delete( + path, + options: Options(headers: headers), + queryParameters: queryParameters, + data: data, + ); + } + + /// Patch method + Future patch( + String path, { + Map? headers, + Map? queryParameters, + dynamic data, + }) async { + return _dio.patch( + path, + options: Options(headers: headers), + queryParameters: queryParameters, + data: data, + ); + } +} \ No newline at end of file diff --git a/lib/src/data/services/dio/dio_interceptor.dart b/lib/src/data/services/dio/dio_interceptor.dart new file mode 100644 index 0000000..63552bf --- /dev/null +++ b/lib/src/data/services/dio/dio_interceptor.dart @@ -0,0 +1,27 @@ +part of 'dio_client.dart'; + +class DioInterceptor implements Interceptor { + @override + void onRequest( + RequestOptions options, RequestInterceptorHandler handler) async { + log('🔸Request[${options.method}] => Path: ${options.uri}'); + return handler.next(options); + } + + @override + void onResponse(Response response, ResponseInterceptorHandler handler) async { + log('🔹Response[${response.statusCode}] =>' + ' Data: ${_formattedData(response.data.toString())}'); + return handler.next(response); + } + + @override + void onError(DioException e, ErrorInterceptorHandler handler) async { + log('🚧onError[${e.type}] => Path: ${e.response?.realUri}'); + return handler.next(e); + } + + String _formattedData(String data) { + return data.length > 64 ? '${data.substring(0, 64)} ... }' : data; + } +} \ No newline at end of file diff --git a/lib/src/data/sources/remote/auth_remote_source.dart b/lib/src/data/sources/remote/auth_remote_source.dart new file mode 100644 index 0000000..bf429cd --- /dev/null +++ b/lib/src/data/sources/remote/auth_remote_source.dart @@ -0,0 +1,19 @@ +import 'package:golub/src/data/services/dio/dio_client.dart'; +import 'package:injectable/injectable.dart'; + +@singleton +class AuthRemoteSource { + final DioClient dioClient; + + const AuthRemoteSource({required this.dioClient}); + + static const String _pathAuthenticateByEmail = '/auth'; + + Future authenticateByEmail(String email) async { + print('__source $email'); + await dioClient.post( + _pathAuthenticateByEmail, + data: {'email': email}, + ); + } +} diff --git a/lib/src/domain/blocs/auth/auth_bloc.dart b/lib/src/domain/blocs/auth/auth_bloc.dart index 0448409..581063e 100644 --- a/lib/src/domain/blocs/auth/auth_bloc.dart +++ b/lib/src/domain/blocs/auth/auth_bloc.dart @@ -1,26 +1,52 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:golub/generated/l10n.dart'; +import 'package:golub/src/domain/extenstions/string_validation_ext.dart'; +import 'package:golub/src/domain/usecases/auth_by_email_usecase.dart'; +import 'package:injectable/injectable.dart'; +part 'auth_bloc.freezed.dart'; part 'auth_event.dart'; part 'auth_state.dart'; -part 'auth_bloc.freezed.dart'; +@injectable class AuthBloc extends Bloc { - AuthBloc() : super(AuthState.initial()) { - on(_changeEmailFieldEvent); + final AuthByEmailUseCase _authByEmailUseCase; + + AuthBloc(this._authByEmailUseCase) : super(AuthState.initial()) { + on(_changeEmailEvent); on(_changePrivacyPolicyStatusEvent); + on(_authenticateByEmailEvent); } - _changeEmailFieldEvent( - ChangeEmailFieldEvent event, Emitter emit) async { - emit(state.copyWith(email: event.value)); + Future _changeEmailEvent(ChangeEmailEvent event, Emitter emit) async { + print('Email changed to ${event.value}'); + emit(state.copyWith(email: event.value, validationError: null)); } - _changePrivacyPolicyStatusEvent( + Future _changePrivacyPolicyStatusEvent( ChangePrivacyPolicyStatusEvent event, Emitter emit) async { emit(state.copyWith(privacyPolicyAccepted: event.value)); } + Future _authenticateByEmailEvent( + AuthenticateByEmailEvent event, Emitter emit) async { + if (!state.email.isValidEmail) { + emit(state.copyWith( + validationError: S.current.authScreenValidationEmailError)); + return; + } + + emit(state.copyWith(status: AuthStatus.loading)); + try { + await _authByEmailUseCase(state.email); + emit(state.copyWith(status: AuthStatus.success)); + } catch (e) { + emit(state.copyWith(status: AuthStatus.error, error: e.toString())); + } + } + /// Getters - bool get isButtonDisabled => !state.privacyPolicyAccepted; -} \ No newline at end of file + bool get isButtonDisabled => + !state.privacyPolicyAccepted || state.email.isEmpty; +} diff --git a/lib/src/domain/blocs/auth/auth_bloc.freezed.dart b/lib/src/domain/blocs/auth/auth_bloc.freezed.dart index 40db6f3..f522a0e 100644 --- a/lib/src/domain/blocs/auth/auth_bloc.freezed.dart +++ b/lib/src/domain/blocs/auth/auth_bloc.freezed.dart @@ -18,6 +18,7 @@ final _privateConstructorUsedError = UnsupportedError( mixin _$AuthState { AuthStatus get status => throw _privateConstructorUsedError; String get email => throw _privateConstructorUsedError; + String? get validationError => throw _privateConstructorUsedError; String? get error => throw _privateConstructorUsedError; bool get privacyPolicyAccepted => throw _privateConstructorUsedError; @@ -34,6 +35,7 @@ abstract class $AuthStateCopyWith<$Res> { $Res call( {AuthStatus status, String email, + String? validationError, String? error, bool privacyPolicyAccepted}); } @@ -53,6 +55,7 @@ class _$AuthStateCopyWithImpl<$Res, $Val extends AuthState> $Res call({ Object? status = null, Object? email = null, + Object? validationError = freezed, Object? error = freezed, Object? privacyPolicyAccepted = null, }) { @@ -65,6 +68,10 @@ class _$AuthStateCopyWithImpl<$Res, $Val extends AuthState> ? _value.email : email // ignore: cast_nullable_to_non_nullable as String, + validationError: freezed == validationError + ? _value.validationError + : validationError // ignore: cast_nullable_to_non_nullable + as String?, error: freezed == error ? _value.error : error // ignore: cast_nullable_to_non_nullable @@ -88,6 +95,7 @@ abstract class _$$AuthStateImplCopyWith<$Res> $Res call( {AuthStatus status, String email, + String? validationError, String? error, bool privacyPolicyAccepted}); } @@ -105,6 +113,7 @@ class __$$AuthStateImplCopyWithImpl<$Res> $Res call({ Object? status = null, Object? email = null, + Object? validationError = freezed, Object? error = freezed, Object? privacyPolicyAccepted = null, }) { @@ -117,6 +126,10 @@ class __$$AuthStateImplCopyWithImpl<$Res> ? _value.email : email // ignore: cast_nullable_to_non_nullable as String, + validationError: freezed == validationError + ? _value.validationError + : validationError // ignore: cast_nullable_to_non_nullable + as String?, error: freezed == error ? _value.error : error // ignore: cast_nullable_to_non_nullable @@ -135,6 +148,7 @@ class _$AuthStateImpl implements _AuthState { const _$AuthStateImpl( {this.status = AuthStatus.initial, this.email = '', + this.validationError = null, this.error = null, this.privacyPolicyAccepted = false}); @@ -146,6 +160,9 @@ class _$AuthStateImpl implements _AuthState { final String email; @override @JsonKey() + final String? validationError; + @override + @JsonKey() final String? error; @override @JsonKey() @@ -153,7 +170,7 @@ class _$AuthStateImpl implements _AuthState { @override String toString() { - return 'AuthState(status: $status, email: $email, error: $error, privacyPolicyAccepted: $privacyPolicyAccepted)'; + return 'AuthState(status: $status, email: $email, validationError: $validationError, error: $error, privacyPolicyAccepted: $privacyPolicyAccepted)'; } @override @@ -163,14 +180,16 @@ class _$AuthStateImpl implements _AuthState { other is _$AuthStateImpl && (identical(other.status, status) || other.status == status) && (identical(other.email, email) || other.email == email) && + (identical(other.validationError, validationError) || + other.validationError == validationError) && (identical(other.error, error) || other.error == error) && (identical(other.privacyPolicyAccepted, privacyPolicyAccepted) || other.privacyPolicyAccepted == privacyPolicyAccepted)); } @override - int get hashCode => - Object.hash(runtimeType, status, email, error, privacyPolicyAccepted); + int get hashCode => Object.hash(runtimeType, status, email, validationError, + error, privacyPolicyAccepted); @JsonKey(ignore: true) @override @@ -183,6 +202,7 @@ abstract class _AuthState implements AuthState { const factory _AuthState( {final AuthStatus status, final String email, + final String? validationError, final String? error, final bool privacyPolicyAccepted}) = _$AuthStateImpl; @@ -191,6 +211,8 @@ abstract class _AuthState implements AuthState { @override String get email; @override + String? get validationError; + @override String? get error; @override bool get privacyPolicyAccepted; diff --git a/lib/src/domain/blocs/auth/auth_event.dart b/lib/src/domain/blocs/auth/auth_event.dart index 12b7564..7d3a8e6 100644 --- a/lib/src/domain/blocs/auth/auth_event.dart +++ b/lib/src/domain/blocs/auth/auth_event.dart @@ -2,14 +2,14 @@ part of 'auth_bloc.dart'; abstract class AuthEvent {} -class ChangeEmailFieldEvent extends AuthEvent { +class ChangeEmailEvent extends AuthEvent { final String value; - - ChangeEmailFieldEvent(this.value); + ChangeEmailEvent(this.value); } class ChangePrivacyPolicyStatusEvent extends AuthEvent { final bool value; - ChangePrivacyPolicyStatusEvent(this.value); -} \ No newline at end of file +} + +class AuthenticateByEmailEvent extends AuthEvent {} diff --git a/lib/src/domain/blocs/auth/auth_state.dart b/lib/src/domain/blocs/auth/auth_state.dart index 5c6b002..35a1c50 100644 --- a/lib/src/domain/blocs/auth/auth_state.dart +++ b/lib/src/domain/blocs/auth/auth_state.dart @@ -12,6 +12,7 @@ class AuthState with _$AuthState { const factory AuthState({ @Default(AuthStatus.initial) AuthStatus status, @Default('') String email, + @Default(null) String? validationError, @Default(null) String? error, @Default(false) bool privacyPolicyAccepted, }) = _AuthState; diff --git a/lib/src/domain/extenstions/string_validation_ext.dart b/lib/src/domain/extenstions/string_validation_ext.dart new file mode 100644 index 0000000..3f4a34a --- /dev/null +++ b/lib/src/domain/extenstions/string_validation_ext.dart @@ -0,0 +1,5 @@ +extension StringValidationExt on String { + bool get isValidEmail { + return RegExp(r'^[a-zA-Z0-9.]+@[a-zA-Z0-9]+\.[a-zA-Z]+').hasMatch(this); + } +} \ No newline at end of file diff --git a/lib/src/domain/repositories/auth_repository.dart b/lib/src/domain/repositories/auth_repository.dart new file mode 100644 index 0000000..269e846 --- /dev/null +++ b/lib/src/domain/repositories/auth_repository.dart @@ -0,0 +1,3 @@ +abstract class AuthRepository { + Future authenticateByEmail(String email); +} diff --git a/lib/src/domain/usecases/auth_by_email_usecase.dart b/lib/src/domain/usecases/auth_by_email_usecase.dart new file mode 100644 index 0000000..ff163c6 --- /dev/null +++ b/lib/src/domain/usecases/auth_by_email_usecase.dart @@ -0,0 +1,13 @@ +import 'package:golub/src/domain/repositories/auth_repository.dart'; +import 'package:injectable/injectable.dart'; + +@injectable +class AuthByEmailUseCase { + final AuthRepository _repository; + + AuthByEmailUseCase(this._repository); + + Future call(String email) async { + return _repository.authenticateByEmail(email); + } +} diff --git a/lib/src/presentation/features/auth/screens/auth_screen.dart b/lib/src/presentation/features/auth/screens/auth_screen.dart index c8ab48b..ba5971b 100644 --- a/lib/src/presentation/features/auth/screens/auth_screen.dart +++ b/lib/src/presentation/features/auth/screens/auth_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:golub/generated/l10n.dart'; +import 'package:golub/src/core/di/injectable.dart'; import 'package:golub/src/domain/blocs/auth/auth_bloc.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; @@ -17,15 +18,30 @@ class AuthScreen extends StatefulWidget { } class _AuthScreenState extends State { - final AuthBloc _authBloc = AuthBloc(); + final AuthBloc _authBloc = getIt(); + + final TextEditingController _emailController = TextEditingController(); + final FocusNode _emailFocusNode = FocusNode(); final TapGestureRecognizer _termsConditionsTapRecognizer = - TapGestureRecognizer(); + TapGestureRecognizer(); final TapGestureRecognizer _privacyPolicyTapRecognizer = - TapGestureRecognizer(); + TapGestureRecognizer(); + + @override + void initState() { + super.initState(); + _emailController.addListener(_handleEmailString); + } + + void _handleEmailString() { + _authBloc.add(ChangeEmailEvent(_emailController.text)); + } @override void dispose() { + _emailController.removeListener(_handleEmailString); + _emailController.dispose(); _termsConditionsTapRecognizer.dispose(); _privacyPolicyTapRecognizer.dispose(); super.dispose(); @@ -46,7 +62,8 @@ class _AuthScreenState extends State { ), child: SafeArea( child: Padding( - padding: const EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0), + padding: + const EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -66,10 +83,16 @@ class _AuthScreenState extends State { style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 16.0), - TextFieldWidget( - hintText: s.authScreenEmailPlaceholder, - error: 'Error', - ), + BlocBuilder( + bloc: _authBloc, + builder: (BuildContext context, AuthState state) { + return TextFieldWidget( + textEditingController: _emailController, + focusNode: _emailFocusNode, + hintText: s.authScreenEmailPlaceholder, + error: state.validationError, + ); + }), Padding( padding: const EdgeInsets.only( top: 60.0, @@ -86,10 +109,9 @@ class _AuthScreenState extends State { builder: (BuildContext context, AuthState state) { return Checkbox( materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, + MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4.0) - ), + borderRadius: BorderRadius.circular(4.0)), side: const BorderSide( color: Colors.black, width: 1.0, @@ -98,7 +120,8 @@ class _AuthScreenState extends State { activeColor: AppColors.brightLightPurple, value: state.privacyPolicyAccepted, onChanged: (bool? value) => _authBloc.add( - ChangePrivacyPolicyStatusEvent(value ?? false), + ChangePrivacyPolicyStatusEvent( + value ?? false), ), ); }, @@ -115,21 +138,23 @@ class _AuthScreenState extends State { TextSpan( recognizer: _termsConditionsTapRecognizer ..onTap = () => - launchLink('https://google.com'), + launchLink('https://google.com'), text: s.termsAndConditionsLabel, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: AppColors.brightBlue - ), + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: AppColors.brightBlue), ), TextSpan(text: s.andLabel), TextSpan( recognizer: _privacyPolicyTapRecognizer ..onTap = () => - launchLink('https://google.com'), + launchLink('https://google.com'), text: s.privacyPolicyLabel, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: AppColors.brightBlue - ), + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: AppColors.brightBlue), ), ], ), @@ -143,9 +168,8 @@ class _AuthScreenState extends State { builder: (BuildContext context, AuthState state) { return ElevatedButtonWidget( isDisabled: _authBloc.isButtonDisabled, - onPressed: () { - print('onPressed'); - }, + onPressed: () => + _authBloc.add(AuthenticateByEmailEvent()), buttonLabel: s.authScreenButtonLabel, ); }, diff --git a/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart b/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart index 15b28a4..c8861fe 100644 --- a/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart +++ b/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart @@ -4,8 +4,14 @@ import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; class TextFieldWidget extends StatelessWidget { final String? hintText; final String? error; + final TextEditingController? textEditingController; + final FocusNode? focusNode; + final VoidCallback? onEditingComplete; const TextFieldWidget({ + this.textEditingController, + this.focusNode, + this.onEditingComplete, this.hintText, this.error, super.key @@ -19,6 +25,9 @@ class TextFieldWidget extends StatelessWidget { minHeight: 52.0, ), child: TextField( + controller: textEditingController, + focusNode: focusNode, + onEditingComplete: onEditingComplete, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).brightness == Brightness.light ? AppColors.baseBlack : AppColors.baseWhite, diff --git a/pubspec.lock b/pubspec.lock index fd1d049..4d227e9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -193,6 +193,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + dio: + dependency: "direct main" + description: + name: dio + sha256: "01870acd87986f768e0c09cc4d7a19a59d814af7b34cbeb0b437d2c33bdfea4c" + url: "https://pub.dev" + source: hosted + version: "5.3.4" fake_async: dependency: transitive description: @@ -230,6 +238,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.3" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" flutter_lints: dependency: "direct dev" description: @@ -285,6 +301,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: f79870884de16d689cf9a7d15eedf31ed61d750e813c538a6efb92660fea83c3 + url: "https://pub.dev" + source: hosted + version: "7.6.4" glob: dependency: transitive description: @@ -333,6 +357,22 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + injectable: + dependency: "direct main" + description: + name: injectable + sha256: cd3c422e13270c81f64ab73c80406b2b2ed563fe59d0ff2093eb7eee63d0bbeb + url: "https://pub.dev" + source: hosted + version: "2.3.2" + injectable_generator: + dependency: "direct dev" + description: + name: injectable_generator + sha256: f9d3c05f0938403f79ad6c6d23ec8e37a7a05ad980b1bf9399493f3e41845788 + url: "https://pub.dev" + source: hosted + version: "2.4.1" intl: dependency: "direct main" description: @@ -509,6 +549,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.3" + recase: + dependency: transitive + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" shelf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1125ffc..a50a1f3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,10 @@ dependencies: freezed_annotation: 2.4.1 flutter_bloc: 8.1.3 url_launcher: 6.2.1 + dio: 5.3.4 + flutter_dotenv: 5.1.0 + injectable: 2.3.2 + get_it: 7.6.4 dev_dependencies: flutter_test: @@ -28,6 +32,7 @@ dev_dependencies: flutter_lints: 2.0.0 intl_utils: 2.8.5 build_runner: 2.4.6 + injectable_generator: 2.4.1 flutter_intl: enabled: true From 76a314919c4f36c9cda08825f075c921fec0cd9c Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 26 Nov 2023 14:27:22 +0200 Subject: [PATCH 06/37] [Auth] - added verification screen scaffold --- assets/icons/arrow_back.svg | 3 + assets/images/chats.svg | 4 + lib/generated/intl/messages_en.dart | 6 +- lib/generated/intl/messages_uk.dart | 6 +- lib/generated/l10n.dart | 20 ++++ lib/l10n/intl_en.arb | 4 +- lib/l10n/intl_uk.arb | 4 +- lib/src/domain/blocs/auth/auth_bloc.dart | 13 ++- lib/src/domain/blocs/auth/auth_event.dart | 2 + .../features/auth/screens/auth_screen.dart | 57 ++++++---- .../auth/screens/verification_screen.dart | 105 ++++++++++++++++++ .../verification_input_widget.dart | 49 ++++++++ .../splash/screens/splash_screen.dart | 3 +- .../presentation/navigation/app_router.dart | 11 +- .../presentation/navigation/app_routes.dart | 4 +- .../presentation/ui_kit/theme/app_assets.dart | 5 + pubspec.yaml | 1 + 17 files changed, 263 insertions(+), 34 deletions(-) create mode 100644 assets/icons/arrow_back.svg create mode 100644 assets/images/chats.svg create mode 100644 lib/src/presentation/features/auth/screens/verification_screen.dart create mode 100644 lib/src/presentation/features/auth/widgets/verification/verification_input_widget.dart diff --git a/assets/icons/arrow_back.svg b/assets/icons/arrow_back.svg new file mode 100644 index 0000000..0483495 --- /dev/null +++ b/assets/icons/arrow_back.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/chats.svg b/assets/images/chats.svg new file mode 100644 index 0000000..e72f660 --- /dev/null +++ b/assets/images/chats.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 5836b92..6564b4d 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -37,6 +37,10 @@ class MessageLookup extends MessageLookupByLibrary { "privacyPolicyLabel": MessageLookupByLibrary.simpleMessage("Privacy Policy"), "termsAndConditionsLabel": - MessageLookupByLibrary.simpleMessage("Terms & Conditions") + MessageLookupByLibrary.simpleMessage("Terms & Conditions"), + "verificationScreenDescription": MessageLookupByLibrary.simpleMessage( + "Paste dynamically generated code"), + "verificationScreenTitle": + MessageLookupByLibrary.simpleMessage("Check your Email") }; } diff --git a/lib/generated/intl/messages_uk.dart b/lib/generated/intl/messages_uk.dart index 97b6cf8..e06b9c4 100644 --- a/lib/generated/intl/messages_uk.dart +++ b/lib/generated/intl/messages_uk.dart @@ -36,6 +36,10 @@ class MessageLookup extends MessageLookupByLibrary { "privacyPolicyLabel": MessageLookupByLibrary.simpleMessage("Політикою приватності"), "termsAndConditionsLabel": - MessageLookupByLibrary.simpleMessage("Умовами та положеннями") + MessageLookupByLibrary.simpleMessage("Умовами та положеннями"), + "verificationScreenDescription": MessageLookupByLibrary.simpleMessage( + "Вставте динамічно згенерований код"), + "verificationScreenTitle": MessageLookupByLibrary.simpleMessage( + "Перевірте свою електронну пошту") }; } diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 74affcc..fd52b65 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -149,6 +149,26 @@ class S { args: [], ); } + + /// `Check your Email` + String get verificationScreenTitle { + return Intl.message( + 'Check your Email', + name: 'verificationScreenTitle', + desc: '', + args: [], + ); + } + + /// `Paste dynamically generated code` + String get verificationScreenDescription { + return Intl.message( + 'Paste dynamically generated code', + name: 'verificationScreenDescription', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index fa49dc0..b227916 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -9,5 +9,7 @@ "iAgreeLabel": "I agree to ", "privacyPolicyLabel": "Privacy Policy", "termsAndConditionsLabel": "Terms & Conditions", - "andLabel": " and " + "andLabel": " and ", + "verificationScreenTitle": "Check your Email", + "verificationScreenDescription": "Paste dynamically generated code" } \ No newline at end of file diff --git a/lib/l10n/intl_uk.arb b/lib/l10n/intl_uk.arb index 2995b48..8766f1f 100644 --- a/lib/l10n/intl_uk.arb +++ b/lib/l10n/intl_uk.arb @@ -9,5 +9,7 @@ "iAgreeLabel": "Я погоджуюсь з ", "privacyPolicyLabel": "Політикою приватності", "termsAndConditionsLabel": "Умовами та положеннями", - "andLabel": " та " + "andLabel": " та ", + "verificationScreenTitle": "Перевірте свою електронну пошту", + "verificationScreenDescription": "Вставте динамічно згенерований код" } \ No newline at end of file diff --git a/lib/src/domain/blocs/auth/auth_bloc.dart b/lib/src/domain/blocs/auth/auth_bloc.dart index 581063e..367edf8 100644 --- a/lib/src/domain/blocs/auth/auth_bloc.dart +++ b/lib/src/domain/blocs/auth/auth_bloc.dart @@ -17,10 +17,11 @@ class AuthBloc extends Bloc { on(_changeEmailEvent); on(_changePrivacyPolicyStatusEvent); on(_authenticateByEmailEvent); + on(_clearStateEvent); } - Future _changeEmailEvent(ChangeEmailEvent event, Emitter emit) async { - print('Email changed to ${event.value}'); + Future _changeEmailEvent( + ChangeEmailEvent event, Emitter emit) async { emit(state.copyWith(email: event.value, validationError: null)); } @@ -39,13 +40,19 @@ class AuthBloc extends Bloc { emit(state.copyWith(status: AuthStatus.loading)); try { - await _authByEmailUseCase(state.email); + await Future.delayed(const Duration(milliseconds: 2000)); + //await _authByEmailUseCase(state.email); emit(state.copyWith(status: AuthStatus.success)); } catch (e) { emit(state.copyWith(status: AuthStatus.error, error: e.toString())); } } + Future _clearStateEvent( + ClearStateEvent event, Emitter emit) async { + emit(AuthState.initial()); + } + /// Getters bool get isButtonDisabled => !state.privacyPolicyAccepted || state.email.isEmpty; diff --git a/lib/src/domain/blocs/auth/auth_event.dart b/lib/src/domain/blocs/auth/auth_event.dart index 7d3a8e6..dae8e7a 100644 --- a/lib/src/domain/blocs/auth/auth_event.dart +++ b/lib/src/domain/blocs/auth/auth_event.dart @@ -13,3 +13,5 @@ class ChangePrivacyPolicyStatusEvent extends AuthEvent { } class AuthenticateByEmailEvent extends AuthEvent {} + +class ClearStateEvent extends AuthEvent {} diff --git a/lib/src/presentation/features/auth/screens/auth_screen.dart b/lib/src/presentation/features/auth/screens/auth_screen.dart index ba5971b..c8b1522 100644 --- a/lib/src/presentation/features/auth/screens/auth_screen.dart +++ b/lib/src/presentation/features/auth/screens/auth_screen.dart @@ -1,9 +1,11 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; import 'package:golub/generated/l10n.dart'; import 'package:golub/src/core/di/injectable.dart'; import 'package:golub/src/domain/blocs/auth/auth_bloc.dart'; +import 'package:golub/src/presentation/navigation/app_router.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; import 'package:golub/src/presentation/ui_kit/ui.dart'; @@ -24,9 +26,9 @@ class _AuthScreenState extends State { final FocusNode _emailFocusNode = FocusNode(); final TapGestureRecognizer _termsConditionsTapRecognizer = - TapGestureRecognizer(); + TapGestureRecognizer(); final TapGestureRecognizer _privacyPolicyTapRecognizer = - TapGestureRecognizer(); + TapGestureRecognizer(); @override void initState() { @@ -77,22 +79,23 @@ class _AuthScreenState extends State { style: Theme.of(context).textTheme.titleLarge, ), ), - const SizedBox(height: 12.0), + const SizedBox(height: 32.0), Text( s.authScreenDescription, style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 16.0), BlocBuilder( - bloc: _authBloc, - builder: (BuildContext context, AuthState state) { - return TextFieldWidget( - textEditingController: _emailController, - focusNode: _emailFocusNode, - hintText: s.authScreenEmailPlaceholder, - error: state.validationError, - ); - }), + bloc: _authBloc, + builder: (BuildContext context, AuthState state) { + return TextFieldWidget( + textEditingController: _emailController, + focusNode: _emailFocusNode, + hintText: s.authScreenEmailPlaceholder, + error: state.validationError, + ); + } + ), Padding( padding: const EdgeInsets.only( top: 60.0, @@ -109,9 +112,9 @@ class _AuthScreenState extends State { builder: (BuildContext context, AuthState state) { return Checkbox( materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, + MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4.0)), + borderRadius: BorderRadius.circular(4.0)), side: const BorderSide( color: Colors.black, width: 1.0, @@ -121,7 +124,7 @@ class _AuthScreenState extends State { value: state.privacyPolicyAccepted, onChanged: (bool? value) => _authBloc.add( ChangePrivacyPolicyStatusEvent( - value ?? false), + value ?? false), ), ); }, @@ -138,23 +141,23 @@ class _AuthScreenState extends State { TextSpan( recognizer: _termsConditionsTapRecognizer ..onTap = () => - launchLink('https://google.com'), + launchLink('https://google.com'), text: s.termsAndConditionsLabel, style: Theme.of(context) - .textTheme - .bodySmall + .textTheme + .bodySmall ?.copyWith(color: AppColors.brightBlue), ), TextSpan(text: s.andLabel), TextSpan( recognizer: _privacyPolicyTapRecognizer ..onTap = () => - launchLink('https://google.com'), + launchLink('https://google.com'), text: s.privacyPolicyLabel, style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: AppColors.brightBlue), + .textTheme + .bodySmall + ?.copyWith(color: AppColors.brightBlue), ), ], ), @@ -163,11 +166,19 @@ class _AuthScreenState extends State { ], ), ), - BlocBuilder( + BlocConsumer( bloc: _authBloc, + listener: (BuildContext context, AuthState state) { + if (state.status == AuthStatus.success) { + _authBloc.add(ClearStateEvent()); + _emailController.clear(); + context.pushNamed(AppRoutes.verification); + } + }, builder: (BuildContext context, AuthState state) { return ElevatedButtonWidget( isDisabled: _authBloc.isButtonDisabled, + isLoading: state.status == AuthStatus.loading, onPressed: () => _authBloc.add(AuthenticateByEmailEvent()), buttonLabel: s.authScreenButtonLabel, diff --git a/lib/src/presentation/features/auth/screens/verification_screen.dart b/lib/src/presentation/features/auth/screens/verification_screen.dart new file mode 100644 index 0000000..036c540 --- /dev/null +++ b/lib/src/presentation/features/auth/screens/verification_screen.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:golub/generated/l10n.dart'; +import 'package:golub/src/presentation/features/auth/widgets/verification/verification_input_widget.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_assets.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; +import 'package:golub/src/presentation/ui_kit/ui.dart'; + +class VerificationScreen extends StatelessWidget { + const VerificationScreen({super.key}); + + @override + Widget build(BuildContext context) { + final s = S.of(context); + return Scaffold( + body: Container( + constraints: const BoxConstraints.expand(), + decoration: const BoxDecoration( + gradient: AppStyles.blueWhiteGradient, + ), + child: SafeArea( + bottom: false, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + children: [ + Align( + alignment: Alignment.topLeft, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => context.pop(), + child: SvgPicture.asset( + AppAssets.arrowBackIcon, + fit: BoxFit.cover, + ), + ), + ), + SizedBox( + width: 64.0, + height: 64.0, + child: SvgPicture.asset( + AppAssets.chatsIcon, + fit: BoxFit.cover, + ), + ), + const SizedBox(height: 32.0), + Text( + s.verificationScreenTitle, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 12.0), + Text( + s.verificationScreenDescription, + style: Theme.of(context).textTheme.bodyMedium, + ), + Container( + padding: const EdgeInsets.only(top: 40.0, bottom: 28.0), + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 1, + child: VerificationInputWidget(), + ), + SizedBox(width: 8.0), + Expanded( + flex: 1, + child: VerificationInputWidget(), + ), + SizedBox(width: 8.0), + Expanded( + flex: 1, + child: VerificationInputWidget(), + ), + SizedBox(width: 8.0), + Expanded( + flex: 1, + child: VerificationInputWidget(), + ), + SizedBox(width: 8.0), + Expanded( + flex: 1, + child: VerificationInputWidget(), + ) + ], + ), + ), + Text( + 'Didn\'t get anything? Resend me code.' + ), + const SizedBox(height: 36.0), + ElevatedButtonWidget( + buttonLabel: 'Next', + onPressed: () {} + ), + ] + ), + ), + ), + ), + ); + } +} diff --git a/lib/src/presentation/features/auth/widgets/verification/verification_input_widget.dart b/lib/src/presentation/features/auth/widgets/verification/verification_input_widget.dart new file mode 100644 index 0000000..a37fab9 --- /dev/null +++ b/lib/src/presentation/features/auth/widgets/verification/verification_input_widget.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; + +class VerificationInputWidget extends StatelessWidget { + + const VerificationInputWidget({ + super.key + }); + + @override + Widget build(BuildContext context) { + return Container( + constraints: const BoxConstraints( + maxWidth: 56.0, + minHeight: 76.0, + ), + child: TextField( + keyboardType: TextInputType.number, + inputFormatters: [ + LengthLimitingTextInputFormatter(1), + FilteringTextInputFormatter.digitsOnly, + ], + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge, + decoration: InputDecoration( + fillColor: Colors.transparent, + contentPadding: const EdgeInsets.symmetric( + vertical: 24.0, horizontal: 12.0), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: AppColors.brightBlue, + width: 1.5, + ), + borderRadius: BorderRadius.circular(16.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.baseGray3), + borderRadius: BorderRadius.circular(16.0), + ), + disabledBorder: OutlineInputBorder( + borderSide: const BorderSide(color: AppColors.baseWhite), + borderRadius: BorderRadius.circular(16.0), + ), + ), + ), + ); + } +} diff --git a/lib/src/presentation/features/splash/screens/splash_screen.dart b/lib/src/presentation/features/splash/screens/splash_screen.dart index c7a4bb3..6e65993 100644 --- a/lib/src/presentation/features/splash/screens/splash_screen.dart +++ b/lib/src/presentation/features/splash/screens/splash_screen.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; -import 'package:golub/src/presentation/navigation/app_routes.dart'; +import 'package:golub/src/presentation/navigation/app_router.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_assets.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; diff --git a/lib/src/presentation/navigation/app_router.dart b/lib/src/presentation/navigation/app_router.dart index c9f2b9b..7f8697f 100644 --- a/lib/src/presentation/navigation/app_router.dart +++ b/lib/src/presentation/navigation/app_router.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:golub/src/presentation/features/auth/screens/auth_screen.dart'; +import 'package:golub/src/presentation/features/auth/screens/verification_screen.dart'; import 'package:golub/src/presentation/features/chats/screens/chats_screen.dart'; import 'package:golub/src/presentation/features/contacts/screens/contacts_screen.dart'; import 'package:golub/src/presentation/features/profile/screens/profile_screen.dart'; import 'package:golub/src/presentation/features/splash/screens/splash_screen.dart'; import 'package:golub/src/presentation/navigation/app_navigation_shell.dart'; -import 'package:golub/src/presentation/navigation/app_routes.dart'; + +part 'app_routes.dart'; final _rootNavigatorKey = GlobalKey(); @@ -32,6 +34,13 @@ final routerConfig = GoRouter( return const AuthScreen(); }, ), + GoRoute( + name: AppRoutes.verification, + path: AppRoutes.getPath(AppRoutes.verification), + builder: (BuildContext context, GoRouterState state) { + return const VerificationScreen(); + }, + ), StatefulShellRoute.indexedStack( builder: (BuildContext context, GoRouterState state, StatefulNavigationShell navigationShell) { diff --git a/lib/src/presentation/navigation/app_routes.dart b/lib/src/presentation/navigation/app_routes.dart index 0d38252..e0563d1 100644 --- a/lib/src/presentation/navigation/app_routes.dart +++ b/lib/src/presentation/navigation/app_routes.dart @@ -1,10 +1,12 @@ -/// App routes +part of 'app_router.dart'; + class AppRoutes { static const String splash = 'splash'; static const String auth = 'auth'; static const String contacts = 'contacts'; static const String chats = 'chats'; static const String profile = 'profile'; + static const String verification = 'verification'; static String getPath(String routeName) => '/$routeName'; } diff --git a/lib/src/presentation/ui_kit/theme/app_assets.dart b/lib/src/presentation/ui_kit/theme/app_assets.dart index 108189d..17fcf56 100644 --- a/lib/src/presentation/ui_kit/theme/app_assets.dart +++ b/lib/src/presentation/ui_kit/theme/app_assets.dart @@ -2,9 +2,14 @@ class AppAssets { static const String _base = 'assets'; static const String _icons = '$_base/icons'; static const String _logo = '$_base/logo'; + static const String _images = '$_base/images'; static const String splashLogo = '$_logo/logo.svg'; static const String contactIcon = '$_icons/contacts.svg'; static const String chatIcon = '$_icons/chat.svg'; static const String profileIcon = '$_icons/profile.svg'; + + static const String arrowBackIcon = '$_icons/arrow_back.svg'; + + static const String chatsIcon = '$_images/chats.svg'; } diff --git a/pubspec.yaml b/pubspec.yaml index a50a1f3..780a01a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,6 +43,7 @@ flutter: assets: - assets/icons/ - assets/logo/ + - assets/images/ fonts: - family: Ubuntu From 0b01e2369dae13a5a29737c2d10a5673d6bdd2e6 Mon Sep 17 00:00:00 2001 From: denid88 Date: Wed, 24 Jan 2024 10:45:47 +0200 Subject: [PATCH 07/37] [Translations] - changed approach to i18n --- assets/icons/camera.svg | 3 + assets/icons/user_profile.svg | 22 ++ ios/Flutter/AppFrameworkInfo.plist | 2 +- ios/Podfile | 2 +- ios/Podfile.lock | 6 +- ios/Runner.xcodeproj/project.pbxproj | 6 +- ios/Runner/Info.plist | 5 + lib/generated/intl/messages_all.dart | 67 ------ lib/generated/intl/messages_en.dart | 46 ---- lib/generated/intl/messages_uk.dart | 45 ---- lib/generated/l10n.dart | 199 ------------------ lib/i18n/.gitignore | 1 + lib/i18n/localization_en.i18n.json | 32 +++ lib/i18n/localization_uk.i18n.json | 32 +++ lib/l10n/intl_en.arb | 15 -- lib/l10n/intl_uk.arb | 15 -- lib/main.dart | 7 +- lib/src/app.dart | 17 +- lib/src/domain/blocs/auth/auth_bloc.dart | 18 +- .../features/auth/screens/auth_screen.dart | 79 ++++--- .../auth/screens/verification_screen.dart | 155 ++++++++------ .../verification_input_widget.dart | 11 +- .../onboarding/onboarding_profile_screen.dart | 48 +++++ .../app_bottom_navigation_bar_widget.dart | 37 ++-- .../presentation/navigation/app_router.dart | 8 + .../presentation/navigation/app_routes.dart | 1 + .../presentation/ui_kit/theme/app_assets.dart | 2 + .../ui_kit/theme/themes/light_theme.dart | 13 +- .../widgets/input/profile_upload_image.dart | 61 ++++++ .../widgets/input/text_field_widget.dart | 20 +- pubspec.lock | 66 ++++-- pubspec.yaml | 6 +- 32 files changed, 464 insertions(+), 583 deletions(-) create mode 100644 assets/icons/camera.svg create mode 100644 assets/icons/user_profile.svg delete mode 100644 lib/generated/intl/messages_all.dart delete mode 100644 lib/generated/intl/messages_en.dart delete mode 100644 lib/generated/intl/messages_uk.dart delete mode 100644 lib/generated/l10n.dart create mode 100644 lib/i18n/.gitignore create mode 100644 lib/i18n/localization_en.i18n.json create mode 100644 lib/i18n/localization_uk.i18n.json delete mode 100644 lib/l10n/intl_en.arb delete mode 100644 lib/l10n/intl_uk.arb create mode 100644 lib/src/presentation/features/onboarding/onboarding_profile_screen.dart create mode 100644 lib/src/presentation/ui_kit/widgets/input/profile_upload_image.dart diff --git a/assets/icons/camera.svg b/assets/icons/camera.svg new file mode 100644 index 0000000..2267f52 --- /dev/null +++ b/assets/icons/camera.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/user_profile.svg b/assets/icons/user_profile.svg new file mode 100644 index 0000000..a8eba92 --- /dev/null +++ b/assets/icons/user_profile.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 9625e10..7c56964 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 12.0 diff --git a/ios/Podfile b/ios/Podfile index fdcc671..d97f17e 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '11.0' +# platform :ios, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3d81393..ad6510d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -14,9 +14,9 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b -PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189 +PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 -COCOAPODS: 1.11.3 +COCOAPODS: 1.14.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 71e169b..08977f1 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -453,7 +453,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -581,7 +581,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -630,7 +630,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 6de8b10..f2a3a6f 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -45,5 +45,10 @@ UIApplicationSupportsIndirectInputEvents + CFBundleLocalizations + + en + uk + diff --git a/lib/generated/intl/messages_all.dart b/lib/generated/intl/messages_all.dart deleted file mode 100644 index 9dc98bc..0000000 --- a/lib/generated/intl/messages_all.dart +++ /dev/null @@ -1,67 +0,0 @@ -// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart -// This is a library that looks up messages for specific locales by -// delegating to the appropriate library. - -// Ignore issues from commonly used lints in this file. -// ignore_for_file:implementation_imports, file_names, unnecessary_new -// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering -// ignore_for_file:argument_type_not_assignable, invalid_assignment -// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases -// ignore_for_file:comment_references - -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:intl/intl.dart'; -import 'package:intl/message_lookup_by_library.dart'; -import 'package:intl/src/intl_helpers.dart'; - -import 'messages_en.dart' as messages_en; -import 'messages_uk.dart' as messages_uk; - -typedef Future LibraryLoader(); -Map _deferredLibraries = { - 'en': () => new SynchronousFuture(null), - 'uk': () => new SynchronousFuture(null), -}; - -MessageLookupByLibrary? _findExact(String localeName) { - switch (localeName) { - case 'en': - return messages_en.messages; - case 'uk': - return messages_uk.messages; - default: - return null; - } -} - -/// User programs should call this before using [localeName] for messages. -Future initializeMessages(String localeName) { - var availableLocale = Intl.verifiedLocale( - localeName, (locale) => _deferredLibraries[locale] != null, - onFailure: (_) => null); - if (availableLocale == null) { - return new SynchronousFuture(false); - } - var lib = _deferredLibraries[availableLocale]; - lib == null ? new SynchronousFuture(false) : lib(); - initializeInternalMessageLookup(() => new CompositeMessageLookup()); - messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); - return new SynchronousFuture(true); -} - -bool _messagesExistFor(String locale) { - try { - return _findExact(locale) != null; - } catch (e) { - return false; - } -} - -MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { - var actualLocale = - Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); - if (actualLocale == null) return null; - return _findExact(actualLocale); -} diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart deleted file mode 100644 index 6564b4d..0000000 --- a/lib/generated/intl/messages_en.dart +++ /dev/null @@ -1,46 +0,0 @@ -// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart -// This is a library that provides messages for a en locale. All the -// messages from the main program should be duplicated here with the same -// function name. - -// Ignore issues from commonly used lints in this file. -// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new -// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering -// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases -// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes -// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes - -import 'package:intl/intl.dart'; -import 'package:intl/message_lookup_by_library.dart'; - -final messages = new MessageLookup(); - -typedef String MessageIfAbsent(String messageStr, List args); - -class MessageLookup extends MessageLookupByLibrary { - String get localeName => 'en'; - - final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "andLabel": MessageLookupByLibrary.simpleMessage(" and "), - "authScreenButtonLabel": MessageLookupByLibrary.simpleMessage("Next"), - "authScreenDescription": MessageLookupByLibrary.simpleMessage( - "Enter your email and get dynamically generated code"), - "authScreenEmailPlaceholder": - MessageLookupByLibrary.simpleMessage("Email"), - "authScreenTitle": - MessageLookupByLibrary.simpleMessage("Authentication"), - "authScreenValidationEmailError": - MessageLookupByLibrary.simpleMessage("Please enter a valid email"), - "iAgreeLabel": MessageLookupByLibrary.simpleMessage("I agree to "), - "labelContacts": MessageLookupByLibrary.simpleMessage("Contacts"), - "privacyPolicyLabel": - MessageLookupByLibrary.simpleMessage("Privacy Policy"), - "termsAndConditionsLabel": - MessageLookupByLibrary.simpleMessage("Terms & Conditions"), - "verificationScreenDescription": MessageLookupByLibrary.simpleMessage( - "Paste dynamically generated code"), - "verificationScreenTitle": - MessageLookupByLibrary.simpleMessage("Check your Email") - }; -} diff --git a/lib/generated/intl/messages_uk.dart b/lib/generated/intl/messages_uk.dart deleted file mode 100644 index e06b9c4..0000000 --- a/lib/generated/intl/messages_uk.dart +++ /dev/null @@ -1,45 +0,0 @@ -// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart -// This is a library that provides messages for a uk locale. All the -// messages from the main program should be duplicated here with the same -// function name. - -// Ignore issues from commonly used lints in this file. -// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new -// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering -// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases -// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes -// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes - -import 'package:intl/intl.dart'; -import 'package:intl/message_lookup_by_library.dart'; - -final messages = new MessageLookup(); - -typedef String MessageIfAbsent(String messageStr, List args); - -class MessageLookup extends MessageLookupByLibrary { - String get localeName => 'uk'; - - final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "andLabel": MessageLookupByLibrary.simpleMessage(" та "), - "authScreenButtonLabel": MessageLookupByLibrary.simpleMessage("Далі"), - "authScreenDescription": MessageLookupByLibrary.simpleMessage( - "Введіть адрес вашої електроної пошти та отримайте код авторизації"), - "authScreenEmailPlaceholder": - MessageLookupByLibrary.simpleMessage("Емайл"), - "authScreenTitle": MessageLookupByLibrary.simpleMessage("Авторизація"), - "authScreenValidationEmailError": - MessageLookupByLibrary.simpleMessage("Введіть коректний емайл"), - "iAgreeLabel": MessageLookupByLibrary.simpleMessage("Я погоджуюсь з "), - "labelContacts": MessageLookupByLibrary.simpleMessage("Контакти"), - "privacyPolicyLabel": - MessageLookupByLibrary.simpleMessage("Політикою приватності"), - "termsAndConditionsLabel": - MessageLookupByLibrary.simpleMessage("Умовами та положеннями"), - "verificationScreenDescription": MessageLookupByLibrary.simpleMessage( - "Вставте динамічно згенерований код"), - "verificationScreenTitle": MessageLookupByLibrary.simpleMessage( - "Перевірте свою електронну пошту") - }; -} diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart deleted file mode 100644 index fd52b65..0000000 --- a/lib/generated/l10n.dart +++ /dev/null @@ -1,199 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; -import 'intl/messages_all.dart'; - -// ************************************************************************** -// Generator: Flutter Intl IDE plugin -// Made by Localizely -// ************************************************************************** - -// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars -// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each -// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes - -class S { - S(); - - static S? _current; - - static S get current { - assert(_current != null, - 'No instance of S was loaded. Try to initialize the S delegate before accessing S.current.'); - return _current!; - } - - static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); - - static Future load(Locale locale) { - final name = (locale.countryCode?.isEmpty ?? false) - ? locale.languageCode - : locale.toString(); - final localeName = Intl.canonicalizedLocale(name); - return initializeMessages(localeName).then((_) { - Intl.defaultLocale = localeName; - final instance = S(); - S._current = instance; - - return instance; - }); - } - - static S of(BuildContext context) { - final instance = S.maybeOf(context); - assert(instance != null, - 'No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates?'); - return instance!; - } - - static S? maybeOf(BuildContext context) { - return Localizations.of(context, S); - } - - /// `Contacts` - String get labelContacts { - return Intl.message( - 'Contacts', - name: 'labelContacts', - desc: '', - args: [], - ); - } - - /// `Authentication` - String get authScreenTitle { - return Intl.message( - 'Authentication', - name: 'authScreenTitle', - desc: '', - args: [], - ); - } - - /// `Enter your email and get dynamically generated code` - String get authScreenDescription { - return Intl.message( - 'Enter your email and get dynamically generated code', - name: 'authScreenDescription', - desc: '', - args: [], - ); - } - - /// `Next` - String get authScreenButtonLabel { - return Intl.message( - 'Next', - name: 'authScreenButtonLabel', - desc: '', - args: [], - ); - } - - /// `Email` - String get authScreenEmailPlaceholder { - return Intl.message( - 'Email', - name: 'authScreenEmailPlaceholder', - desc: '', - args: [], - ); - } - - /// `Please enter a valid email` - String get authScreenValidationEmailError { - return Intl.message( - 'Please enter a valid email', - name: 'authScreenValidationEmailError', - desc: '', - args: [], - ); - } - - /// `I agree to ` - String get iAgreeLabel { - return Intl.message( - 'I agree to ', - name: 'iAgreeLabel', - desc: '', - args: [], - ); - } - - /// `Privacy Policy` - String get privacyPolicyLabel { - return Intl.message( - 'Privacy Policy', - name: 'privacyPolicyLabel', - desc: '', - args: [], - ); - } - - /// `Terms & Conditions` - String get termsAndConditionsLabel { - return Intl.message( - 'Terms & Conditions', - name: 'termsAndConditionsLabel', - desc: '', - args: [], - ); - } - - /// ` and ` - String get andLabel { - return Intl.message( - ' and ', - name: 'andLabel', - desc: '', - args: [], - ); - } - - /// `Check your Email` - String get verificationScreenTitle { - return Intl.message( - 'Check your Email', - name: 'verificationScreenTitle', - desc: '', - args: [], - ); - } - - /// `Paste dynamically generated code` - String get verificationScreenDescription { - return Intl.message( - 'Paste dynamically generated code', - name: 'verificationScreenDescription', - desc: '', - args: [], - ); - } -} - -class AppLocalizationDelegate extends LocalizationsDelegate { - const AppLocalizationDelegate(); - - List get supportedLocales { - return const [ - Locale.fromSubtags(languageCode: 'en'), - Locale.fromSubtags(languageCode: 'uk'), - ]; - } - - @override - bool isSupported(Locale locale) => _isSupported(locale); - @override - Future load(Locale locale) => S.load(locale); - @override - bool shouldReload(AppLocalizationDelegate old) => false; - - bool _isSupported(Locale locale) { - for (var supportedLocale in supportedLocales) { - if (supportedLocale.languageCode == locale.languageCode) { - return true; - } - } - return false; - } -} diff --git a/lib/i18n/.gitignore b/lib/i18n/.gitignore new file mode 100644 index 0000000..2536133 --- /dev/null +++ b/lib/i18n/.gitignore @@ -0,0 +1 @@ +*.g.dart \ No newline at end of file diff --git a/lib/i18n/localization_en.i18n.json b/lib/i18n/localization_en.i18n.json new file mode 100644 index 0000000..0454ea0 --- /dev/null +++ b/lib/i18n/localization_en.i18n.json @@ -0,0 +1,32 @@ +{ + "@@locale": "en", + "common": { + }, + "errors": { + "validation": { + "email": "Please enter a valid email." + } + }, + "screens": { + "auth": { + "title": "Authentication", + "description": "Please enter your credentials to login.", + "buttonLabel": "Next", + "emailPlaceholder": "Email", + "statementIAgree": "I agree to ", + "statementPrivacyPolicy": "Privacy Policy", + "statementTermsAndConditions": "Terms & Conditions", + "statementAnd": " and " + }, + "verification": { + "title": "Check your Email", + "description": "Paste dynamically generated code", + "buttonLabel": "Next", + "noCodeQuestion": "Didn't get anything?", + "noCodeButtonLabel": "Resend code" + }, + "onboardingProfile": { + "title": "Profile" + } + } +} diff --git a/lib/i18n/localization_uk.i18n.json b/lib/i18n/localization_uk.i18n.json new file mode 100644 index 0000000..2fa6178 --- /dev/null +++ b/lib/i18n/localization_uk.i18n.json @@ -0,0 +1,32 @@ +{ + "@@locale": "uk", + "common": { + }, + "errors": { + "validation": { + "email": "Введіть коректний email" + } + }, + "screens": { + "auth": { + "title": "Авторизація", + "description": "Введіть email для авторизації", + "buttonLabel": "Далі", + "emailPlaceholder": "Введіть email", + "statementIAgree": "Я погоджуюсь з ", + "statementPrivacyPolicy": "Політикою конфіденційності", + "statementTermsAndConditions": "Умовами використання", + "statementAnd": " та " + }, + "verification": { + "title": "Перевірте електронну пошту", + "description": "Ми надіслали вам код підтвердження на електронну пошту", + "buttonLabel": "Далі", + "noCodeQuestion": "Не отримали код?", + "noCodeButtonLabel": "Відправити ще раз" + }, + "onboardingProfile": { + "title": "Профіль" + } + } +} \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb deleted file mode 100644 index b227916..0000000 --- a/lib/l10n/intl_en.arb +++ /dev/null @@ -1,15 +0,0 @@ -{ - "@@locale": "en", - "labelContacts": "Contacts", - "authScreenTitle": "Authentication", - "authScreenDescription": "Enter your email and get dynamically generated code", - "authScreenButtonLabel": "Next", - "authScreenEmailPlaceholder": "Email", - "authScreenValidationEmailError": "Please enter a valid email", - "iAgreeLabel": "I agree to ", - "privacyPolicyLabel": "Privacy Policy", - "termsAndConditionsLabel": "Terms & Conditions", - "andLabel": " and ", - "verificationScreenTitle": "Check your Email", - "verificationScreenDescription": "Paste dynamically generated code" -} \ No newline at end of file diff --git a/lib/l10n/intl_uk.arb b/lib/l10n/intl_uk.arb deleted file mode 100644 index 8766f1f..0000000 --- a/lib/l10n/intl_uk.arb +++ /dev/null @@ -1,15 +0,0 @@ -{ - "@@locale": "uk", - "labelContacts": "Контакти", - "authScreenTitle": "Авторизація", - "authScreenDescription": "Введіть адрес вашої електроної пошти та отримайте код авторизації", - "authScreenButtonLabel": "Далі", - "authScreenEmailPlaceholder": "Емайл", - "authScreenValidationEmailError": "Введіть коректний емайл", - "iAgreeLabel": "Я погоджуюсь з ", - "privacyPolicyLabel": "Політикою приватності", - "termsAndConditionsLabel": "Умовами та положеннями", - "andLabel": " та ", - "verificationScreenTitle": "Перевірте свою електронну пошту", - "verificationScreenDescription": "Вставте динамічно згенерований код" -} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 0309b1f..2e5c164 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,18 +1,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:golub/i18n/strings.g.dart'; import 'package:golub/src/app.dart'; import 'package:golub/src/core/di/injectable.dart'; -import 'package:golub/src/data/services/dio/dio_client.dart'; import 'package:golub/src/presentation/ui_kit/ui.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); + LocaleSettings.useDeviceLocale(); await _precacheLogoImage(); configureDependencies(); - runApp(const App()); + runApp(TranslationProvider(child: const App())); } Future _precacheLogoImage() async { const loader = SvgAssetLoader(AppAssets.splashLogo); svg.cache.putIfAbsent(loader.cacheKey(null), () => loader.loadBytes(null)); -} \ No newline at end of file +} diff --git a/lib/src/app.dart b/lib/src/app.dart index d0c64a9..dbad391 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:golub/generated/l10n.dart'; -import 'package:golub/src/presentation/navigation/app_router.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:golub/i18n/strings.g.dart'; +import 'package:golub/src/presentation/navigation/app_router.dart'; import 'package:golub/src/presentation/ui_kit/theme/themes/light_theme.dart'; class App extends StatelessWidget { @@ -15,16 +15,9 @@ class App extends StatelessWidget { // theme: themeState.currentTheme, // darkTheme: themeState.currentThemeDark, theme: lightTheme, - localizationsDelegates: const [ - S.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: const [ - Locale('en', 'US'), - Locale('uk', 'UA'), - ], + locale: TranslationProvider.of(context).flutterLocale, + localizationsDelegates: GlobalMaterialLocalizations.delegates, + supportedLocales: AppLocaleUtils.supportedLocales, routerConfig: routerConfig, ); } diff --git a/lib/src/domain/blocs/auth/auth_bloc.dart b/lib/src/domain/blocs/auth/auth_bloc.dart index 367edf8..a7cdb13 100644 --- a/lib/src/domain/blocs/auth/auth_bloc.dart +++ b/lib/src/domain/blocs/auth/auth_bloc.dart @@ -1,6 +1,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:golub/generated/l10n.dart'; +import 'package:golub/i18n/strings.g.dart'; import 'package:golub/src/domain/extenstions/string_validation_ext.dart'; import 'package:golub/src/domain/usecases/auth_by_email_usecase.dart'; import 'package:injectable/injectable.dart'; @@ -20,21 +20,19 @@ class AuthBloc extends Bloc { on(_clearStateEvent); } - Future _changeEmailEvent( - ChangeEmailEvent event, Emitter emit) async { + Future _changeEmailEvent(ChangeEmailEvent event, Emitter emit) async { emit(state.copyWith(email: event.value, validationError: null)); } Future _changePrivacyPolicyStatusEvent( - ChangePrivacyPolicyStatusEvent event, Emitter emit) async { + ChangePrivacyPolicyStatusEvent event, Emitter emit) async { emit(state.copyWith(privacyPolicyAccepted: event.value)); } Future _authenticateByEmailEvent( - AuthenticateByEmailEvent event, Emitter emit) async { + AuthenticateByEmailEvent event, Emitter emit) async { if (!state.email.isValidEmail) { - emit(state.copyWith( - validationError: S.current.authScreenValidationEmailError)); + emit(state.copyWith(validationError: t.errors.validation.email)); return; } @@ -48,12 +46,10 @@ class AuthBloc extends Bloc { } } - Future _clearStateEvent( - ClearStateEvent event, Emitter emit) async { + Future _clearStateEvent(ClearStateEvent event, Emitter emit) async { emit(AuthState.initial()); } /// Getters - bool get isButtonDisabled => - !state.privacyPolicyAccepted || state.email.isEmpty; + bool get isButtonDisabled => !state.privacyPolicyAccepted || state.email.isEmpty; } diff --git a/lib/src/presentation/features/auth/screens/auth_screen.dart b/lib/src/presentation/features/auth/screens/auth_screen.dart index c8b1522..c8a21b7 100644 --- a/lib/src/presentation/features/auth/screens/auth_screen.dart +++ b/lib/src/presentation/features/auth/screens/auth_screen.dart @@ -2,7 +2,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; -import 'package:golub/generated/l10n.dart'; +import 'package:golub/i18n/strings.g.dart'; import 'package:golub/src/core/di/injectable.dart'; import 'package:golub/src/domain/blocs/auth/auth_bloc.dart'; import 'package:golub/src/presentation/navigation/app_router.dart'; @@ -22,18 +22,19 @@ class AuthScreen extends StatefulWidget { class _AuthScreenState extends State { final AuthBloc _authBloc = getIt(); - final TextEditingController _emailController = TextEditingController(); + final TextEditingController _emailController = TextEditingController( + //text: 'test@gmail.com', + ); final FocusNode _emailFocusNode = FocusNode(); - final TapGestureRecognizer _termsConditionsTapRecognizer = - TapGestureRecognizer(); - final TapGestureRecognizer _privacyPolicyTapRecognizer = - TapGestureRecognizer(); + final TapGestureRecognizer _termsConditionsTapRecognizer = TapGestureRecognizer(); + final TapGestureRecognizer _privacyPolicyTapRecognizer = TapGestureRecognizer(); @override void initState() { super.initState(); _emailController.addListener(_handleEmailString); + _authBloc.add(ChangeEmailEvent(_emailController.text)); } void _handleEmailString() { @@ -51,7 +52,6 @@ class _AuthScreenState extends State { @override Widget build(BuildContext context) { - final s = S.of(context); final size = MediaQuery.of(context).size; return BlocProvider( @@ -64,8 +64,7 @@ class _AuthScreenState extends State { ), child: SafeArea( child: Padding( - padding: - const EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0), + padding: const EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -75,27 +74,26 @@ class _AuthScreenState extends State { top: size.height / 6, ), child: Text( - s.authScreenTitle, + t.screens.auth.title, style: Theme.of(context).textTheme.titleLarge, ), ), const SizedBox(height: 32.0), Text( - s.authScreenDescription, + t.screens.auth.description, style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: 16.0), BlocBuilder( - bloc: _authBloc, - builder: (BuildContext context, AuthState state) { - return TextFieldWidget( - textEditingController: _emailController, - focusNode: _emailFocusNode, - hintText: s.authScreenEmailPlaceholder, - error: state.validationError, - ); - } - ), + bloc: _authBloc, + builder: (BuildContext context, AuthState state) { + return TextFieldWidget( + textEditingController: _emailController, + focusNode: _emailFocusNode, + hintText: t.screens.auth.emailPlaceholder, + error: state.validationError, + ); + }), Padding( padding: const EdgeInsets.only( top: 60.0, @@ -111,10 +109,9 @@ class _AuthScreenState extends State { bloc: _authBloc, builder: (BuildContext context, AuthState state) { return Checkbox( - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4.0)), + borderRadius: BorderRadius.circular(4.0)), side: const BorderSide( color: Colors.black, width: 1.0, @@ -123,8 +120,7 @@ class _AuthScreenState extends State { activeColor: AppColors.brightLightPurple, value: state.privacyPolicyAccepted, onChanged: (bool? value) => _authBloc.add( - ChangePrivacyPolicyStatusEvent( - value ?? false), + ChangePrivacyPolicyStatusEvent(value ?? false), ), ); }, @@ -135,29 +131,29 @@ class _AuthScreenState extends State { child: RichText( maxLines: 2, text: TextSpan( - text: s.iAgreeLabel, + text: t.screens.auth.statementIAgree, style: Theme.of(context).textTheme.bodySmall, children: [ TextSpan( recognizer: _termsConditionsTapRecognizer - ..onTap = () => - launchLink('https://google.com'), - text: s.termsAndConditionsLabel, + ..onTap = () => launchLink('https://google.com'), + text: t.screens.auth.statementTermsAndConditions, style: Theme.of(context) - .textTheme - .bodySmall + .textTheme + .bodySmall ?.copyWith(color: AppColors.brightBlue), ), - TextSpan(text: s.andLabel), + TextSpan( + text: t.screens.auth.statementAnd, + ), TextSpan( recognizer: _privacyPolicyTapRecognizer - ..onTap = () => - launchLink('https://google.com'), - text: s.privacyPolicyLabel, + ..onTap = () => launchLink('https://google.com'), + text: t.screens.auth.statementPrivacyPolicy, style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: AppColors.brightBlue), + .textTheme + .bodySmall + ?.copyWith(color: AppColors.brightBlue), ), ], ), @@ -179,9 +175,8 @@ class _AuthScreenState extends State { return ElevatedButtonWidget( isDisabled: _authBloc.isButtonDisabled, isLoading: state.status == AuthStatus.loading, - onPressed: () => - _authBloc.add(AuthenticateByEmailEvent()), - buttonLabel: s.authScreenButtonLabel, + onPressed: () => _authBloc.add(AuthenticateByEmailEvent()), + buttonLabel: t.screens.auth.buttonLabel, ); }, ), diff --git a/lib/src/presentation/features/auth/screens/verification_screen.dart b/lib/src/presentation/features/auth/screens/verification_screen.dart index 036c540..2bc1450 100644 --- a/lib/src/presentation/features/auth/screens/verification_screen.dart +++ b/lib/src/presentation/features/auth/screens/verification_screen.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; -import 'package:golub/generated/l10n.dart'; import 'package:golub/src/presentation/features/auth/widgets/verification/verification_input_widget.dart'; -import 'package:golub/src/presentation/ui_kit/theme/app_assets.dart'; +import 'package:golub/src/presentation/navigation/app_router.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; import 'package:golub/src/presentation/ui_kit/ui.dart'; @@ -13,7 +12,6 @@ class VerificationScreen extends StatelessWidget { @override Widget build(BuildContext context) { - final s = S.of(context); return Scaffold( body: Container( constraints: const BoxConstraints.expand(), @@ -24,79 +22,96 @@ class VerificationScreen extends StatelessWidget { bottom: false, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Column( - children: [ - Align( - alignment: Alignment.topLeft, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => context.pop(), - child: SvgPicture.asset( - AppAssets.arrowBackIcon, - fit: BoxFit.cover, - ), - ), - ), - SizedBox( - width: 64.0, - height: 64.0, + child: Column(children: [ + Align( + alignment: Alignment.topLeft, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => context.pop(), child: SvgPicture.asset( - AppAssets.chatsIcon, + AppAssets.arrowBackIcon, fit: BoxFit.cover, ), ), - const SizedBox(height: 32.0), - Text( - s.verificationScreenTitle, - style: Theme.of(context).textTheme.titleLarge, + ), + SizedBox( + width: 64.0, + height: 64.0, + child: SvgPicture.asset( + AppAssets.chatsIcon, + fit: BoxFit.cover, ), - const SizedBox(height: 12.0), - Text( - s.verificationScreenDescription, - style: Theme.of(context).textTheme.bodyMedium, + ), + const SizedBox(height: 32.0), + Text( + '', //s.verificationScreenTitle, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 12.0), + Text( + '', //s.verificationScreenDescription, + style: Theme.of(context).textTheme.bodyMedium, + ), + Container( + padding: const EdgeInsets.only(top: 40.0, bottom: 28.0), + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 1, + child: VerificationInputWidget(), + ), + SizedBox(width: 8.0), + Expanded( + flex: 1, + child: VerificationInputWidget(), + ), + SizedBox(width: 8.0), + Expanded( + flex: 1, + child: VerificationInputWidget(), + ), + SizedBox(width: 8.0), + Expanded( + flex: 1, + child: VerificationInputWidget(), + ), + SizedBox(width: 8.0), + Expanded( + flex: 1, + child: VerificationInputWidget(), + ) + ], ), - Container( - padding: const EdgeInsets.only(top: 40.0, bottom: 28.0), - child: const Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - flex: 1, - child: VerificationInputWidget(), - ), - SizedBox(width: 8.0), - Expanded( - flex: 1, - child: VerificationInputWidget(), - ), - SizedBox(width: 8.0), - Expanded( - flex: 1, - child: VerificationInputWidget(), - ), - SizedBox(width: 8.0), - Expanded( - flex: 1, - child: VerificationInputWidget(), - ), - SizedBox(width: 8.0), - Expanded( - flex: 1, - child: VerificationInputWidget(), - ) - ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '', //s.verificationScreenNoCodeQuestion, ), - ), - Text( - 'Didn\'t get anything? Resend me code.' - ), - const SizedBox(height: 36.0), - ElevatedButtonWidget( - buttonLabel: 'Next', - onPressed: () {} - ), - ] - ), + const SizedBox(width: 4.0), + GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + print('resend code'); + }, + child: Text( + '', //s.verificationScreenNoCodeButtonLabel, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: AppColors.brightOrange, + ), + ), + ), + ], + ), + const SizedBox(height: 36.0), + ElevatedButtonWidget( + buttonLabel: '', //s.verificationScreenButtonLabel, + onPressed: () { + context.goNamed(AppRoutes.onboardingProfile); + }), + ]), ), ), ), diff --git a/lib/src/presentation/features/auth/widgets/verification/verification_input_widget.dart b/lib/src/presentation/features/auth/widgets/verification/verification_input_widget.dart index a37fab9..0139985 100644 --- a/lib/src/presentation/features/auth/widgets/verification/verification_input_widget.dart +++ b/lib/src/presentation/features/auth/widgets/verification/verification_input_widget.dart @@ -3,10 +3,10 @@ import 'package:flutter/services.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; class VerificationInputWidget extends StatelessWidget { + final TextEditingController? controller; + final FocusNode? focusNode; - const VerificationInputWidget({ - super.key - }); + const VerificationInputWidget({this.controller, this.focusNode, super.key}); @override Widget build(BuildContext context) { @@ -16,6 +16,8 @@ class VerificationInputWidget extends StatelessWidget { minHeight: 76.0, ), child: TextField( + controller: controller, + focusNode: focusNode, keyboardType: TextInputType.number, inputFormatters: [ LengthLimitingTextInputFormatter(1), @@ -25,8 +27,7 @@ class VerificationInputWidget extends StatelessWidget { style: Theme.of(context).textTheme.titleLarge, decoration: InputDecoration( fillColor: Colors.transparent, - contentPadding: const EdgeInsets.symmetric( - vertical: 24.0, horizontal: 12.0), + contentPadding: const EdgeInsets.symmetric(vertical: 24.0, horizontal: 12.0), focusedBorder: OutlineInputBorder( borderSide: const BorderSide( color: AppColors.brightBlue, diff --git a/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart b/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart new file mode 100644 index 0000000..9dc779e --- /dev/null +++ b/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; +import 'package:golub/src/presentation/ui_kit/widgets/input/profile_upload_image.dart'; +import 'package:golub/src/presentation/ui_kit/widgets/input/text_field_widget.dart'; + +class OnboardingProfileScreen extends StatelessWidget { + const OnboardingProfileScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text( + '', //s.onboardingProfileScreenTitle, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: AppColors.baseBlack, + ), + ), + centerTitle: true, + backgroundColor: Colors.transparent, + elevation: 0.0, + ), + body: SafeArea( + bottom: false, + child: Container( + padding: const EdgeInsets.only( + left: 16.0, + right: 16.0, + top: 16.0, + ), + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + ProfileUploadImage(), + const SizedBox(height: 32.0), + TextFieldWidget( + hintText: 'Profile Name', + fillColor: AppColors.baseGray2, + ), + ], + ), + ), + )); + } +} diff --git a/lib/src/presentation/navigation/app_bottom_navigation_bar_widget.dart b/lib/src/presentation/navigation/app_bottom_navigation_bar_widget.dart index 01cfa57..4353364 100644 --- a/lib/src/presentation/navigation/app_bottom_navigation_bar_widget.dart +++ b/lib/src/presentation/navigation/app_bottom_navigation_bar_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:golub/generated/l10n.dart'; +import 'package:golub/i18n/strings.g.dart'; import 'package:golub/src/presentation/navigation/app_bottom_navigation_bar_item_widget.dart'; import 'package:golub/src/presentation/ui_kit/ui.dart'; @@ -7,22 +7,15 @@ class AppBottomNavigationBarWidget extends StatefulWidget { final int index; final Function(int) onTap; - const AppBottomNavigationBarWidget({ - required this.index, - required this.onTap, - super.key - }); + const AppBottomNavigationBarWidget({required this.index, required this.onTap, super.key}); @override - State createState() => - _AppBottomNavigationBarWidgetState(); + State createState() => _AppBottomNavigationBarWidgetState(); } -class _AppBottomNavigationBarWidgetState extends - State { - +class _AppBottomNavigationBarWidgetState extends State { final List<({String icon, String label})> _bottomNavigationBarItem = [ - (icon: AppAssets.contactIcon, label: S.current.labelContacts), + (icon: AppAssets.contactIcon, label: t.screens.auth.title), (icon: AppAssets.chatIcon, label: 'Chats'), (icon: AppAssets.profileIcon, label: 'Profile'), ]; @@ -40,14 +33,18 @@ class _AppBottomNavigationBarWidgetState extends mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.max, - children: _bottomNavigationBarItem.asMap().entries.map((e) => - AppBottomNavigationBarItemWidget( - image: e.value.icon, - label: e.value.label, - isActive: e.key == widget.index, - onTap: () => widget.onTap(e.key), - ), - ).toList(), + children: _bottomNavigationBarItem + .asMap() + .entries + .map( + (e) => AppBottomNavigationBarItemWidget( + image: e.value.icon, + label: e.value.label, + isActive: e.key == widget.index, + onTap: () => widget.onTap(e.key), + ), + ) + .toList(), ), ); } diff --git a/lib/src/presentation/navigation/app_router.dart b/lib/src/presentation/navigation/app_router.dart index 7f8697f..68d1815 100644 --- a/lib/src/presentation/navigation/app_router.dart +++ b/lib/src/presentation/navigation/app_router.dart @@ -4,6 +4,7 @@ import 'package:golub/src/presentation/features/auth/screens/auth_screen.dart'; import 'package:golub/src/presentation/features/auth/screens/verification_screen.dart'; import 'package:golub/src/presentation/features/chats/screens/chats_screen.dart'; import 'package:golub/src/presentation/features/contacts/screens/contacts_screen.dart'; +import 'package:golub/src/presentation/features/onboarding/onboarding_profile_screen.dart'; import 'package:golub/src/presentation/features/profile/screens/profile_screen.dart'; import 'package:golub/src/presentation/features/splash/screens/splash_screen.dart'; import 'package:golub/src/presentation/navigation/app_navigation_shell.dart'; @@ -41,6 +42,13 @@ final routerConfig = GoRouter( return const VerificationScreen(); }, ), + GoRoute( + name: AppRoutes.onboardingProfile, + path: AppRoutes.getPath(AppRoutes.onboardingProfile), + builder: (BuildContext context, GoRouterState state) { + return const OnboardingProfileScreen(); + }, + ), StatefulShellRoute.indexedStack( builder: (BuildContext context, GoRouterState state, StatefulNavigationShell navigationShell) { diff --git a/lib/src/presentation/navigation/app_routes.dart b/lib/src/presentation/navigation/app_routes.dart index e0563d1..02dceb4 100644 --- a/lib/src/presentation/navigation/app_routes.dart +++ b/lib/src/presentation/navigation/app_routes.dart @@ -7,6 +7,7 @@ class AppRoutes { static const String chats = 'chats'; static const String profile = 'profile'; static const String verification = 'verification'; + static const String onboardingProfile = 'onboardingProfile'; static String getPath(String routeName) => '/$routeName'; } diff --git a/lib/src/presentation/ui_kit/theme/app_assets.dart b/lib/src/presentation/ui_kit/theme/app_assets.dart index 17fcf56..e593517 100644 --- a/lib/src/presentation/ui_kit/theme/app_assets.dart +++ b/lib/src/presentation/ui_kit/theme/app_assets.dart @@ -8,6 +8,8 @@ class AppAssets { static const String contactIcon = '$_icons/contacts.svg'; static const String chatIcon = '$_icons/chat.svg'; static const String profileIcon = '$_icons/profile.svg'; + static const String userProfileIcon = '$_icons/user_profile.svg'; + static const String cameraIcon = '$_icons/camera.svg'; static const String arrowBackIcon = '$_icons/arrow_back.svg'; diff --git a/lib/src/presentation/ui_kit/theme/themes/light_theme.dart b/lib/src/presentation/ui_kit/theme/themes/light_theme.dart index fb07352..81b4116 100644 --- a/lib/src/presentation/ui_kit/theme/themes/light_theme.dart +++ b/lib/src/presentation/ui_kit/theme/themes/light_theme.dart @@ -25,9 +25,18 @@ final lightTheme = ThemeData( ), ), inputDecorationTheme: InputDecorationTheme( - hintStyle: AppTextStyles.bodyMedium, + labelStyle: AppTextStyles.bodyMedium.copyWith( + color: AppColors.baseGray5, + ), + floatingLabelStyle: AppTextStyles.bodySmall.copyWith( + color: AppColors.baseGray5, + ), + floatingLabelAlignment: FloatingLabelAlignment.start, + helperStyle: AppTextStyles.displaySmall.copyWith( + color: AppColors.brightRed, + ), fillColor: AppColors.baseWhite, - contentPadding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), + contentPadding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0), filled: true, focusedBorder: OutlineInputBorder( borderSide: const BorderSide(color: AppColors.baseWhite), diff --git a/lib/src/presentation/ui_kit/widgets/input/profile_upload_image.dart b/lib/src/presentation/ui_kit/widgets/input/profile_upload_image.dart new file mode 100644 index 0000000..774b7a9 --- /dev/null +++ b/lib/src/presentation/ui_kit/widgets/input/profile_upload_image.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_assets.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; + +class ProfileUploadImage extends StatelessWidget { + final EdgeInsets padding; + + const ProfileUploadImage({ + this.padding = const EdgeInsets.all(16.0), + super.key + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: padding, + child: Stack( + alignment: Alignment.center, + clipBehavior: Clip.none, + children: [ + Container( + width: 144.0, + height: 144.0, + decoration: BoxDecoration( + color: AppColors.baseGray2, + borderRadius: BorderRadius.circular(16.0), + ), + child: Center( + child: SvgPicture.asset( + AppAssets.userProfileIcon, + fit: BoxFit.cover, + width: 76.0, + height: 84.0, + ), + ), + ), + Positioned( + bottom: -16.0, + child: Container( + width: 32.0, + height: 32.0, + decoration: BoxDecoration( + color: AppColors.baseWhite, + borderRadius: BorderRadius.circular(8.0), + ), + child: Center( + child: SvgPicture.asset( + AppAssets.cameraIcon, + fit: BoxFit.cover, + width: 16.0, + height: 16.0, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart b/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart index c8861fe..3057b00 100644 --- a/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart +++ b/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart @@ -2,18 +2,20 @@ import 'package:flutter/material.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; class TextFieldWidget extends StatelessWidget { - final String? hintText; + final String hintText; final String? error; final TextEditingController? textEditingController; final FocusNode? focusNode; final VoidCallback? onEditingComplete; + final Color? fillColor; const TextFieldWidget({ this.textEditingController, this.focusNode, this.onEditingComplete, - this.hintText, + this.hintText = '', this.error, + this.fillColor, super.key }); @@ -22,8 +24,14 @@ class TextFieldWidget extends StatelessWidget { return Container( constraints: const BoxConstraints( minWidth: double.infinity, - minHeight: 52.0, + minHeight: 64.0, ), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(16.0)), + color: fillColor ?? AppColors.baseWhite, + //color: Colors.yellow, + ), + alignment: Alignment.center, child: TextField( controller: textEditingController, focusNode: focusNode, @@ -33,11 +41,9 @@ class TextFieldWidget extends StatelessWidget { AppColors.baseBlack : AppColors.baseWhite, ), decoration: InputDecoration( - hintText: hintText, + labelText: hintText, helperText: error, - helperStyle: Theme.of(context).textTheme.displaySmall?.copyWith( - color: AppColors.brightRed, - ), + fillColor: fillColor, ), ), ); diff --git a/pubspec.lock b/pubspec.lock index 4d227e9..1cc9762 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -157,10 +157,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" convert: dependency: transitive description: @@ -177,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + csv: + dependency: transitive + description: + name: csv + sha256: "63ed2871dd6471193dffc52c0e6c76fb86269c00244d244297abbb355c84a86e" + url: "https://pub.dev" + source: hosted + version: "5.1.1" cupertino_icons: dependency: "direct main" description: @@ -405,6 +413,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json2yaml: + dependency: transitive + description: + name: json2yaml + sha256: da94630fbc56079426fdd167ae58373286f603371075b69bf46d848d63ba3e51 + url: "https://pub.dev" + source: hosted + version: "3.0.1" json_annotation: dependency: transitive description: @@ -449,10 +465,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" mime: dependency: transitive description: @@ -578,6 +594,30 @@ packages: description: flutter source: sdk version: "0.0.99" + slang: + dependency: "direct main" + description: + name: slang + sha256: "77fd99f7b0da15e671ef0289b24a0a63e74f693c58a0ca54111388e4c0ddb1dd" + url: "https://pub.dev" + source: hosted + version: "3.28.0" + slang_build_runner: + dependency: "direct dev" + description: + name: slang_build_runner + sha256: "387a3d569da4490b1fffbf31f203021fbfd34f15228d83e14a0f40bc940966fa" + url: "https://pub.dev" + source: hosted + version: "3.28.0" + slang_flutter: + dependency: "direct main" + description: + name: slang_flutter + sha256: "57817bb15553bb5df37aed3bac497286bdd8c2eab6763f4de6815efe2c0becee" + url: "https://pub.dev" + source: hosted + version: "3.28.0" source_gen: dependency: transitive description: @@ -598,18 +638,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: @@ -638,10 +678,10 @@ packages: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" timing: dependency: transitive description: @@ -766,10 +806,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" web_socket_channel: dependency: transitive description: @@ -795,5 +835,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.3 <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index 780a01a..8e6d55f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -25,6 +25,8 @@ dependencies: flutter_dotenv: 5.1.0 injectable: 2.3.2 get_it: 7.6.4 + slang: 3.28.0 + slang_flutter: 3.28.0 dev_dependencies: flutter_test: @@ -33,9 +35,7 @@ dev_dependencies: intl_utils: 2.8.5 build_runner: 2.4.6 injectable_generator: 2.4.1 - -flutter_intl: - enabled: true + slang_build_runner: 3.28.0 flutter: uses-material-design: true From bcf30c429136df8d2e7bb0fc12178e58b7494141 Mon Sep 17 00:00:00 2001 From: denid88 Date: Thu, 25 Jan 2024 09:57:20 +0200 Subject: [PATCH 08/37] [Localizations] - changing localization fields --- lib/i18n/localization_en.i18n.json | 11 +++++++---- lib/i18n/localization_uk.i18n.json | 11 +++++++---- lib/src/domain/blocs/auth/auth_bloc.dart | 2 +- .../features/auth/screens/auth_screen.dart | 4 ++-- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/i18n/localization_en.i18n.json b/lib/i18n/localization_en.i18n.json index 0454ea0..f0d1049 100644 --- a/lib/i18n/localization_en.i18n.json +++ b/lib/i18n/localization_en.i18n.json @@ -1,18 +1,22 @@ { "@@locale": "en", "common": { + "buttons": { + "next": "Next" + }, + "placeholders": { + "email": "Email" + } }, "errors": { "validation": { - "email": "Please enter a valid email." + "invalidEmail": "Please enter a valid email." } }, "screens": { "auth": { "title": "Authentication", "description": "Please enter your credentials to login.", - "buttonLabel": "Next", - "emailPlaceholder": "Email", "statementIAgree": "I agree to ", "statementPrivacyPolicy": "Privacy Policy", "statementTermsAndConditions": "Terms & Conditions", @@ -21,7 +25,6 @@ "verification": { "title": "Check your Email", "description": "Paste dynamically generated code", - "buttonLabel": "Next", "noCodeQuestion": "Didn't get anything?", "noCodeButtonLabel": "Resend code" }, diff --git a/lib/i18n/localization_uk.i18n.json b/lib/i18n/localization_uk.i18n.json index 2fa6178..2d0049c 100644 --- a/lib/i18n/localization_uk.i18n.json +++ b/lib/i18n/localization_uk.i18n.json @@ -1,18 +1,22 @@ { "@@locale": "uk", "common": { + "buttons": { + "next": "Далі" + }, + "placeholders": { + "email": "Емейл" + } }, "errors": { "validation": { - "email": "Введіть коректний email" + "invalidEmail": "Введіть коректний email" } }, "screens": { "auth": { "title": "Авторизація", "description": "Введіть email для авторизації", - "buttonLabel": "Далі", - "emailPlaceholder": "Введіть email", "statementIAgree": "Я погоджуюсь з ", "statementPrivacyPolicy": "Політикою конфіденційності", "statementTermsAndConditions": "Умовами використання", @@ -21,7 +25,6 @@ "verification": { "title": "Перевірте електронну пошту", "description": "Ми надіслали вам код підтвердження на електронну пошту", - "buttonLabel": "Далі", "noCodeQuestion": "Не отримали код?", "noCodeButtonLabel": "Відправити ще раз" }, diff --git a/lib/src/domain/blocs/auth/auth_bloc.dart b/lib/src/domain/blocs/auth/auth_bloc.dart index a7cdb13..2acd06c 100644 --- a/lib/src/domain/blocs/auth/auth_bloc.dart +++ b/lib/src/domain/blocs/auth/auth_bloc.dart @@ -32,7 +32,7 @@ class AuthBloc extends Bloc { Future _authenticateByEmailEvent( AuthenticateByEmailEvent event, Emitter emit) async { if (!state.email.isValidEmail) { - emit(state.copyWith(validationError: t.errors.validation.email)); + emit(state.copyWith(validationError: t.errors.validation.invalidEmail)); return; } diff --git a/lib/src/presentation/features/auth/screens/auth_screen.dart b/lib/src/presentation/features/auth/screens/auth_screen.dart index c8a21b7..ddd806e 100644 --- a/lib/src/presentation/features/auth/screens/auth_screen.dart +++ b/lib/src/presentation/features/auth/screens/auth_screen.dart @@ -90,7 +90,7 @@ class _AuthScreenState extends State { return TextFieldWidget( textEditingController: _emailController, focusNode: _emailFocusNode, - hintText: t.screens.auth.emailPlaceholder, + hintText: t.common.placeholders.email, error: state.validationError, ); }), @@ -176,7 +176,7 @@ class _AuthScreenState extends State { isDisabled: _authBloc.isButtonDisabled, isLoading: state.status == AuthStatus.loading, onPressed: () => _authBloc.add(AuthenticateByEmailEvent()), - buttonLabel: t.screens.auth.buttonLabel, + buttonLabel: t.common.buttons.next, ); }, ), From 0aad6c09427ab695065ee9be50ef9b42e506124f Mon Sep 17 00:00:00 2001 From: denid88 Date: Thu, 25 Jan 2024 11:07:13 +0200 Subject: [PATCH 09/37] [Localizations] - changed files --- lib/i18n/README.md | 22 +++++++++++++++++++ ...localization_en.i18n.json => en.i18n.json} | 2 +- ...localization_uk.i18n.json => uk.i18n.json} | 0 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 lib/i18n/README.md rename lib/i18n/{localization_en.i18n.json => en.i18n.json} (93%) rename lib/i18n/{localization_uk.i18n.json => uk.i18n.json} (100%) diff --git a/lib/i18n/README.md b/lib/i18n/README.md new file mode 100644 index 0000000..6d13f9f --- /dev/null +++ b/lib/i18n/README.md @@ -0,0 +1,22 @@ +# README: Localization File Guide + +## Overview + +This README provides a guide on how to effectively use and populate a localization file using +the provided JSON model. The JSON structure encompasses various sections, including common elements, +error messages, and screen-specific content for an authentication and onboarding system. + +## Categories and Guidelines + +### Common + +- **buttons** +- **placeholders** + +### Errors + +- **validation** + +### Screens + +#### Particular Screen \ No newline at end of file diff --git a/lib/i18n/localization_en.i18n.json b/lib/i18n/en.i18n.json similarity index 93% rename from lib/i18n/localization_en.i18n.json rename to lib/i18n/en.i18n.json index f0d1049..0c78f47 100644 --- a/lib/i18n/localization_en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -10,7 +10,7 @@ }, "errors": { "validation": { - "invalidEmail": "Please enter a valid email." + "invalidEmail": "Please enter a valid email" } }, "screens": { diff --git a/lib/i18n/localization_uk.i18n.json b/lib/i18n/uk.i18n.json similarity index 100% rename from lib/i18n/localization_uk.i18n.json rename to lib/i18n/uk.i18n.json From d8777c563fcb531720a636f15a6835fd0c89e2e3 Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 09:39:26 +0200 Subject: [PATCH 10/37] [*][Codemagic] - yaml config --- android/app/build.gradle | 26 +++++++++++++-- codemagic.yaml | 33 +++++++++++++++++++ .../{en.i18n.json => locale_en.i18n.json} | 0 .../{uk.i18n.json => locale_uk.i18n.json} | 0 4 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 codemagic.yaml rename lib/i18n/{en.i18n.json => locale_en.i18n.json} (100%) rename lib/i18n/{uk.i18n.json => locale_uk.i18n.json} (100%) diff --git a/android/app/build.gradle b/android/app/build.gradle index f73b2e7..f5af78e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -22,6 +22,12 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + android { namespace "com.golub.golub" compileSdkVersion flutter.compileSdkVersion @@ -51,11 +57,25 @@ android { versionName flutterVersionName } + signingConfigs { + release { + if (System.getenv()["CI"]) { // CI=true is exported by Codemagic + storeFile file(System.getenv()["CM_KEYSTORE_PATH"]) + storePassword System.getenv()["CM_KEYSTORE_PASSWORD"] + keyAlias System.getenv()["CM_KEY_ALIAS"] + keyPassword System.getenv()["CM_KEY_PASSWORD"] + } else { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + } + buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug + signingConfig signingConfigs.release } } } diff --git a/codemagic.yaml b/codemagic.yaml new file mode 100644 index 0000000..daed586 --- /dev/null +++ b/codemagic.yaml @@ -0,0 +1,33 @@ +workflows: + android-dev: + name: Android Development + instance_type: mac_mini_m1 + max_build_duration: 90 + environment: + flutter: stable + android_signing: + - keystore_reference: Golub + triggering: + events: + - tag + tag_patterns: + - '*' + scripts: + - name: Get Flutter dependencies + script: flutter packages pub get + - name: Run tests + script: flutter test + - name: Flutter analyze + script: flutter analyze + - name: Build APK + script: flutter build apk --release + artifacts: + - build/**/outputs/apk/**/*.apk + publishing: + email: + recipients: + - $DEVELOPER_EMAIL + subject: "Golub Android Build" + body: "Build has been completed successfully" + attachments: + - build/**/outputs/apk/**/*.apk diff --git a/lib/i18n/en.i18n.json b/lib/i18n/locale_en.i18n.json similarity index 100% rename from lib/i18n/en.i18n.json rename to lib/i18n/locale_en.i18n.json diff --git a/lib/i18n/uk.i18n.json b/lib/i18n/locale_uk.i18n.json similarity index 100% rename from lib/i18n/uk.i18n.json rename to lib/i18n/locale_uk.i18n.json From b4bd4648a1588737a1f643487e6b8355e8af3060 Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 09:42:22 +0200 Subject: [PATCH 11/37] [*][Codemagic] - fixing problem --- codemagic.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codemagic.yaml b/codemagic.yaml index daed586..e1cc28a 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -6,7 +6,7 @@ workflows: environment: flutter: stable android_signing: - - keystore_reference: Golub + - keystore_reference: golub_keystore triggering: events: - tag From 2b1d2ec2ffae1826bfec17e5275a0bd0ca1ce984 Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 09:44:33 +0200 Subject: [PATCH 12/37] [*][Codemagic] - changing android signing --- codemagic.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codemagic.yaml b/codemagic.yaml index e1cc28a..657eacf 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -6,7 +6,7 @@ workflows: environment: flutter: stable android_signing: - - keystore_reference: golub_keystore + - golub_keystore triggering: events: - tag From 7d7b1c502b939ebad2d5f9fb95647bc6812ea0d6 Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 09:46:19 +0200 Subject: [PATCH 13/37] [*][Codeamgic] - change trigger pattern --- codemagic.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codemagic.yaml b/codemagic.yaml index 657eacf..4c43e53 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -11,7 +11,8 @@ workflows: events: - tag tag_patterns: - - '*' + - pattern: '*' + include: true scripts: - name: Get Flutter dependencies script: flutter packages pub get From 93261fa15e023f6df3339ce094707243acf089cf Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 09:57:38 +0200 Subject: [PATCH 14/37] [*][Codeamgic] - change notify pattern --- codemagic.yaml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/codemagic.yaml b/codemagic.yaml index 4c43e53..9e49e2f 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -25,10 +25,9 @@ workflows: artifacts: - build/**/outputs/apk/**/*.apk publishing: - email: - recipients: - - $DEVELOPER_EMAIL - subject: "Golub Android Build" - body: "Build has been completed successfully" - attachments: - - build/**/outputs/apk/**/*.apk + email: + recipients: + - $DEVELOPER_EMAIL + notify: + success: true + From e5034571a14ff0405cb61c77acdcc1175ec59d25 Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 09:59:22 +0200 Subject: [PATCH 15/37] [*][Codeamgic] - change notify pattern to real email --- codemagic.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codemagic.yaml b/codemagic.yaml index 9e49e2f..b4ccc22 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -27,7 +27,7 @@ workflows: publishing: email: recipients: - - $DEVELOPER_EMAIL + - "denisdubov88@gmail.com" notify: success: true From ea3499ca7799fbf4e862db70316a11b8d5d40bb3 Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 10:04:17 +0200 Subject: [PATCH 16/37] [*][Codeamgic] - remove test sections --- codemagic.yaml | 2 -- test/widget_test.dart | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/codemagic.yaml b/codemagic.yaml index b4ccc22..6c1f2af 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -16,8 +16,6 @@ workflows: scripts: - name: Get Flutter dependencies script: flutter packages pub get - - name: Run tests - script: flutter test - name: Flutter analyze script: flutter analyze - name: Build APK diff --git a/test/widget_test.dart b/test/widget_test.dart index 2a2b819..4647042 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -5,4 +5,6 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. -void main() {} +void main() { + +} From 7c45d9c6f82f4dd477b7f3b6a5695760238fbe6f Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 10:08:00 +0200 Subject: [PATCH 17/37] [*][Codeamgic] - remove analyze section --- codemagic.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/codemagic.yaml b/codemagic.yaml index 6c1f2af..59b1224 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -16,8 +16,6 @@ workflows: scripts: - name: Get Flutter dependencies script: flutter packages pub get - - name: Flutter analyze - script: flutter analyze - name: Build APK script: flutter build apk --release artifacts: From cba1aa141f055ac6eb5f0c56a15a8f168507f1f0 Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 10:14:38 +0200 Subject: [PATCH 18/37] [*][Codemagic] - fixes localization --- lib/i18n/strings.g.dart | 432 ++++++++++++++++++ .../onboarding/onboarding_profile_screen.dart | 65 +-- 2 files changed, 465 insertions(+), 32 deletions(-) create mode 100644 lib/i18n/strings.g.dart diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart new file mode 100644 index 0000000..f12c603 --- /dev/null +++ b/lib/i18n/strings.g.dart @@ -0,0 +1,432 @@ +/// Generated file. Do not edit. +/// +/// Original: lib/i18n +/// To regenerate, run: `dart run slang` +/// +/// Locales: 2 +/// Strings: 28 (14 per locale) +/// +/// Built on 2024-01-25 at 09:17 UTC + +// coverage:ignore-file +// ignore_for_file: type=lint + +import 'package:flutter/widgets.dart'; +import 'package:slang/builder/model/node.dart'; +import 'package:slang_flutter/slang_flutter.dart'; +export 'package:slang_flutter/slang_flutter.dart'; + +const AppLocale _baseLocale = AppLocale.en; + +/// Supported locales, see extension methods below. +/// +/// Usage: +/// - LocaleSettings.setLocale(AppLocale.en) // set locale +/// - Locale locale = AppLocale.en.flutterLocale // get flutter locale from enum +/// - if (LocaleSettings.currentLocale == AppLocale.en) // locale check +enum AppLocale with BaseAppLocale { + en(languageCode: 'en', build: Translations.build), + uk(languageCode: 'uk', build: _StringsUk.build); + + const AppLocale({required this.languageCode, this.scriptCode, this.countryCode, required this.build}); // ignore: unused_element + + @override final String languageCode; + @override final String? scriptCode; + @override final String? countryCode; + @override final TranslationBuilder build; + + /// Gets current instance managed by [LocaleSettings]. + Translations get translations => LocaleSettings.instance.translationMap[this]!; +} + +/// Method A: Simple +/// +/// No rebuild after locale change. +/// Translation happens during initialization of the widget (call of t). +/// Configurable via 'translate_var'. +/// +/// Usage: +/// String a = t.someKey.anotherKey; +/// String b = t['someKey.anotherKey']; // Only for edge cases! +Translations get t => LocaleSettings.instance.currentTranslations; + +/// Method B: Advanced +/// +/// All widgets using this method will trigger a rebuild when locale changes. +/// Use this if you have e.g. a settings page where the user can select the locale during runtime. +/// +/// Step 1: +/// wrap your App with +/// TranslationProvider( +/// child: MyApp() +/// ); +/// +/// Step 2: +/// final t = Translations.of(context); // Get t variable. +/// String a = t.someKey.anotherKey; // Use t variable. +/// String b = t['someKey.anotherKey']; // Only for edge cases! +class TranslationProvider extends BaseTranslationProvider { + TranslationProvider({required super.child}) : super(settings: LocaleSettings.instance); + + static InheritedLocaleData of(BuildContext context) => InheritedLocaleData.of(context); +} + +/// Method B shorthand via [BuildContext] extension method. +/// Configurable via 'translate_var'. +/// +/// Usage (e.g. in a widget's build method): +/// context.t.someKey.anotherKey +extension BuildContextTranslationsExtension on BuildContext { + Translations get t => TranslationProvider.of(this).translations; +} + +/// Manages all translation instances and the current locale +class LocaleSettings extends BaseFlutterLocaleSettings { + LocaleSettings._() : super(utils: AppLocaleUtils.instance); + + static final instance = LocaleSettings._(); + + // static aliases (checkout base methods for documentation) + static AppLocale get currentLocale => instance.currentLocale; + static Stream getLocaleStream() => instance.getLocaleStream(); + static AppLocale setLocale(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocale(locale, listenToDeviceLocale: listenToDeviceLocale); + static AppLocale setLocaleRaw(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRaw(rawLocale, listenToDeviceLocale: listenToDeviceLocale); + static AppLocale useDeviceLocale() => instance.useDeviceLocale(); + @Deprecated('Use [AppLocaleUtils.supportedLocales]') static List get supportedLocales => instance.supportedLocales; + @Deprecated('Use [AppLocaleUtils.supportedLocalesRaw]') static List get supportedLocalesRaw => instance.supportedLocalesRaw; + static void setPluralResolver({String? language, AppLocale? locale, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) => instance.setPluralResolver( + language: language, + locale: locale, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ); +} + +/// Provides utility functions without any side effects. +class AppLocaleUtils extends BaseAppLocaleUtils { + AppLocaleUtils._() : super(baseLocale: _baseLocale, locales: AppLocale.values); + + static final instance = AppLocaleUtils._(); + + // static aliases (checkout base methods for documentation) + static AppLocale parse(String rawLocale) => instance.parse(rawLocale); + static AppLocale parseLocaleParts({required String languageCode, String? scriptCode, String? countryCode}) => instance.parseLocaleParts(languageCode: languageCode, scriptCode: scriptCode, countryCode: countryCode); + static AppLocale findDeviceLocale() => instance.findDeviceLocale(); + static List get supportedLocales => instance.supportedLocales; + static List get supportedLocalesRaw => instance.supportedLocalesRaw; +} + +// translations + +// Path: +class Translations implements BaseTranslations { + /// Returns the current translations of the given [context]. + /// + /// Usage: + /// final t = Translations.of(context); + static Translations of(BuildContext context) => InheritedLocaleData.of(context).translations; + + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + Translations.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) + : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), + $meta = TranslationMetadata( + locale: AppLocale.en, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } + + /// Metadata for the translations of . + @override final TranslationMetadata $meta; + + /// Access flat map + dynamic operator[](String key) => $meta.getTranslation(key); + + late final Translations _root = this; // ignore: unused_field + + // Translations + late final _StringsCommonEn common = _StringsCommonEn._(_root); + late final _StringsErrorsEn errors = _StringsErrorsEn._(_root); + late final _StringsScreensEn screens = _StringsScreensEn._(_root); +} + +// Path: common +class _StringsCommonEn { + _StringsCommonEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsCommonButtonsEn buttons = _StringsCommonButtonsEn._(_root); + late final _StringsCommonPlaceholdersEn placeholders = _StringsCommonPlaceholdersEn._(_root); +} + +// Path: errors +class _StringsErrorsEn { + _StringsErrorsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsErrorsValidationEn validation = _StringsErrorsValidationEn._(_root); +} + +// Path: screens +class _StringsScreensEn { + _StringsScreensEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + late final _StringsScreensAuthEn auth = _StringsScreensAuthEn._(_root); + late final _StringsScreensVerificationEn verification = _StringsScreensVerificationEn._(_root); + late final _StringsScreensOnboardingProfileEn onboardingProfile = _StringsScreensOnboardingProfileEn._(_root); +} + +// Path: common.buttons +class _StringsCommonButtonsEn { + _StringsCommonButtonsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get next => 'Next'; +} + +// Path: common.placeholders +class _StringsCommonPlaceholdersEn { + _StringsCommonPlaceholdersEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get email => 'Email'; +} + +// Path: errors.validation +class _StringsErrorsValidationEn { + _StringsErrorsValidationEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get invalidEmail => 'Please enter a valid email'; +} + +// Path: screens.auth +class _StringsScreensAuthEn { + _StringsScreensAuthEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => 'Authentication'; + String get description => 'Please enter your credentials to login.'; + String get statementIAgree => 'I agree to '; + String get statementPrivacyPolicy => 'Privacy Policy'; + String get statementTermsAndConditions => 'Terms & Conditions'; + String get statementAnd => ' and '; +} + +// Path: screens.verification +class _StringsScreensVerificationEn { + _StringsScreensVerificationEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => 'Check your Email'; + String get description => 'Paste dynamically generated code'; + String get noCodeQuestion => 'Didn\'t get anything?'; + String get noCodeButtonLabel => 'Resend code'; +} + +// Path: screens.onboardingProfile +class _StringsScreensOnboardingProfileEn { + _StringsScreensOnboardingProfileEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get title => 'Profile'; +} + +// Path: +class _StringsUk implements Translations { + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + _StringsUk.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) + : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), + $meta = TranslationMetadata( + locale: AppLocale.uk, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } + + /// Metadata for the translations of . + @override final TranslationMetadata $meta; + + /// Access flat map + @override dynamic operator[](String key) => $meta.getTranslation(key); + + @override late final _StringsUk _root = this; // ignore: unused_field + + // Translations + @override late final _StringsCommonUk common = _StringsCommonUk._(_root); + @override late final _StringsErrorsUk errors = _StringsErrorsUk._(_root); + @override late final _StringsScreensUk screens = _StringsScreensUk._(_root); +} + +// Path: common +class _StringsCommonUk implements _StringsCommonEn { + _StringsCommonUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override late final _StringsCommonButtonsUk buttons = _StringsCommonButtonsUk._(_root); + @override late final _StringsCommonPlaceholdersUk placeholders = _StringsCommonPlaceholdersUk._(_root); +} + +// Path: errors +class _StringsErrorsUk implements _StringsErrorsEn { + _StringsErrorsUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override late final _StringsErrorsValidationUk validation = _StringsErrorsValidationUk._(_root); +} + +// Path: screens +class _StringsScreensUk implements _StringsScreensEn { + _StringsScreensUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override late final _StringsScreensAuthUk auth = _StringsScreensAuthUk._(_root); + @override late final _StringsScreensVerificationUk verification = _StringsScreensVerificationUk._(_root); + @override late final _StringsScreensOnboardingProfileUk onboardingProfile = _StringsScreensOnboardingProfileUk._(_root); +} + +// Path: common.buttons +class _StringsCommonButtonsUk implements _StringsCommonButtonsEn { + _StringsCommonButtonsUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override String get next => 'Далі'; +} + +// Path: common.placeholders +class _StringsCommonPlaceholdersUk implements _StringsCommonPlaceholdersEn { + _StringsCommonPlaceholdersUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override String get email => 'Емейл'; +} + +// Path: errors.validation +class _StringsErrorsValidationUk implements _StringsErrorsValidationEn { + _StringsErrorsValidationUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override String get invalidEmail => 'Введіть коректний email'; +} + +// Path: screens.auth +class _StringsScreensAuthUk implements _StringsScreensAuthEn { + _StringsScreensAuthUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override String get title => 'Авторизація'; + @override String get description => 'Введіть email для авторизації'; + @override String get statementIAgree => 'Я погоджуюсь з '; + @override String get statementPrivacyPolicy => 'Політикою конфіденційності'; + @override String get statementTermsAndConditions => 'Умовами використання'; + @override String get statementAnd => ' та '; +} + +// Path: screens.verification +class _StringsScreensVerificationUk implements _StringsScreensVerificationEn { + _StringsScreensVerificationUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override String get title => 'Перевірте електронну пошту'; + @override String get description => 'Ми надіслали вам код підтвердження на електронну пошту'; + @override String get noCodeQuestion => 'Не отримали код?'; + @override String get noCodeButtonLabel => 'Відправити ще раз'; +} + +// Path: screens.onboardingProfile +class _StringsScreensOnboardingProfileUk implements _StringsScreensOnboardingProfileEn { + _StringsScreensOnboardingProfileUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override String get title => 'Профіль'; +} + +/// Flat map(s) containing all translations. +/// Only for edge cases! For simple maps, use the map function of this library. + +extension on Translations { + dynamic _flatMapFunction(String path) { + switch (path) { + case 'common.buttons.next': return 'Next'; + case 'common.placeholders.email': return 'Email'; + case 'errors.validation.invalidEmail': return 'Please enter a valid email'; + case 'screens.auth.title': return 'Authentication'; + case 'screens.auth.description': return 'Please enter your credentials to login.'; + case 'screens.auth.statementIAgree': return 'I agree to '; + case 'screens.auth.statementPrivacyPolicy': return 'Privacy Policy'; + case 'screens.auth.statementTermsAndConditions': return 'Terms & Conditions'; + case 'screens.auth.statementAnd': return ' and '; + case 'screens.verification.title': return 'Check your Email'; + case 'screens.verification.description': return 'Paste dynamically generated code'; + case 'screens.verification.noCodeQuestion': return 'Didn\'t get anything?'; + case 'screens.verification.noCodeButtonLabel': return 'Resend code'; + case 'screens.onboardingProfile.title': return 'Profile'; + default: return null; + } + } +} + +extension on _StringsUk { + dynamic _flatMapFunction(String path) { + switch (path) { + case 'common.buttons.next': return 'Далі'; + case 'common.placeholders.email': return 'Емейл'; + case 'errors.validation.invalidEmail': return 'Введіть коректний email'; + case 'screens.auth.title': return 'Авторизація'; + case 'screens.auth.description': return 'Введіть email для авторизації'; + case 'screens.auth.statementIAgree': return 'Я погоджуюсь з '; + case 'screens.auth.statementPrivacyPolicy': return 'Політикою конфіденційності'; + case 'screens.auth.statementTermsAndConditions': return 'Умовами використання'; + case 'screens.auth.statementAnd': return ' та '; + case 'screens.verification.title': return 'Перевірте електронну пошту'; + case 'screens.verification.description': return 'Ми надіслали вам код підтвердження на електронну пошту'; + case 'screens.verification.noCodeQuestion': return 'Не отримали код?'; + case 'screens.verification.noCodeButtonLabel': return 'Відправити ще раз'; + case 'screens.onboardingProfile.title': return 'Профіль'; + default: return null; + } + } +} diff --git a/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart b/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart index 9dc779e..2c25ef3 100644 --- a/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart +++ b/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart @@ -9,40 +9,41 @@ class OnboardingProfileScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - automaticallyImplyLeading: false, - title: Text( - '', //s.onboardingProfileScreenTitle, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: AppColors.baseBlack, - ), + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text( + '', //s.onboardingProfileScreenTitle, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: AppColors.baseBlack, ), - centerTitle: true, - backgroundColor: Colors.transparent, - elevation: 0.0, ), - body: SafeArea( - bottom: false, - child: Container( - padding: const EdgeInsets.only( - left: 16.0, - right: 16.0, - top: 16.0, - ), - width: double.infinity, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - children: [ - ProfileUploadImage(), - const SizedBox(height: 32.0), - TextFieldWidget( - hintText: 'Profile Name', - fillColor: AppColors.baseGray2, - ), - ], - ), + centerTitle: true, + backgroundColor: Colors.transparent, + elevation: 0.0, + ), + body: SafeArea( + bottom: false, + child: Container( + padding: const EdgeInsets.only( + left: 16.0, + right: 16.0, + top: 16.0, ), - )); + width: double.infinity, + child: const Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + ProfileUploadImage(), + SizedBox(height: 32.0), + TextFieldWidget( + hintText: 'Profile Name', + fillColor: AppColors.baseGray2, + ), + ], + ), + ), + ), + ); } } From 759d6033d8b7ddcf00305b889ddc4a779c5fb822 Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 10:50:09 +0200 Subject: [PATCH 19/37] [*][Codemagic] - android code signing --- codemagic.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/codemagic.yaml b/codemagic.yaml index 59b1224..9560f66 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -16,6 +16,14 @@ workflows: scripts: - name: Get Flutter dependencies script: flutter packages pub get + - name: Set up key properties + script: / + cat >> $FCI_BUILD_DIR/android/key.properties << EOF + storePassword=$CM_KEYSTORE_PASSWORD + keyPassword=$CM_KEYSTORE_PASSWORD + keyAlias=$CM_KEY_ALIAS + storeFile=$CM_KEYSTORE_PATH + EOF - name: Build APK script: flutter build apk --release artifacts: From d89018be5f13cef0c0deace496840ed4f97341bc Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 11:00:59 +0200 Subject: [PATCH 20/37] [*][Codemagic] - keystore fixing --- codemagic.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codemagic.yaml b/codemagic.yaml index 9560f66..0e6ca60 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -17,7 +17,7 @@ workflows: - name: Get Flutter dependencies script: flutter packages pub get - name: Set up key properties - script: / + script: cat >> $FCI_BUILD_DIR/android/key.properties << EOF storePassword=$CM_KEYSTORE_PASSWORD keyPassword=$CM_KEYSTORE_PASSWORD From a9c72e33911cf3d68a2f565f3a0ab3e1c354a11b Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 11:06:33 +0200 Subject: [PATCH 21/37] [*][Codemagic] - keystore fixing --- codemagic.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codemagic.yaml b/codemagic.yaml index 0e6ca60..8ffd487 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -17,8 +17,8 @@ workflows: - name: Get Flutter dependencies script: flutter packages pub get - name: Set up key properties - script: - cat >> $FCI_BUILD_DIR/android/key.properties << EOF + script: / + cat >> $FCI_BUILD_DIR/android/key.properties < Date: Sun, 28 Jan 2024 11:09:42 +0200 Subject: [PATCH 22/37] [*][Codemagic] - variable fixing --- codemagic.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codemagic.yaml b/codemagic.yaml index 8ffd487..bd2f609 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -18,7 +18,7 @@ workflows: script: flutter packages pub get - name: Set up key properties script: / - cat >> $FCI_BUILD_DIR/android/key.properties <> $CM_BUILD_DIR/android/key.properties < Date: Sun, 28 Jan 2024 11:12:24 +0200 Subject: [PATCH 23/37] [*][Codemagic] - variable fixes --- codemagic.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codemagic.yaml b/codemagic.yaml index bd2f609..199fa8b 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -17,7 +17,7 @@ workflows: - name: Get Flutter dependencies script: flutter packages pub get - name: Set up key properties - script: / + script: cat >> $CM_BUILD_DIR/android/key.properties < Date: Sun, 28 Jan 2024 11:17:55 +0200 Subject: [PATCH 24/37] [*][Codemagic] - remove key.prop files --- codemagic.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/codemagic.yaml b/codemagic.yaml index 199fa8b..59b1224 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -16,14 +16,6 @@ workflows: scripts: - name: Get Flutter dependencies script: flutter packages pub get - - name: Set up key properties - script: - cat >> $CM_BUILD_DIR/android/key.properties < Date: Sun, 28 Jan 2024 17:26:14 +0200 Subject: [PATCH 25/37] [*][TextField] - changes error in widget --- codemagic.yaml | 1 + .../features/auth/screens/auth_screen.dart | 9 ++- .../auth/screens/verification_screen.dart | 42 ++++++---- ...t_widget.dart => verification_input..dart} | 4 +- .../onboarding/onboarding_profile_screen.dart | 4 +- .../ui_kit/widgets/input/gtext_field.dart | 79 +++++++++++++++++++ .../widgets/input/text_field_widget.dart | 51 ------------ 7 files changed, 114 insertions(+), 76 deletions(-) rename lib/src/presentation/features/auth/widgets/verification/{verification_input_widget.dart => verification_input..dart} (92%) create mode 100644 lib/src/presentation/ui_kit/widgets/input/gtext_field.dart delete mode 100644 lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart diff --git a/codemagic.yaml b/codemagic.yaml index 59b1224..3c93220 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -10,6 +10,7 @@ workflows: triggering: events: - tag + - push tag_patterns: - pattern: '*' include: true diff --git a/lib/src/presentation/features/auth/screens/auth_screen.dart b/lib/src/presentation/features/auth/screens/auth_screen.dart index ddd806e..d350af8 100644 --- a/lib/src/presentation/features/auth/screens/auth_screen.dart +++ b/lib/src/presentation/features/auth/screens/auth_screen.dart @@ -9,7 +9,7 @@ import 'package:golub/src/presentation/navigation/app_router.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; import 'package:golub/src/presentation/ui_kit/ui.dart'; -import 'package:golub/src/presentation/ui_kit/widgets/input/text_field_widget.dart'; +import 'package:golub/src/presentation/ui_kit/widgets/input/gtext_field.dart'; import 'package:golub/src/presentation/utils/link_launcher.dart'; class AuthScreen extends StatefulWidget { @@ -23,8 +23,8 @@ class _AuthScreenState extends State { final AuthBloc _authBloc = getIt(); final TextEditingController _emailController = TextEditingController( - //text: 'test@gmail.com', - ); + text: 'test@gmail.com', + ); final FocusNode _emailFocusNode = FocusNode(); final TapGestureRecognizer _termsConditionsTapRecognizer = TapGestureRecognizer(); @@ -35,6 +35,7 @@ class _AuthScreenState extends State { super.initState(); _emailController.addListener(_handleEmailString); _authBloc.add(ChangeEmailEvent(_emailController.text)); + _authBloc.add(ChangePrivacyPolicyStatusEvent(true)); } void _handleEmailString() { @@ -87,7 +88,7 @@ class _AuthScreenState extends State { BlocBuilder( bloc: _authBloc, builder: (BuildContext context, AuthState state) { - return TextFieldWidget( + return GTextField( textEditingController: _emailController, focusNode: _emailFocusNode, hintText: t.common.placeholders.email, diff --git a/lib/src/presentation/features/auth/screens/verification_screen.dart b/lib/src/presentation/features/auth/screens/verification_screen.dart index 2bc1450..b4967b5 100644 --- a/lib/src/presentation/features/auth/screens/verification_screen.dart +++ b/lib/src/presentation/features/auth/screens/verification_screen.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; -import 'package:golub/src/presentation/features/auth/widgets/verification/verification_input_widget.dart'; +import 'package:golub/i18n/strings.g.dart'; +import 'package:golub/src/presentation/features/auth/widgets/verification/verification_input..dart'; import 'package:golub/src/presentation/navigation/app_router.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; @@ -12,6 +13,8 @@ class VerificationScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final t = Translations.of(context); + return Scaffold( body: Container( constraints: const BoxConstraints.expand(), @@ -21,7 +24,11 @@ class VerificationScreen extends StatelessWidget { child: SafeArea( bottom: false, child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.only( + left: 16.0, + right: 16.0, + top: 20.0, + ), child: Column(children: [ Align( alignment: Alignment.topLeft, @@ -44,12 +51,12 @@ class VerificationScreen extends StatelessWidget { ), const SizedBox(height: 32.0), Text( - '', //s.verificationScreenTitle, + t.screens.verification.title, style: Theme.of(context).textTheme.titleLarge, ), const SizedBox(height: 12.0), Text( - '', //s.verificationScreenDescription, + t.screens.verification.description, style: Theme.of(context).textTheme.bodyMedium, ), Container( @@ -59,27 +66,27 @@ class VerificationScreen extends StatelessWidget { children: [ Expanded( flex: 1, - child: VerificationInputWidget(), + child: VerificationInput(), ), SizedBox(width: 8.0), Expanded( flex: 1, - child: VerificationInputWidget(), + child: VerificationInput(), ), SizedBox(width: 8.0), Expanded( flex: 1, - child: VerificationInputWidget(), + child: VerificationInput(), ), SizedBox(width: 8.0), Expanded( flex: 1, - child: VerificationInputWidget(), + child: VerificationInput(), ), SizedBox(width: 8.0), Expanded( flex: 1, - child: VerificationInputWidget(), + child: VerificationInput(), ) ], ), @@ -88,7 +95,7 @@ class VerificationScreen extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - '', //s.verificationScreenNoCodeQuestion, + t.screens.verification.noCodeQuestion, ), const SizedBox(width: 4.0), GestureDetector( @@ -97,20 +104,21 @@ class VerificationScreen extends StatelessWidget { print('resend code'); }, child: Text( - '', //s.verificationScreenNoCodeButtonLabel, + t.screens.verification.noCodeButtonLabel, style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: AppColors.brightOrange, - ), + color: AppColors.brightOrange, + ), ), ), ], ), const SizedBox(height: 36.0), ElevatedButtonWidget( - buttonLabel: '', //s.verificationScreenButtonLabel, - onPressed: () { - context.goNamed(AppRoutes.onboardingProfile); - }), + buttonLabel: t.common.buttons.next, + onPressed: () { + context.goNamed(AppRoutes.onboardingProfile); + } + ), ]), ), ), diff --git a/lib/src/presentation/features/auth/widgets/verification/verification_input_widget.dart b/lib/src/presentation/features/auth/widgets/verification/verification_input..dart similarity index 92% rename from lib/src/presentation/features/auth/widgets/verification/verification_input_widget.dart rename to lib/src/presentation/features/auth/widgets/verification/verification_input..dart index 0139985..66f65fc 100644 --- a/lib/src/presentation/features/auth/widgets/verification/verification_input_widget.dart +++ b/lib/src/presentation/features/auth/widgets/verification/verification_input..dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; -class VerificationInputWidget extends StatelessWidget { +class VerificationInput extends StatelessWidget { final TextEditingController? controller; final FocusNode? focusNode; - const VerificationInputWidget({this.controller, this.focusNode, super.key}); + const VerificationInput({this.controller, this.focusNode, super.key}); @override Widget build(BuildContext context) { diff --git a/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart b/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart index 2c25ef3..8805007 100644 --- a/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart +++ b/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/widgets/input/profile_upload_image.dart'; -import 'package:golub/src/presentation/ui_kit/widgets/input/text_field_widget.dart'; +import 'package:golub/src/presentation/ui_kit/widgets/input/gtext_field.dart'; class OnboardingProfileScreen extends StatelessWidget { const OnboardingProfileScreen({super.key}); @@ -36,7 +36,7 @@ class OnboardingProfileScreen extends StatelessWidget { children: [ ProfileUploadImage(), SizedBox(height: 32.0), - TextFieldWidget( + GTextField( hintText: 'Profile Name', fillColor: AppColors.baseGray2, ), diff --git a/lib/src/presentation/ui_kit/widgets/input/gtext_field.dart b/lib/src/presentation/ui_kit/widgets/input/gtext_field.dart new file mode 100644 index 0000000..f9bf59a --- /dev/null +++ b/lib/src/presentation/ui_kit/widgets/input/gtext_field.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; + +class GTextField extends StatelessWidget { + final String hintText; + final String? error; + final TextEditingController? textEditingController; + final FocusNode? focusNode; + final VoidCallback? onEditingComplete; + final Color? fillColor; + + const GTextField({ + this.textEditingController, + this.focusNode, + this.onEditingComplete, + this.hintText = '', + this.error, + this.fillColor, + super.key + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + constraints: const BoxConstraints( + minWidth: double.infinity, + minHeight: 64.0, + ), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(16.0)), + color: fillColor ?? AppColors.baseWhite, + ), + alignment: Alignment.center, + child: TextField( + controller: textEditingController, + focusNode: focusNode, + onEditingComplete: onEditingComplete, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).brightness == Brightness.light ? + AppColors.baseBlack : AppColors.baseWhite, + ), + decoration: InputDecoration( + labelText: hintText, + fillColor: fillColor, + filled: true, + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: fillColor ?? AppColors.baseWhite), + borderRadius: BorderRadius.circular(16.0), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: fillColor ?? AppColors.baseWhite), + borderRadius: BorderRadius.circular(16.0), + ), + disabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: fillColor ?? AppColors.baseWhite), + borderRadius: BorderRadius.circular(16.0), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 8.0, + left: 12.0, + ), + child: Text( + error ?? '', + style: Theme.of(context).textTheme.displaySmall?.copyWith( + color: error == null ? Colors.transparent : AppColors.brightRed, + ), + ), + ), + ], + ); + } +} diff --git a/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart b/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart deleted file mode 100644 index 3057b00..0000000 --- a/lib/src/presentation/ui_kit/widgets/input/text_field_widget.dart +++ /dev/null @@ -1,51 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; - -class TextFieldWidget extends StatelessWidget { - final String hintText; - final String? error; - final TextEditingController? textEditingController; - final FocusNode? focusNode; - final VoidCallback? onEditingComplete; - final Color? fillColor; - - const TextFieldWidget({ - this.textEditingController, - this.focusNode, - this.onEditingComplete, - this.hintText = '', - this.error, - this.fillColor, - super.key - }); - - @override - Widget build(BuildContext context) { - return Container( - constraints: const BoxConstraints( - minWidth: double.infinity, - minHeight: 64.0, - ), - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(16.0)), - color: fillColor ?? AppColors.baseWhite, - //color: Colors.yellow, - ), - alignment: Alignment.center, - child: TextField( - controller: textEditingController, - focusNode: focusNode, - onEditingComplete: onEditingComplete, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).brightness == Brightness.light ? - AppColors.baseBlack : AppColors.baseWhite, - ), - decoration: InputDecoration( - labelText: hintText, - helperText: error, - fillColor: fillColor, - ), - ), - ); - } -} From 60a6d99c09f2f5a239015bf80714b56bd71b538e Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 17:28:39 +0200 Subject: [PATCH 26/37] [*][Codemagic] - new trigger scenario --- codemagic.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/codemagic.yaml b/codemagic.yaml index 3c93220..4eb3641 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -11,6 +11,10 @@ workflows: events: - tag - push + branch_patterns: + - pattern: '*' + include: true + source: true tag_patterns: - pattern: '*' include: true From 4e5c1f0324948fad6dc87abee1fc7a9fd58fdc51 Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 17:31:05 +0200 Subject: [PATCH 27/37] [*][Codemagic] - new trigger scenario --- codemagic.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codemagic.yaml b/codemagic.yaml index 4eb3641..1f2a707 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -12,7 +12,7 @@ workflows: - tag - push branch_patterns: - - pattern: '*' + - pattern: '{feature}/*' include: true source: true tag_patterns: From 67983ceb1e14681f044b65e43af988063fae5bda Mon Sep 17 00:00:00 2001 From: denid88 Date: Sun, 28 Jan 2024 17:32:17 +0200 Subject: [PATCH 28/37] [*][Codemagic] - new trigger scenario --- codemagic.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/codemagic.yaml b/codemagic.yaml index 1f2a707..aeed5ab 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -9,15 +9,11 @@ workflows: - golub_keystore triggering: events: - - tag - push branch_patterns: - pattern: '{feature}/*' include: true - source: true - tag_patterns: - - pattern: '*' - include: true + source: false scripts: - name: Get Flutter dependencies script: flutter packages pub get From 4a6db4901bb11164f68ab6286c33f8f14bba1e3f Mon Sep 17 00:00:00 2001 From: denid88 Date: Mon, 29 Jan 2024 21:15:33 +0200 Subject: [PATCH 29/37] [Codemagic] - appbundle included --- codemagic.yaml | 5 +++++ lib/src/presentation/ui_kit/theme/themes/light_theme.dart | 1 + 2 files changed, 6 insertions(+) diff --git a/codemagic.yaml b/codemagic.yaml index aeed5ab..f4d0941 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -19,8 +19,13 @@ workflows: script: flutter packages pub get - name: Build APK script: flutter build apk --release + - name: Build App Bundle + script: flutter build appbundle --release artifacts: - build/**/outputs/apk/**/*.apk + - build/**/outputs/**/*.aab + - build/**/outputs/**/mapping.txt + - flutter_drive.log publishing: email: recipients: diff --git a/lib/src/presentation/ui_kit/theme/themes/light_theme.dart b/lib/src/presentation/ui_kit/theme/themes/light_theme.dart index 81b4116..ed280e8 100644 --- a/lib/src/presentation/ui_kit/theme/themes/light_theme.dart +++ b/lib/src/presentation/ui_kit/theme/themes/light_theme.dart @@ -30,6 +30,7 @@ final lightTheme = ThemeData( ), floatingLabelStyle: AppTextStyles.bodySmall.copyWith( color: AppColors.baseGray5, + fontSize: 14.0, ), floatingLabelAlignment: FloatingLabelAlignment.start, helperStyle: AppTextStyles.displaySmall.copyWith( From 41bb4427d40ea31c525bc0a3c0253d4d22feea27 Mon Sep 17 00:00:00 2001 From: denid88 Date: Tue, 30 Jan 2024 21:42:57 +0200 Subject: [PATCH 30/37] [Codemagic] - credentials --- codemagic.yaml | 9 +++++++-- .../presentation/ui_kit/theme/themes/light_theme.dart | 4 +--- .../presentation/ui_kit/widgets/input/gtext_field.dart | 3 ++- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/codemagic.yaml b/codemagic.yaml index f4d0941..4650c19 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -4,6 +4,8 @@ workflows: instance_type: mac_mini_m1 max_build_duration: 90 environment: + groups: + - google_credentials flutter: stable android_signing: - golub_keystore @@ -18,15 +20,18 @@ workflows: - name: Get Flutter dependencies script: flutter packages pub get - name: Build APK - script: flutter build apk --release + script: flutter build apk --release --build-name=1.0.0 --build-number=2 - name: Build App Bundle - script: flutter build appbundle --release + script: flutter build appbundle --release --build-name=1.0.0 --build-number=2 artifacts: - build/**/outputs/apk/**/*.apk - build/**/outputs/**/*.aab - build/**/outputs/**/mapping.txt - flutter_drive.log publishing: + google_play: + credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS + track: internal email: recipients: - "denisdubov88@gmail.com" diff --git a/lib/src/presentation/ui_kit/theme/themes/light_theme.dart b/lib/src/presentation/ui_kit/theme/themes/light_theme.dart index ed280e8..221ec71 100644 --- a/lib/src/presentation/ui_kit/theme/themes/light_theme.dart +++ b/lib/src/presentation/ui_kit/theme/themes/light_theme.dart @@ -28,16 +28,14 @@ final lightTheme = ThemeData( labelStyle: AppTextStyles.bodyMedium.copyWith( color: AppColors.baseGray5, ), - floatingLabelStyle: AppTextStyles.bodySmall.copyWith( + floatingLabelStyle: AppTextStyles.bodyMedium.copyWith( color: AppColors.baseGray5, - fontSize: 14.0, ), floatingLabelAlignment: FloatingLabelAlignment.start, helperStyle: AppTextStyles.displaySmall.copyWith( color: AppColors.brightRed, ), fillColor: AppColors.baseWhite, - contentPadding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0), filled: true, focusedBorder: OutlineInputBorder( borderSide: const BorderSide(color: AppColors.baseWhite), diff --git a/lib/src/presentation/ui_kit/widgets/input/gtext_field.dart b/lib/src/presentation/ui_kit/widgets/input/gtext_field.dart index f9bf59a..149e0eb 100644 --- a/lib/src/presentation/ui_kit/widgets/input/gtext_field.dart +++ b/lib/src/presentation/ui_kit/widgets/input/gtext_field.dart @@ -27,7 +27,7 @@ class GTextField extends StatelessWidget { Container( constraints: const BoxConstraints( minWidth: double.infinity, - minHeight: 64.0, + minHeight: 68.0, ), decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(16.0)), @@ -43,6 +43,7 @@ class GTextField extends StatelessWidget { AppColors.baseBlack : AppColors.baseWhite, ), decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(horizontal: 16.0), labelText: hintText, fillColor: fillColor, filled: true, From ea0647a918fef8aa4f49e77f1cc8bbbcd52b50b4 Mon Sep 17 00:00:00 2001 From: denid88 Date: Tue, 30 Jan 2024 22:07:57 +0200 Subject: [PATCH 31/37] [Codemagic] - remove apk version --- codemagic.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/codemagic.yaml b/codemagic.yaml index 4650c19..40d93d2 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -19,12 +19,12 @@ workflows: scripts: - name: Get Flutter dependencies script: flutter packages pub get - - name: Build APK - script: flutter build apk --release --build-name=1.0.0 --build-number=2 + # - name: Build APK + # script: flutter build apk --release --build-name=1.0.0 --build-number=2 - name: Build App Bundle script: flutter build appbundle --release --build-name=1.0.0 --build-number=2 artifacts: - - build/**/outputs/apk/**/*.apk + #- build/**/outputs/apk/**/*.apk - build/**/outputs/**/*.aab - build/**/outputs/**/mapping.txt - flutter_drive.log @@ -32,6 +32,7 @@ workflows: google_play: credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS track: internal + changes_not_sent_for_review: true email: recipients: - "denisdubov88@gmail.com" From e5b6e2f76b0de7a1e54a3bb887e2014531956802 Mon Sep 17 00:00:00 2001 From: denid88 Date: Tue, 30 Jan 2024 22:13:12 +0200 Subject: [PATCH 32/37] [Codemagic] - remove draft value --- codemagic.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/codemagic.yaml b/codemagic.yaml index 40d93d2..e2cb3c1 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -32,7 +32,6 @@ workflows: google_play: credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS track: internal - changes_not_sent_for_review: true email: recipients: - "denisdubov88@gmail.com" From 4f4ab06e7803788678bdb18a82745e706514c2ae Mon Sep 17 00:00:00 2001 From: denid88 Date: Tue, 30 Jan 2024 22:21:00 +0200 Subject: [PATCH 33/37] [Codemagic] - fixes --- codemagic.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codemagic.yaml b/codemagic.yaml index e2cb3c1..cede0ba 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -32,6 +32,8 @@ workflows: google_play: credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS track: internal + changes_not_sent_for_review: true + submit_as_draft: true email: recipients: - "denisdubov88@gmail.com" From 77c6cd2b1a30adb20919173b1afdc9cf49ec2b42 Mon Sep 17 00:00:00 2001 From: denid88 Date: Tue, 30 Jan 2024 22:25:52 +0200 Subject: [PATCH 34/37] [Codemagic] - fixes --- codemagic.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/codemagic.yaml b/codemagic.yaml index cede0ba..af03ba5 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -32,7 +32,6 @@ workflows: google_play: credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS track: internal - changes_not_sent_for_review: true submit_as_draft: true email: recipients: From 83ae87baccccb2f38efe1cf74e3b991712a3ce99 Mon Sep 17 00:00:00 2001 From: denid88 Date: Tue, 30 Jan 2024 22:50:52 +0200 Subject: [PATCH 35/37] [Codemagic] - versioning --- codemagic.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codemagic.yaml b/codemagic.yaml index af03ba5..c54e85b 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -22,7 +22,7 @@ workflows: # - name: Build APK # script: flutter build apk --release --build-name=1.0.0 --build-number=2 - name: Build App Bundle - script: flutter build appbundle --release --build-name=1.0.0 --build-number=2 + script: flutter build appbundle --release --build-name=1.0.0 --build-number=$(($(google-play get-latest-build-number --package-name 'com.golub.golub') + 1)) artifacts: #- build/**/outputs/apk/**/*.apk - build/**/outputs/**/*.aab From 241bdbb3a140824c32c97f4586f6579441a8c3ca Mon Sep 17 00:00:00 2001 From: denid88 Date: Thu, 1 Feb 2024 10:51:12 +0200 Subject: [PATCH 36/37] [Links] - made links active --- ios/Podfile.lock | 2 +- lib/i18n/strings.g.dart | 2 +- lib/src/domain/blocs/auth/auth_state.dart | 2 +- .../features/auth/screens/auth_screen.dart | 7 +- .../auth/screens/verification_screen.dart | 79 ++++++++++++---- ...on_input..dart => verification_input.dart} | 20 +++- ...app_bottom_navigation_bar_item_widget.dart | 58 +++++------- lib/src/presentation/utils/link_contants.dart | 5 + pubspec.lock | 92 +++++++++---------- 9 files changed, 160 insertions(+), 107 deletions(-) rename lib/src/presentation/features/auth/widgets/verification/{verification_input..dart => verification_input.dart} (74%) create mode 100644 lib/src/presentation/utils/link_contants.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ad6510d..426afef 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -15,7 +15,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b + url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index f12c603..818bb6d 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -6,7 +6,7 @@ /// Locales: 2 /// Strings: 28 (14 per locale) /// -/// Built on 2024-01-25 at 09:17 UTC +/// Built on 2024-01-31 at 19:44 UTC // coverage:ignore-file // ignore_for_file: type=lint diff --git a/lib/src/domain/blocs/auth/auth_state.dart b/lib/src/domain/blocs/auth/auth_state.dart index 35a1c50..13810bb 100644 --- a/lib/src/domain/blocs/auth/auth_state.dart +++ b/lib/src/domain/blocs/auth/auth_state.dart @@ -18,4 +18,4 @@ class AuthState with _$AuthState { }) = _AuthState; factory AuthState.initial() => const AuthState(); -} \ No newline at end of file +} diff --git a/lib/src/presentation/features/auth/screens/auth_screen.dart b/lib/src/presentation/features/auth/screens/auth_screen.dart index d350af8..10924df 100644 --- a/lib/src/presentation/features/auth/screens/auth_screen.dart +++ b/lib/src/presentation/features/auth/screens/auth_screen.dart @@ -10,6 +10,7 @@ import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; import 'package:golub/src/presentation/ui_kit/ui.dart'; import 'package:golub/src/presentation/ui_kit/widgets/input/gtext_field.dart'; +import 'package:golub/src/presentation/utils/link_contants.dart'; import 'package:golub/src/presentation/utils/link_launcher.dart'; class AuthScreen extends StatefulWidget { @@ -137,7 +138,7 @@ class _AuthScreenState extends State { children: [ TextSpan( recognizer: _termsConditionsTapRecognizer - ..onTap = () => launchLink('https://google.com'), + ..onTap = () => launchLink(LinkConstants.termAndCondition), text: t.screens.auth.statementTermsAndConditions, style: Theme.of(context) .textTheme @@ -149,7 +150,7 @@ class _AuthScreenState extends State { ), TextSpan( recognizer: _privacyPolicyTapRecognizer - ..onTap = () => launchLink('https://google.com'), + ..onTap = () => launchLink(LinkConstants.privacy), text: t.screens.auth.statementPrivacyPolicy, style: Theme.of(context) .textTheme @@ -167,8 +168,6 @@ class _AuthScreenState extends State { bloc: _authBloc, listener: (BuildContext context, AuthState state) { if (state.status == AuthStatus.success) { - _authBloc.add(ClearStateEvent()); - _emailController.clear(); context.pushNamed(AppRoutes.verification); } }, diff --git a/lib/src/presentation/features/auth/screens/verification_screen.dart b/lib/src/presentation/features/auth/screens/verification_screen.dart index b4967b5..d0abc45 100644 --- a/lib/src/presentation/features/auth/screens/verification_screen.dart +++ b/lib/src/presentation/features/auth/screens/verification_screen.dart @@ -2,15 +2,32 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:golub/i18n/strings.g.dart'; -import 'package:golub/src/presentation/features/auth/widgets/verification/verification_input..dart'; +import 'package:golub/src/presentation/features/auth/widgets/verification/verification_input.dart'; import 'package:golub/src/presentation/navigation/app_router.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; import 'package:golub/src/presentation/ui_kit/ui.dart'; -class VerificationScreen extends StatelessWidget { +class VerificationScreen extends StatefulWidget { const VerificationScreen({super.key}); + @override + State createState() => _VerificationScreenState(); +} + +class _VerificationScreenState extends State { + final TextEditingController _verificationDigitFirst = TextEditingController(); + final TextEditingController _verificationDigitSecond = TextEditingController(); + final TextEditingController _verificationDigitThird = TextEditingController(); + final TextEditingController _verificationDigitFourth = TextEditingController(); + final TextEditingController _verificationDigitFifth = TextEditingController(); + + final FocusNode _fnFirst = FocusNode(); + final FocusNode _fnSecond = FocusNode(); + final FocusNode _fnThird = FocusNode(); + final FocusNode _fnFourth = FocusNode(); + final FocusNode _fnFifth = FocusNode(); + @override Widget build(BuildContext context) { final t = Translations.of(context); @@ -61,33 +78,56 @@ class VerificationScreen extends StatelessWidget { ), Container( padding: const EdgeInsets.only(top: 40.0, bottom: 28.0), - child: const Row( + child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( flex: 1, - child: VerificationInput(), + child: VerificationInput( + textInputAction: TextInputAction.next, + controller: _verificationDigitFirst, + focusNode: _fnFirst, + nextFocusNode: _fnSecond, + ), ), - SizedBox(width: 8.0), + const SizedBox(width: 8.0), Expanded( flex: 1, - child: VerificationInput(), + child: VerificationInput( + textInputAction: TextInputAction.next, + controller: _verificationDigitSecond, + focusNode: _fnSecond, + nextFocusNode: _fnThird, + ), ), - SizedBox(width: 8.0), + const SizedBox(width: 8.0), Expanded( flex: 1, - child: VerificationInput(), + child: VerificationInput( + textInputAction: TextInputAction.next, + controller: _verificationDigitThird, + focusNode: _fnThird, + nextFocusNode: _fnFourth, + ), ), - SizedBox(width: 8.0), + const SizedBox(width: 8.0), Expanded( flex: 1, - child: VerificationInput(), + child: VerificationInput( + textInputAction: TextInputAction.next, + controller: _verificationDigitFourth, + focusNode: _fnFourth, + nextFocusNode: _fnFifth, + ), ), - SizedBox(width: 8.0), + const SizedBox(width: 8.0), Expanded( flex: 1, - child: VerificationInput(), - ) + child: VerificationInput( + controller: _verificationDigitFifth, + focusNode: _fnFifth, + ), + ), ], ), ), @@ -106,19 +146,18 @@ class VerificationScreen extends StatelessWidget { child: Text( t.screens.verification.noCodeButtonLabel, style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: AppColors.brightOrange, - ), + color: AppColors.brightOrange, + ), ), ), ], ), const SizedBox(height: 36.0), ElevatedButtonWidget( - buttonLabel: t.common.buttons.next, - onPressed: () { - context.goNamed(AppRoutes.onboardingProfile); - } - ), + buttonLabel: t.common.buttons.next, + onPressed: () { + context.goNamed(AppRoutes.onboardingProfile); + }), ]), ), ), diff --git a/lib/src/presentation/features/auth/widgets/verification/verification_input..dart b/lib/src/presentation/features/auth/widgets/verification/verification_input.dart similarity index 74% rename from lib/src/presentation/features/auth/widgets/verification/verification_input..dart rename to lib/src/presentation/features/auth/widgets/verification/verification_input.dart index 66f65fc..1fdeb85 100644 --- a/lib/src/presentation/features/auth/widgets/verification/verification_input..dart +++ b/lib/src/presentation/features/auth/widgets/verification/verification_input.dart @@ -5,8 +5,18 @@ import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; class VerificationInput extends StatelessWidget { final TextEditingController? controller; final FocusNode? focusNode; + final FocusNode? nextFocusNode; + final TextInputAction textInputAction; + final VoidCallback? onEditingComplete; - const VerificationInput({this.controller, this.focusNode, super.key}); + const VerificationInput({ + this.controller, + this.focusNode, + this.nextFocusNode, + this.textInputAction = TextInputAction.done, + this.onEditingComplete, + super.key + }); @override Widget build(BuildContext context) { @@ -19,6 +29,7 @@ class VerificationInput extends StatelessWidget { controller: controller, focusNode: focusNode, keyboardType: TextInputType.number, + textInputAction: textInputAction, inputFormatters: [ LengthLimitingTextInputFormatter(1), FilteringTextInputFormatter.digitsOnly, @@ -44,6 +55,13 @@ class VerificationInput extends StatelessWidget { borderRadius: BorderRadius.circular(16.0), ), ), + onEditingComplete: () { + focusNode?.unfocus(); + nextFocusNode?.requestFocus(); + if (onEditingComplete != null) { + onEditingComplete!(); + } + }, ), ); } diff --git a/lib/src/presentation/navigation/app_bottom_navigation_bar_item_widget.dart b/lib/src/presentation/navigation/app_bottom_navigation_bar_item_widget.dart index 62ad463..abcc578 100644 --- a/lib/src/presentation/navigation/app_bottom_navigation_bar_item_widget.dart +++ b/lib/src/presentation/navigation/app_bottom_navigation_bar_item_widget.dart @@ -9,13 +9,12 @@ class AppBottomNavigationBarItemWidget extends StatelessWidget { final bool isActive; final VoidCallback onTap; - const AppBottomNavigationBarItemWidget({ - required this.image, - this.label = '', - required this.isActive, - required this.onTap, - super.key - }); + const AppBottomNavigationBarItemWidget( + {required this.image, + this.label = '', + required this.isActive, + required this.onTap, + super.key}); @override Widget build(BuildContext context) { @@ -31,33 +30,29 @@ class AppBottomNavigationBarItemWidget extends StatelessWidget { children: [ AnimatedContainer( duration: const Duration(milliseconds: 250), - width: isActive ? - AppSizes.bottomNavigationBarActiveItemSize : - AppSizes.bottomNavigationBarItemSize, - height: isActive ? - AppSizes.bottomNavigationBarActiveItemSize : - AppSizes.bottomNavigationBarItemSize, + width: isActive + ? AppSizes.bottomNavigationBarActiveItemSize + : AppSizes.bottomNavigationBarItemSize, + height: isActive + ? AppSizes.bottomNavigationBarActiveItemSize + : AppSizes.bottomNavigationBarItemSize, child: SvgPicture.asset( image, - colorFilter: ColorFilter.mode( - // isActive ? - // Theme.of(context) - // .bottomNavigationBarTheme - // .selectedItemColor! : - // Theme.of(context) - // .bottomNavigationBarTheme - // .unselectedItemColor!, - Colors.white, - BlendMode.srcIn - ), + colorFilter: const ColorFilter.mode( + // isActive ? + // Theme.of(context) + // .bottomNavigationBarTheme + // .selectedItemColor! : + // Theme.of(context) + // .bottomNavigationBarTheme + // .unselectedItemColor!, + Colors.white, + BlendMode.srcIn), fit: BoxFit.cover, placeholderBuilder: (BuildContext context) => Container( width: AppSizes.bottomNavigationBarItemSize, height: AppSizes.bottomNavigationBarItemSize, - decoration: const BoxDecoration( - color: Colors.grey, - shape: BoxShape.circle - ), + decoration: const BoxDecoration(color: Colors.grey, shape: BoxShape.circle), ), ), ), @@ -70,11 +65,8 @@ class AppBottomNavigationBarItemWidget extends StatelessWidget { // Theme.of(context) // .bottomNavigationBarTheme // .unselectedLabelStyle!, - style: TextStyle( - color: Colors.white, - fontSize: 12.0, - fontWeight: FontWeight.w600 - ), + style: + const TextStyle(color: Colors.white, fontSize: 12.0, fontWeight: FontWeight.w600), child: Text( label, style: const TextStyle( diff --git a/lib/src/presentation/utils/link_contants.dart b/lib/src/presentation/utils/link_contants.dart new file mode 100644 index 0000000..71378ac --- /dev/null +++ b/lib/src/presentation/utils/link_contants.dart @@ -0,0 +1,5 @@ +class LinkConstants { + static const String _web = 'https:'; + static const String termAndCondition = '$_web//doc-hosting.flycricket.io/golub-terms-of-use/c3b635a9-679b-43a0-8d60-f209ba2d5a8c/terms'; + static const String privacy = '$_web//doc-hosting.flycricket.io/golub-privacy-policy/7a3f0325-e980-4da9-a87b-5303de81861e/privacy'; +} diff --git a/pubspec.lock b/pubspec.lock index 1cc9762..35aa2cc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: archive - sha256: "7e0d52067d05f2e0324268097ba723b71cb41ac8a6a2b24d1edf9c536b987b03" + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" url: "https://pub.dev" source: hosted - version: "3.4.6" + version: "3.4.10" args: dependency: transitive description: @@ -77,18 +77,18 @@ packages: dependency: transitive description: name: build_daemon - sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.1" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_runner: dependency: "direct dev" description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: build_runner_core - sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185 + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" url: "https://pub.dev" source: hosted - version: "7.2.11" + version: "7.3.0" built_collection: dependency: transitive description: @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: built_value - sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" + sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 url: "https://pub.dev" source: hosted - version: "8.7.0" + version: "8.9.0" characters: dependency: transitive description: @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.10.0" collection: dependency: transitive description: @@ -345,10 +345,10 @@ packages: dependency: transitive description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" http_multi_server: dependency: transitive description: @@ -473,10 +473,10 @@ packages: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" nested: dependency: transitive description: @@ -513,26 +513,26 @@ packages: dependency: transitive description: name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "6.0.2" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.8" pointycastle: dependency: transitive description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "3.7.4" pool: dependency: transitive description: @@ -622,10 +622,10 @@ packages: dependency: transitive description: name: source_gen - sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.5.0" source_span: dependency: transitive description: @@ -710,26 +710,26 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" + sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.2.2" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.4" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" url_launcher_macos: dependency: transitive description: @@ -742,50 +742,50 @@ packages: dependency: transitive description: name: url_launcher_platform_interface - sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" + sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.1" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" + sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" + sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" + sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 + sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" url: "https://pub.dev" source: hosted - version: "1.1.9+1" + version: "1.1.9+2" vector_math: dependency: transitive description: @@ -822,10 +822,10 @@ packages: dependency: transitive description: name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.5.0" yaml: dependency: transitive description: @@ -835,5 +835,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.13.0" + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" From 5ae291fa66366a379c69748099da55458d5e9c1f Mon Sep 17 00:00:00 2001 From: denid88 Date: Sat, 3 Feb 2024 17:46:11 +0200 Subject: [PATCH 37/37] [Updates] - profile screen, widgets improvements, localization updates --- android/app/src/main/AndroidManifest.xml | 2 +- .../main/res/mipmap-hdpi/launcher_icon.png | Bin 0 -> 2218 bytes .../main/res/mipmap-mdpi/launcher_icon.png | Bin 0 -> 1430 bytes .../main/res/mipmap-xhdpi/launcher_icon.png | Bin 0 -> 3082 bytes .../main/res/mipmap-xxhdpi/launcher_icon.png | Bin 0 -> 5009 bytes .../main/res/mipmap-xxxhdpi/launcher_icon.png | Bin 0 -> 7167 bytes assets/logo/logo.png | Bin 0 -> 38715 bytes .../Icon-App-1024x1024@1x.png | Bin 10932 -> 79750 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 295 -> 513 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 406 -> 1094 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 450 -> 1668 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 282 -> 768 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 462 -> 1578 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 704 -> 2584 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 406 -> 1094 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 586 -> 2313 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 862 -> 3682 bytes .../AppIcon.appiconset/Icon-App-50x50@1x.png | Bin 0 -> 1376 bytes .../AppIcon.appiconset/Icon-App-50x50@2x.png | Bin 0 -> 2983 bytes .../AppIcon.appiconset/Icon-App-57x57@1x.png | Bin 0 -> 1562 bytes .../AppIcon.appiconset/Icon-App-57x57@2x.png | Bin 0 -> 3468 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 862 -> 3682 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 1674 -> 6111 bytes .../AppIcon.appiconset/Icon-App-72x72@1x.png | Bin 0 -> 2023 bytes .../AppIcon.appiconset/Icon-App-72x72@2x.png | Bin 0 -> 4627 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 762 -> 2148 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 1226 -> 4921 bytes .../Icon-App-83.5x83.5@2x.png | Bin 1418 -> 5478 bytes lib/i18n/locale_en.i18n.json | 6 +++- lib/i18n/locale_uk.i18n.json | 6 +++- lib/i18n/strings.g.dart | 32 ++++++++++++++++-- .../features/auth/screens/auth_screen.dart | 2 +- .../auth/screens/verification_screen.dart | 4 +-- .../onboarding/onboarding_profile_screen.dart | 26 ++++++++++---- lib/src/presentation/ui_kit/ui.dart | 2 +- ...evated_button_widget.dart => gbutton.dart} | 4 +-- .../presentation/ui_kit/widgets/widgets.dart | 2 +- pubspec.lock | 24 +++++++++++++ pubspec.yaml | 6 ++++ 39 files changed, 98 insertions(+), 18 deletions(-) create mode 100644 android/app/src/main/res/mipmap-hdpi/launcher_icon.png create mode 100644 android/app/src/main/res/mipmap-mdpi/launcher_icon.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/launcher_icon.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png create mode 100644 assets/logo/logo.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png create mode 100644 ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png rename lib/src/presentation/ui_kit/widgets/buttons/{elevated_button_widget.dart => gbutton.dart} (96%) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 778788a..12532cf 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ + android:icon="@mipmap/launcher_icon"> eX=ixo3)5*|I@3P1 z41MOM%ygJe+oA1DLOTtinWOfCSP338VuONCza44oDy!kU%;h zfiwXWPb6`*+mCzKmuu17s*)yvzPdGw<6UEDTUQEmu~CaASB*3QbmeP50--oQx#Gk2 zEmc}HxoV^u5KpfjPe9uL__Imex5*A`iAnX1Er+_U`tb0k3Psnb1~f1dLO7a`_JiRB zKD;=ICmvd(wpq=UppJa)$RHlvV29CYkbRwMKz&1@MeomDn!;8ZMQbX}YMa(nC+WU= zsWYd8aa5Mde{A(5$h#O2Pr1V%c=6-!)+_3%f5~%_dk0&ub8_Rm^1l`nz=2AE-R(7iKht9 zIDuXFJJozm-tvrwaQO7t{Cjhe5oK0oA&4qKvDh3MLk>vq(o}1yx1qVts^+T{i+`+n z{hdL?;&XS#uXPSH3KCJ=0gDJNgq5HW>95BI@w2^6D6DwMB6|s_xiilGhtPe;kL%9v%2jo#GzMN8{5`ym?|cG%+Wk-6wTx!|>(ysA>E0%WtK8@$}4j-MSzF&WC8TODe?Hm8nJ zCtSEblks^u8RyM~24p2bT;2##Z>9f$oedSA^Y%xh(%)V8YY*wu8#6d~awP2@=3aN) zQz3PZQmM9@RrX>GrJ_{1I(*728TiLKwR8WyLqYuU--8()C5b-Ub6HA;*XFnpTszT~3=69NEEa4 zd@_M?UlcDsQJZ%qvTrzuKm2PzD%fRe4%5DGRp;FassXKaD~m1CXTbSuGZ>w)pQ(pQMJ}is5D(pr zEoC@&*_Ux_3458kaPbN33E!a9;37@e=O3+xYl(tekfsv)5yWa85O6=L@L`~IVPz+@A2D|Vp{4~mDXyf=t zX@a^?ngDXqLu{i5I{o>iybA;%Il+AI(@AM-ZLp$aeK|HZ*|3hh(NeL@bRdQ=`vN#m zaL*9r0kUgc8{=@ImTVixLEqo*g1btYozpCP+)cgB<=5W9pjJ&v8ToZjKzeX~Se`v@ zdRPa=UOb%`q>sQ1O+?@&Z+II>`bcJ2fB)|&{?RptpFi7xryr0Xe`yAY4dLEBwfOUq z0rY!w$3^J_DCdA$5YWUET;mDj7PZeH7)UaCr>Yp3}{1L^lihp^UV!LH4f!n@PFKw}|D`=6>q*XL6> zadr%$X#T5=(;QuH*y$(dILv=vJ=BLSEv0aj7w5c(WvO5p$@M?7r3&{@cz^8l7(U}j zThy(}58sgh4%@4Z6c*Gu%~CkPvD2hKhNBlIu>aAToJ}uliH{T6eUwJ*q|E&&8OY1s z{w2>%Lar^!awyBi{MJ=bj34f<#^YNY5~O@okQ%7lS%ROCZR!~a;K->ld_54zyDhn+ z>VdX${CaPb6bGqMK^IVQ@&4uK8*zq$%0nkd5RNR*e?hd9ZF%*@b$Qi)(pB&kW}KQl zbZ;f9DPVi;kOztQ9Z1CU96Qwgj)0jArL1JGw4S&3~rGOWi)_ z7vF1u)1KQiDbb3qQLJB6lJQxt(IL{FtySo36X$74w4#9eM-^5g=aosT*=@m3pQ=}P zLs_&UfH=qMy6nTb|D{SKDs6RDc@ciIe_h_LJeP!kI3bKs4r8?#Ro9Mv{wTh@86Y@Q z=pG0nb0vy$Qg)jOzxz>3Mp;BF2?6nHk>Sx0&VDf^Ep?F!R0ONUY@*F5)sl;g4N|ho z#VUS8UhWo&C8gxhABo;e!OcNE7(H7T)p4;xiUdo}0WUdWRqr8lCquVNN{yR;l^5gXES(e;^ETm&;!5`nuJ+8rGZI>IG$@U8%tZ!t zZnWdcZBBCKg;(B@QaH7Gd-Ha%p4nVo&d61fx?COp-@qfFVj$IfUTIfxRJT-Ofrg z8YsGU*oz9Xj!2FQ5-9RQt6F;S+AOKXYM1{1Vgl)a1kwQsqyrL22PBXVNFW`MKsq3S sbU*^>fCSP338VuONCza44oIN?1Ma0KE~Z8U*8l(j07*qoM6N<$f{pt+E&u=k literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/android/app/src/main/res/mipmap-mdpi/launcher_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..970e724679b817d113bb6a171d501dfc0301ef72 GIT binary patch literal 1430 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 C&7F$N!0%-^!{h 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 literal 0 HcmV?d00001 diff --git a/assets/logo/logo.png b/assets/logo/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a88e662aa66abd6c1bb1a70682269e296115f20b GIT binary patch literal 38715 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 z z;Nk;-_N@sx^RiVz2Jax<sO$6>z_5AQs=W5QZCZC7_Mmc>GN>xnZsWO+Kdef4?Am z8lXovjsIo_fc8`A?f-s3)&C>>|M!sp#^h!_{I^d2^PT_V$$#PTzff=!4NW?(sUE}L V{LA%I{dx0WO-W0!{Kea_{{zIt8}R@D literal 10932 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 797d452e458972bab9d994556c8305db4c827017..54dc729ee4c788f71d71e6604a85a3949d36eb68 100644 GIT binary patch delta 1084 zcmV-C1jGB51I7rD8Gix*000A=FFF7K1Pw_cjvzM=`OG=E3QRL zi)1BOh_M7CA=*G1>q7%JHce`p=o=4x;i(Ur_~?UA#u(EywBgBUkkDZ8R!KpHAOW;W zB-~^dcG(&BI=ge}ha1ObcJ^vgZOWI)&e=2P%aX2x)Kh42;9rNl?vD-W^%=n%VV+BfPzc9_!4d!Q!(~NKJ?rs- zvA|weA>dG0kbldMrrCXaj73D*)jFwWWP*Ipz^fm>-hrmYr>en9UA(>oLxv4eRZbNECW%!4Y@T4x7bCw9uAKbR zf#lj<5SW+xmYEO_-Kw2Bqne*Gf=z}uk0gzJaE?6-5`Q?-(Q4Ofv$6aC@=wf$lD(QU zY{iUVqaKAxM`CYRI>~?Vgb^f7XRAGvs*FyveZy1mB`8l1_Rvyuxf(xbUys^m6yj4l{W9A zlX55s(toqL5RM@yj+Em#g5z2U!(Dcju*wZ~e`0zt5%Boqa*dR73r2-s{#dy&>}#o~ z8$LBx*ofLaCe7=}T3F+Tr`hMPqA-qwHRcw#B|RpxfnmrUM;({1ErNJ$d7zbtpz*PZ z-rK9c4S6XNrHvRk*~7%yh#-g(gaZ8De7N3JYkxlf{VoFAA|pA@<1#Dp!!c*iz>1$u zy!CmS_n{&b71#(bh?_7)vwKtQ+1`b7Cta!Y&YcKAh;@DIy8Xz1e}aj|#hmR^jW%En z)h3$>izcL(p4e`Ot^hC?MIh}>rOovp-5Cqq9S;CLWwDjhm;ZMjDm`_yoFY_&I$vV@|3P z45p*drv27NHw`J~*v!V#Y7Ah2R25FS5=PuoM}OC9HyiR@JN0&O_U*5+z~y7&Q?sF& zxzN04Q@x}#{~9T@@iTMV0h_ag%oCXnEL#ap9B#0}1(}e?$ALfwBSM@PK~2qitTo(5 zRFsg#W!?FD24dCa(795i=N;{X{C3&~Z-f7v;C}#Z#G}b-X_Z<40000+>eB z?J{?+FLkYu+4_Uk`r_>LHF~flZm0oBf#vr8%vJ>#p~!KNvqGG3)|f1T_)ydeh8$vDceZ>oNbH^|*hJ*t?Yc*1`WB&W>VYVEzu) zq#7;;VjO)t*nbgf(!`OXJBr45rP>>AQr$6c7slJWvbpNW@KTwna6d?PP>hvXCcp=4 zF;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f~Y3fegFUf diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 6ed2d933e1120817fe9182483a228007b18ab6ae..d8b87346a771423fcc3080908919bac66cc59831 100644 GIT binary patch delta 1662 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 delta 266 zcmV+l0rmcX2ATqp8Gi!+003c4mpuRg09{Z_R7L;)|5U~JDYo_jSDX9(|7FYh`2GLd z^Zv2r{H^2sT*&w!Y^SB+`<>qVZqE6)=lqo0`vF#&*75!I`TIh@_d&k*HoEtQyV-iD z%Xz2D9EQRbeYh5Nr~y=#0ZD;^+vz0$004MNL_t(2&&|%+4u6C&2tZM$Wf&dzefR%A z(^3-?6X>hnCz2Ba@RH&`m!pgy?n@#@AuLYB&}Q)FGY`?vcft0!vht0Z@M&ZeNCWXh75gzRTXR8EE3oN&6 Q00000NkvXXt^-0~g5IBncmMzZ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index fe730945a01f64a61e2235dbe3f45b08f7729182..297be3ab6e547339f36941321d57ba9b48dee315 100644 GIT binary patch delta 1572 zcmV+<2HW}01F8&=8Gix*003^;-G2Z81@TElK~#7F?U`##Q)d{*+tZ$2PD}6QBD8>@ zVwoZuCdihcQb-d zxG9gDOXYEMsXT5j_4MNQ-*whj7Z4Ib?r72q6_=vuo&kr;7uma0n^Pcmq2gL*f*xO_ z@2<1CP9@EmffpjQ)k&6W#i=ep#T}jS!(Wo5uMRl&H|erNm!K}Qlh5a60T-+rYh1tM z+_p(ks1ap>Zhy5Xn*Q#J^)Ra~kZ>Vuo3V#tZTMb4T$wp_#2~=Z5wovyC$LH44t_&9bQ-(qEyQ^iIH%zWrt!=cinK56-w$h6l8$7qN`@-AA{~#Ta05y zy@cgrBwQSi;wD-cwt43+&!`JzpEV4PtvoOP!A6{!Dgsk3$gqtK!SY{`~)R~_q zPQPl%V1L?$7pSiXxC(H8KGVD_zdZiPSLZFKUnw&baj-G0<=?l@ zj7|mPJNIl=vEPN}3_?sH6FNK#sV?vafO!1-&mJSASVB>eyu9+HGfmq@Jmg<2WW1VnUn`fd<3)&9zDt3{+gT0^d+B8yxe- zoPRCe$cY9Y8ux#6cC@ipaj02WtXYBvdsmhba3GGjCS1_oM(v2jPf|%UCMGjvgFR-~ zRkO=fA!{-zYAa+qB_8h~;1%~R{_eZZ{s*4v`LI~TuPGxA?$owyQKoVvZn%222zEYg zJvNqW7}NlLXViOZ#0w2MAt+Ldq+$WNK!44;!jp5+R1x~&^&!TG{8aCd=h~oWU%l$% zW!yAb5(ZCn|oAl~mZ#sfu>hqJ8N%LuLxbT@? zY}P{n`?=c&Yb^_8YV7pAop)T{v`xSJ^18%Rnba9z^HXgg+OOOD{&Gc_sbxC8)PIso z1=S@IrA!#1c;-jGN48*jLb{V-Swv0%Ue($K^m!7${ zxHy9733?U&*&CIJth{X1$Y2T{e1A@NZ_M{_I*@oa6*?@hA09QTPk)PWMNqhtXW(wgB3|Ha?uEVCiRTQ-&wpPZ-^ z;TYGklH*QVgSQvAdxTUVl?cQl3=Uk##{|)E4(lQWP+uV8@PsFAp^=Gz$A8b*EYqnR z0=g@TB%hwFV6P`|!!0e6Kt6tJ(A{TtLhxLQoUss3^A-;}LNEWMlCi;SQA>RBs;`!R zaKunRWcXE6-Uj(-!xq(clX7UxH*nuQI?2H=qe^%Ki3oeGrSM>r7Gtj~u`115Lhfv; zlEX3Y3g3ROG)jDTgBFh2`OxsVe{?GF za4O*PrQB?SL^yw=LDo>CXf!DZads-$oPDTJBZi}Sn;P1XOj_-v-5qxO7GO&Q(+z?? zArYtxgs_Q#%@2Ex*@VR)&D_x+*~S;n@i5 ziGV;=={^- z?sLQGb)?A{hr$_!z8HbH7kH=vM0x-*R~t>;jsO4v^GQTOR7l6|(&r9>FcgO2dg?%> z;=sK?5%;?Pn^T7LL?Y$@5u?06NuIR*0?Yf$Hf5Afk+lM<^ch*jvO$sU*m9J?JI7eI zGFV6+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9R%3*Q+)t%S!MU_`id^@& zY{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&p6kME1_Z%?`+u)^el0!1<0sd p?Eyu!OMLDifi)An*I;?S-wj=m4RYIt!kPd8002ovPDHLkV1f~Q^d|rS diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 321773cd857a8a0f0c9c7d3dc3f5ff4fb298dc10..dcf1eb51f4695a802d14405f4009dc0494913bb3 100644 GIT binary patch literal 2584 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 delta 691 zcmV;k0!;mw6u4br2|=<_Wb|z`~RBV`-<24{r>;E==`tb{CU#(0alua*7{P! z_>|iF0Z@&o;`@Zw`ed2Hv*!Fwin#$(m7w4Ij@kM+yZ0`*_J0?7s{u=e0YGxN=lnXn z_j;$xb)?A|hr(Z#!1DV3H@o+7qQ_N_ycmMI0acg)Gg|cf|J(EaqTu_A!rvTerUFQQ z05n|zFjFP9FmM0>0mMl}K~z}7?bK^if#bc3@hBPX@I$58-z}(ZZE!t-aOGpjNkbau@>yEzH(5Yj4kZ ziMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_stABAHe$v|ToifVv60B@podBTcIqVcr1w`hG7HeY|fvLid#^Ok4NAXIXSt1 Zxpx7IC@PekH?;r&002ovPDHLkV1lm-Ydin| diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 797d452e458972bab9d994556c8305db4c827017..54dc729ee4c788f71d71e6604a85a3949d36eb68 100644 GIT binary patch delta 1084 zcmV-C1jGB51I7rD8Gix*000A=FFF7K1Pw_cjvzM=`OG=E3QRL zi)1BOh_M7CA=*G1>q7%JHce`p=o=4x;i(Ur_~?UA#u(EywBgBUkkDZ8R!KpHAOW;W zB-~^dcG(&BI=ge}ha1ObcJ^vgZOWI)&e=2P%aX2x)Kh42;9rNl?vD-W^%=n%VV+BfPzc9_!4d!Q!(~NKJ?rs- zvA|weA>dG0kbldMrrCXaj73D*)jFwWWP*Ipz^fm>-hrmYr>en9UA(>oLxv4eRZbNECW%!4Y@T4x7bCw9uAKbR zf#lj<5SW+xmYEO_-Kw2Bqne*Gf=z}uk0gzJaE?6-5`Q?-(Q4Ofv$6aC@=wf$lD(QU zY{iUVqaKAxM`CYRI>~?Vgb^f7XRAGvs*FyveZy1mB`8l1_Rvyuxf(xbUys^m6yj4l{W9A zlX55s(toqL5RM@yj+Em#g5z2U!(Dcju*wZ~e`0zt5%Boqa*dR73r2-s{#dy&>}#o~ z8$LBx*ofLaCe7=}T3F+Tr`hMPqA-qwHRcw#B|RpxfnmrUM;({1ErNJ$d7zbtpz*PZ z-rK9c4S6XNrHvRk*~7%yh#-g(gaZ8De7N3JYkxlf{VoFAA|pA@<1#Dp!!c*iz>1$u zy!CmS_n{&b71#(bh?_7)vwKtQ+1`b7Cta!Y&YcKAh;@DIy8Xz1e}aj|#hmR^jW%En z)h3$>izcL(p4e`Ot^hC?MIh}>rOovp-5Cqq9S;CLWwDjhm;ZMjDm`_yoFY_&I$vV@|3P z45p*drv27NHw`J~*v!V#Y7Ah2R25FS5=PuoM}OC9HyiR@JN0&O_U*5+z~y7&Q?sF& zxzN04Q@x}#{~9T@@iTMV0h_ag%oCXnEL#ap9B#0}1(}e?$ALfwBSM@PK~2qitTo(5 zRFsg#W!?FD24dCa(795i=N;{X{C3&~Z-f7v;C}#Z#G}b-X_Z<40000+>eB z?J{?+FLkYu+4_Uk`r_>LHF~flZm0oBf#vr8%vJ>#p~!KNvqGG3)|f1T_)ydeh8$vDceZ>oNbH^|*hJ*t?Yc*1`WB&W>VYVEzu) zq#7;;VjO)t*nbgf(!`OXJBr45rP>>AQr$6c7slJWvbpNW@KTwna6d?PP>hvXCcp=4 zF;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f~Y3fegFUf diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 502f463a9bc882b461c96aadf492d1729e49e725..59d085cc16e3ae2e117f4266bcf16ba97c36aa56 100644 GIT binary patch literal 2313 zcmV+k3HJ7hP)Ow3G}(56sGNhd=K%(PE^=uG?2(idL( zP}*r{GM#$D5TFf{G`Mk~G{mtO~wRvn%p@B)5rkUGQZn){c zwzO@NN-E~(wN+VT*%MOBk+@(Io?G<$0yNzC1N6{0%e%Lg;mOGhJpS;B3+6YvYEUX; z5}ug##_tEec64k~%cb{B$tN#bCQW|C%9w;_7JTu0uRq*3Xg%0jg(oUUxO@@yHdaXx z>*Ej(g&C7AkXSN2>U^kG*<_GmNXrqY1}xCy-ar_!J`Q1rlM07dR>EQ*3`3mlx4iLU zJxQ*sivpe>U9!SWm_}Wh!y)W+hlo|GahR;Z!5fZU>+7OE>bE#Nk;Vv+I~s?u+Z#$# z$|L%|v^DCbwaC*cN0^p^C%>=|(XN+CP)#Nd;g~~81rwRu@0~OL>~MovL}G}|7CoPt z0@O-N$`q(Xa0mxO(bq)BG%tB<+nv;zi{?Z7sxic7g;(qQej9≫3C{?uHeoocKsq+8+<87#!#KVPh^5?7TKDv|2>)U@ab zCYH45rHIes5YCt$Q6c7UpBUdfREKU$&~bgz-S=O6Vu@HtZmcaNVG)8&g2?3fnfccb z)(QprG++=m|M~2Ed^;@m+1?}v&WI9l2+Jf%(o-CA{Oi<$@A#R8*ACQ(kXyqI@B)80 zHD`CGI>p@&sL_Ss5LPB9Vrz!_F6y^uJSX~$KkBOC@$wLXeSFaj#yKv0#ZB;tr`k2> zLUJOU8qOvAsMziBm|H4d{9b2Oj#20EnCtvd!qnvvG1J-BAg@pf(dFO}Rx1Po0WU}~ zytR&wBDn+N^apM@f)xGIem(YL(6v$LUwe&oV!X5Z;%6VuE)~Zi47#c+7k_8+a|i5+ zzg`(}+FhYz-3E-b8B(#HUSlZ4#8k>82v9omd$3}(UU3uYM138`Pw`mbSNP=FI_c8!3upjlI&AM3arHW3Hi57jOW*87jo(YM~?= zvAg|YvxAym^!tJlQ%w_0%IejfIp5nKes{9p^3H2bH3<1xuDgfbdvvzt;IwgdSvZ~+ zr$eMXp>NVw^HcQ1f^Tfz2mGQ8iI_6En=!uNd;Q&szr5K}t%+C&mdG^d6Nqdj=+-SOjZZP2N?W_2F+hrx5a@?6blU)et&vZYus^iV8)`AV0t zOHBScZw>zHy_rA$ERE3q*yFZdPx( zU(xd~BgB%d^->sSvfVH4)*aoi$8J@@Pw_}Zd`JoZ^zq!yDNp9wMEcPmFKcU(AKjnR zD*t*MTY{XX>#>S8!U%pVZMjTL{_^|vV4ZSl%sQRZszcrxNJYX3Km25+PK~;y!iIGs z?Dnl>&dFOdYv4$sW7vA1;?NVi+*`6vgnPeSNva|W%RZ!Rs1*PDrTiQSD=c9M`XR44 zcg1#N(!I9$mTudTRRTfOR)~Lhq*xLMD($lX97aFAqWvpf^cduf^p1Q}ld_=Hz_A)2+uGS~<*QbO=dJ2$I-dcc5q zz78Eo;=qw^Y*N5wv;>B3IYuVkRG5=f#tF(^0g9DU{;{r_T@PllMTU;-`cJPDckiw0 z+);jO!hLhxHMbbZ{7hP~uoVl*&TZO*yL1}lGaK0gN3L8d*xsgrYspCg^Bd!?1#2ee z>e zRw=A56B%^k#%if#UCubgk>{xbR$ZqR=`t-H-b0lB$)ng&B3$e!5iWL=2p2m_go_;| j!o`jf;bKRLaIxcm?$4KWb-ct300000NkvXXu0mjfW(|ci delta 572 zcmV-C0>k}@63PUS8Gi!+006nq0-pc?0H{z*R7L;)|5U~JDYo_jSDXF*|5nEMy6F5^ z$M}8I`uzU?*Yf=uXr;5|{0m;6_Wb|A>ik^D_|)+I$?g3CSDK^3+eX0mD!2CP`2NN0 z{dLg!a?km&%iyTt`yiax0acdp`~T(l{$a`ZF1YpsRg(cvjDG_-U$Er-fz#Bw>2W$eUI#iU z)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G!hkE!s;%oku3;IwG3U^2k zw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn_j%}l|2+O?a>_7qq7W zmx(qtA2nV^tZlLpy_#$U%ZNx5;$`0L&dZ!@e7rFXPGAOup%q`|03hpdtXsPP0000< KMNUMnLSTZuA2k>N diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 0ec303439225b78712f49115768196d8d76f6790..fe1e28667ed5fc8a0e1ac9c2fca3756f7732fae4 100644 GIT binary patch literal 3682 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 delta 850 zcmV-Y1Fihx9Nq?y8Gi!+000iU#^3+|0OwFlR7L;)|5U~J09TtSw)Xt~|5(QO`~Ck( z!T0|D|3<*~RmJ%E{r+;#`2ba!klFf7!uJMSo%Q?vP{jByxcAZE>;OrUCbaZYjJo^$ z{nGILmD~Da$@upC{`C6(Ey4dPw)Pyc^>5DkHoEo!QcuK-Jwl-l}t(fQKv z{dds$V#@dygS`PvhX6is7Z+@*x-d;$ zb=6f@U3Jw}_s+W3%*+b9H_vS)-R#9?zrXogeLVI2We2RFTTAL}&3C8PS~<5D&v@UI z+`s*$wqQ=yd$laNUY-|ovcS9~n_90tFUdl#qq0tEUXle|k{Op|DHpSrbxEeZ5~$>o%>OSe z^=41qvh3LlC2xXzu+-2eQoqs1^L>7ylB$bCP);(%(xYZL1 cY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f@|{182|tP diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..a8873eb6445b51f0f7067861551d05a0ffc212b2 GIT binary patch literal 1376 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 delta 850 zcmV-Y1Fihx9Nq?y8Gi!+000iU#^3+|0OwFlR7L;)|5U~J09TtSw)Xt~|5(QO`~Ck( z!T0|D|3<*~RmJ%E{r+;#`2ba!klFf7!uJMSo%Q?vP{jByxcAZE>;OrUCbaZYjJo^$ z{nGILmD~Da$@upC{`C6(Ey4dPw)Pyc^>5DkHoEo!QcuK-Jwl-l}t(fQKv z{dds$V#@dygS`PvhX6is7Z+@*x-d;$ zb=6f@U3Jw}_s+W3%*+b9H_vS)-R#9?zrXogeLVI2We2RFTTAL}&3C8PS~<5D&v@UI z+`s*$wqQ=yd$laNUY-|ovcS9~n_90tFUdl#qq0tEUXle|k{Op|DHpSrbxEeZ5~$>o%>OSe z^=41qvh3LlC2xXzu+-2eQoqs1^L>7ylB$bCP);(%(xYZL1 cY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f@|{182|tP diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index e9f5fea27c705180eb716271f41b582e76dcbd90..d4f470fab44caea14a40839ad93e340c247df03e 100644 GIT binary patch literal 6111 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 O0000m;7KJN~sN*SAgv!5&?Vo#B;9A zT2U2XQE%e*Djy6Ps7&B+|P?h2zAbQzI)X~S!$h{wrpzU4uRki@Tp!;KxkVBY&`KAjPzDVsBRv~@JPrm{dx=R#&+f~lyN+}QRExWP!i+?2F%KJk?3#nt6fFuWoG z9BiPPPgI>q_|vd3>spV4VkZ>3gwL*n7Xn6n&64abO)8Dz86LpYRLaTjgAeXz`CJHWwiJPN6^GBhk*To!3$l79>a9gD)~w|59S_V zJG>#&c);QaPQgUa3n2XQkFlTHtRHUIH!r8~kO_jpI&RsL_w6a;uqC!nBS^|~Y6%R7 zbj$z5;s*?XAqWJIJIi6Y9ym5+a|QQq(8g(&c!~i_d~dVvSnFu-6E6}kFphDCAU!kt zRBW?^AYYy_6{>~nYqH6nI5?~$Br7b4?boAcH!Q=VzFcK5sIHXtZg+RVC{t)ZyS;=R2gcT=4b*l2bsnQ6RM zC3vq{H(>Ohx?+7~^wPgD{tH}QA^WtYNXEp8A*tB-E7ghj?RaU8#7_6O$sd#2h_ z$-zCvG;^?MrDR7F!mgEzvvU>vL&ps`HaEbU6!l^x5S5Jg$)&}V?huM zSs)SKI}L;g2u$5*$nFZ6Y<`b7&6kGtN;`2SlMH*UPV#gOL7`&*`LP<%1xZmLI zxMLp}M#J4jreg4L7(QK?N-l6@y3B|q>OZ7HFaCIEVZvr`mMGR3;FT9j;4qA$*Sj4L z9(!nPCX$H!LP*J8U#)IgtKr7o8(<{5#-l(hg2U|%-RpOEJ#-D5{WJleH4D3}*;lcU z+A`^`b(*qd?>{lKic&GRad9>r46`#v$tdhUdq$<%Se@hKX z*xW0#cx&pFaDdI|A4Ug_-p3}N$rcEN;=V`%$&zp?3M94V()t%<5@8B|{4!rZCKYjO z7RYMCKN!O>!ZHyUvj-+zAprIT2=E!;EWnmRY_7`Y>vIH3IX8*BXK|EF2hHKI@^S=u zGsCForOfXY(@hz$bW;W_-IM`KH)X)mO&PFsQwA*E^e-yICNGu*<=p@P002ovPDHLk FV1l%?>cId2 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2dfd9978259591546a31cfe361f64481efe1403c GIT binary patch literal 4627 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 literal 0 HcmV?d00001 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 84ac32ae7d989f82d5e46a60405adcc8279e8001..fcd69f01414172c16256afdedd23081d6977505f 100644 GIT binary patch delta 2146 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 8953cba09064923c5daf2d37e7c3c836ccdd794b..bb6e108858dc88da6c50fc4a3b0ff641baeb7502 100644 GIT binary patch literal 4921 zcmcgw=QA7*wAG{czJjccUZN+uSYh?3VG*6^zmRCFmn;%(_1+dsbXJcRqDNM)i=nOF|;vy1y^sKxhrkIK3tg6;ow_i;0QdS;2t(+%h;pe*`;<0LNQPVFc3Z2+3(r1FYl8oaXW z6W$q41uKbGQu%^+vg}7*48F#@#476^TwJ-t?aa; z_wNn~bIk1Qc^|_*xJ>SmdRW-6?S<{a*(9%2m4prGbpO8D`RD|)p{cV;CjOX*7IdBS z^i}o-Q)aU%;(MR*89(ltE60 zKPr_!yTh#bGJl@1?{8paBF#VNtE+}eha4$-Xdi44r0t{fIVZQeRjg@yfQ*i!9|
y@jWEz!Dl=b+(GfUiDWTQc) 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 diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 0467bf12aa4d28f374bb26596605a46dcbb3e7c8..2f7eba757a452b5b26d2869f0098fd8c2778be97 100644 GIT binary patch literal 5478 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 MhKi0d5@Z|oKdDvDNB{r; delta 1410 zcmV-|1%3MFDvAq`8Gi!+002YCyxsr+0P|2xR7L;)|5U~JDYo_jSDRDB`2GI>{Qds= z{r_0T`1}6fwc-8!#-TGX}_?g)CZq4{k!uZ_g@DrQdoW0kI zu+W69&uN^)W`CK&06mMNcYMVF00dG=L_t(|+U?wHQxh>12H+Dm+1+fh+IF>G0SjJM zkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJTkdTm&kdTm&kdTm&kdP`e zsgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>VI$fQI%^ugM`#6By?GeadWcu z0gy9!D`m!H>Bd!JW(@avE8`|5XX(0PN}!8K>`dkavs;rHL+wy96QGNT=S@#7%xtlm zIW!++@*2zm-Py#Zr`DzqsLm!b{iskFNULSqE9A>SqHem>o31A%XL>S_5?=;V_i_y+ z(xxXhnt#r-l1Y8_*h`r?8Tr|)(RAiO)4jQR`13X0mx07C&p@KBP_2s``KEhv^|*8c z$$_T(v6^1Ig=#R}sE{vjA?ErGDZGUsyoJuWdJMc7Nb1^KF)-u<7q zPy$=;)0>vuWuK2hQhswLf!9yg`88u&eBbR8uhod?Nw09AXH}-#qOLLxeT2%C;R)QQ$Za#qp~cM&YVmS4i-*Fpd!cC zBXc?(4wcg>sHmXGd^VdE<5QX{Kyz$;$sCPl(_*-P2Iw?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF34$0Z;QO!J zOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUCUoZo%k(yku QW&i*H07*qoM6N<$g1FkvM*si- diff --git a/lib/i18n/locale_en.i18n.json b/lib/i18n/locale_en.i18n.json index 0c78f47..6960f5d 100644 --- a/lib/i18n/locale_en.i18n.json +++ b/lib/i18n/locale_en.i18n.json @@ -2,10 +2,14 @@ "@@locale": "en", "common": { "buttons": { - "next": "Next" + "next": "Next", + "done": "Done" }, "placeholders": { "email": "Email" + }, + "inputs": { + "profileHint": "Profile Name" } }, "errors": { diff --git a/lib/i18n/locale_uk.i18n.json b/lib/i18n/locale_uk.i18n.json index 2d0049c..f8aa65f 100644 --- a/lib/i18n/locale_uk.i18n.json +++ b/lib/i18n/locale_uk.i18n.json @@ -2,10 +2,14 @@ "@@locale": "uk", "common": { "buttons": { - "next": "Далі" + "next": "Далі", + "done": "Done" }, "placeholders": { "email": "Емейл" + }, + "inputs": { + "profileHint": "Profile Name" } }, "errors": { diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 818bb6d..e598858 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 2 -/// Strings: 28 (14 per locale) +/// Strings: 32 (16 per locale) /// -/// Built on 2024-01-31 at 19:44 UTC +/// Built on 2024-02-03 at 15:42 UTC // coverage:ignore-file // ignore_for_file: type=lint @@ -162,6 +162,7 @@ class _StringsCommonEn { // Translations late final _StringsCommonButtonsEn buttons = _StringsCommonButtonsEn._(_root); late final _StringsCommonPlaceholdersEn placeholders = _StringsCommonPlaceholdersEn._(_root); + late final _StringsCommonInputsEn inputs = _StringsCommonInputsEn._(_root); } // Path: errors @@ -194,6 +195,7 @@ class _StringsCommonButtonsEn { // Translations String get next => 'Next'; + String get done => 'Done'; } // Path: common.placeholders @@ -206,6 +208,16 @@ class _StringsCommonPlaceholdersEn { String get email => 'Email'; } +// Path: common.inputs +class _StringsCommonInputsEn { + _StringsCommonInputsEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get profileHint => 'Profile Name'; +} + // Path: errors.validation class _StringsErrorsValidationEn { _StringsErrorsValidationEn._(this._root); @@ -292,6 +304,7 @@ class _StringsCommonUk implements _StringsCommonEn { // Translations @override late final _StringsCommonButtonsUk buttons = _StringsCommonButtonsUk._(_root); @override late final _StringsCommonPlaceholdersUk placeholders = _StringsCommonPlaceholdersUk._(_root); + @override late final _StringsCommonInputsUk inputs = _StringsCommonInputsUk._(_root); } // Path: errors @@ -324,6 +337,7 @@ class _StringsCommonButtonsUk implements _StringsCommonButtonsEn { // Translations @override String get next => 'Далі'; + @override String get done => 'Done'; } // Path: common.placeholders @@ -336,6 +350,16 @@ class _StringsCommonPlaceholdersUk implements _StringsCommonPlaceholdersEn { @override String get email => 'Емейл'; } +// Path: common.inputs +class _StringsCommonInputsUk implements _StringsCommonInputsEn { + _StringsCommonInputsUk._(this._root); + + @override final _StringsUk _root; // ignore: unused_field + + // Translations + @override String get profileHint => 'Profile Name'; +} + // Path: errors.validation class _StringsErrorsValidationUk implements _StringsErrorsValidationEn { _StringsErrorsValidationUk._(this._root); @@ -391,7 +415,9 @@ extension on Translations { dynamic _flatMapFunction(String path) { switch (path) { case 'common.buttons.next': return 'Next'; + case 'common.buttons.done': return 'Done'; case 'common.placeholders.email': return 'Email'; + case 'common.inputs.profileHint': return 'Profile Name'; case 'errors.validation.invalidEmail': return 'Please enter a valid email'; case 'screens.auth.title': return 'Authentication'; case 'screens.auth.description': return 'Please enter your credentials to login.'; @@ -413,7 +439,9 @@ extension on _StringsUk { dynamic _flatMapFunction(String path) { switch (path) { case 'common.buttons.next': return 'Далі'; + case 'common.buttons.done': return 'Done'; case 'common.placeholders.email': return 'Емейл'; + case 'common.inputs.profileHint': return 'Profile Name'; case 'errors.validation.invalidEmail': return 'Введіть коректний email'; case 'screens.auth.title': return 'Авторизація'; case 'screens.auth.description': return 'Введіть email для авторизації'; diff --git a/lib/src/presentation/features/auth/screens/auth_screen.dart b/lib/src/presentation/features/auth/screens/auth_screen.dart index 10924df..b542974 100644 --- a/lib/src/presentation/features/auth/screens/auth_screen.dart +++ b/lib/src/presentation/features/auth/screens/auth_screen.dart @@ -172,7 +172,7 @@ class _AuthScreenState extends State { } }, builder: (BuildContext context, AuthState state) { - return ElevatedButtonWidget( + return GButton( isDisabled: _authBloc.isButtonDisabled, isLoading: state.status == AuthStatus.loading, onPressed: () => _authBloc.add(AuthenticateByEmailEvent()), diff --git a/lib/src/presentation/features/auth/screens/verification_screen.dart b/lib/src/presentation/features/auth/screens/verification_screen.dart index d0abc45..d29bf39 100644 --- a/lib/src/presentation/features/auth/screens/verification_screen.dart +++ b/lib/src/presentation/features/auth/screens/verification_screen.dart @@ -153,10 +153,10 @@ class _VerificationScreenState extends State { ], ), const SizedBox(height: 36.0), - ElevatedButtonWidget( + GButton( buttonLabel: t.common.buttons.next, onPressed: () { - context.goNamed(AppRoutes.onboardingProfile); + context.pushNamed(AppRoutes.onboardingProfile); }), ]), ), diff --git a/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart b/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart index 8805007..5b1e43f 100644 --- a/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart +++ b/lib/src/presentation/features/onboarding/onboarding_profile_screen.dart @@ -1,18 +1,23 @@ import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:golub/i18n/strings.g.dart'; +import 'package:golub/src/presentation/navigation/app_router.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; -import 'package:golub/src/presentation/ui_kit/widgets/input/profile_upload_image.dart'; +import 'package:golub/src/presentation/ui_kit/ui.dart'; import 'package:golub/src/presentation/ui_kit/widgets/input/gtext_field.dart'; +import 'package:golub/src/presentation/ui_kit/widgets/input/profile_upload_image.dart'; class OnboardingProfileScreen extends StatelessWidget { const OnboardingProfileScreen({super.key}); @override Widget build(BuildContext context) { + final t = Translations.of(context); return Scaffold( appBar: AppBar( automaticallyImplyLeading: false, title: Text( - '', //s.onboardingProfileScreenTitle, + t.screens.onboardingProfile.title, style: Theme.of(context).textTheme.titleMedium?.copyWith( color: AppColors.baseBlack, ), @@ -30,16 +35,25 @@ class OnboardingProfileScreen extends StatelessWidget { top: 16.0, ), width: double.infinity, - child: const Column( + child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ - ProfileUploadImage(), - SizedBox(height: 32.0), + const ProfileUploadImage(), + const SizedBox(height: 32.0), GTextField( - hintText: 'Profile Name', + hintText: t.common.inputs.profileHint, fillColor: AppColors.baseGray2, ), + const SizedBox(height: 48.0), + GButton( + buttonLabel: t.common.buttons.done, + isDisabled: false, + onPressed: () { + print('Done'); + context.goNamed(AppRoutes.chats); + } + ), ], ), ), diff --git a/lib/src/presentation/ui_kit/ui.dart b/lib/src/presentation/ui_kit/ui.dart index dd2e64d..50cf4b9 100644 --- a/lib/src/presentation/ui_kit/ui.dart +++ b/lib/src/presentation/ui_kit/ui.dart @@ -2,4 +2,4 @@ library ui_kit; export 'theme/app_assets.dart'; export 'theme/app_sizes.dart'; -export 'widgets/buttons/elevated_button_widget.dart'; \ No newline at end of file +export 'widgets/buttons/gbutton.dart'; \ No newline at end of file diff --git a/lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart b/lib/src/presentation/ui_kit/widgets/buttons/gbutton.dart similarity index 96% rename from lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart rename to lib/src/presentation/ui_kit/widgets/buttons/gbutton.dart index ee32fc7..0acefc9 100644 --- a/lib/src/presentation/ui_kit/widgets/buttons/elevated_button_widget.dart +++ b/lib/src/presentation/ui_kit/widgets/buttons/gbutton.dart @@ -3,7 +3,7 @@ import 'package:golub/src/presentation/ui_kit/theme/app_colors.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_sizes.dart'; import 'package:golub/src/presentation/ui_kit/theme/app_styles.dart'; -class ElevatedButtonWidget extends StatelessWidget { +class GButton extends StatelessWidget { final double? width; final double? height; final double? borderRadius; @@ -12,7 +12,7 @@ class ElevatedButtonWidget extends StatelessWidget { final bool isLoading; final bool isDisabled; - const ElevatedButtonWidget({ + const GButton({ required this.onPressed, this.width, this.height, diff --git a/lib/src/presentation/ui_kit/widgets/widgets.dart b/lib/src/presentation/ui_kit/widgets/widgets.dart index 9c25cd4..1f6b86c 100644 --- a/lib/src/presentation/ui_kit/widgets/widgets.dart +++ b/lib/src/presentation/ui_kit/widgets/widgets.dart @@ -1 +1 @@ -export 'buttons/elevated_button_widget.dart'; \ No newline at end of file +export 'buttons/gbutton.dart'; \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 35aa2cc..a9aad08 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -137,6 +137,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev" + source: hosted + version: "0.4.1" clock: dependency: transitive description: @@ -254,6 +262,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + url: "https://pub.dev" + source: hosted + version: "0.13.1" flutter_lints: dependency: "direct dev" description: @@ -365,6 +381,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image: + dependency: transitive + description: + name: image + sha256: "49a0d4b0c12402853d3f227fe7c315601b238d126aa4caa5dbb2dcf99421aa4a" + url: "https://pub.dev" + source: hosted + version: "4.1.6" injectable: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 8e6d55f..df9db7b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,6 +36,7 @@ dev_dependencies: build_runner: 2.4.6 injectable_generator: 2.4.1 slang_build_runner: 3.28.0 + flutter_launcher_icons: 0.13.1 flutter: uses-material-design: true @@ -55,3 +56,8 @@ flutter: fonts: - asset: assets/fonts/roboto/roboto-regular.ttf +flutter_launcher_icons: + android: "launcher_icon" + ios: true + image_path: "assets/logo/logo.png" + remove_alpha_ios: true \ No newline at end of file