From cdec9980aff0129e0d1130bb31071251044f8e44 Mon Sep 17 00:00:00 2001 From: jahongireshonqulov Date: Tue, 28 Oct 2025 19:41:05 +0500 Subject: [PATCH] feat:login page done --- lib/core/di/injection_container.config.dart | 3 + lib/core/helpers/formatters.dart | 31 +++++ lib/core/router/app_routes.dart | 6 + .../services/request_handler_service.dart | 1 - lib/core/theme/app_colors.dart | 1 + lib/core/theme/app_theme.dart | 30 ++++- .../blocs/login_bloc/login_bloc.dart | 18 +++ .../blocs/login_bloc/login_event.dart | 8 ++ .../blocs/login_bloc/login_state.dart | 8 ++ .../pages/login_page/login_page.dart | 21 ++++ .../pages/login_page/widgets/login_body.dart | 112 ++++++++++++++++++ .../presentation/widgets/app_button.dart | 40 ++++--- .../widgets/app_text_form_field.dart | 99 ++++++++-------- .../pages/splash_page/splash_page.dart | 3 +- pubspec.lock | 8 ++ pubspec.yaml | 2 + 16 files changed, 324 insertions(+), 67 deletions(-) create mode 100644 lib/core/helpers/formatters.dart create mode 100644 lib/feature/auth/presentation/blocs/login_bloc/login_bloc.dart create mode 100644 lib/feature/auth/presentation/blocs/login_bloc/login_event.dart create mode 100644 lib/feature/auth/presentation/blocs/login_bloc/login_state.dart create mode 100644 lib/feature/auth/presentation/pages/login_page/login_page.dart create mode 100644 lib/feature/auth/presentation/pages/login_page/widgets/login_body.dart diff --git a/lib/core/di/injection_container.config.dart b/lib/core/di/injection_container.config.dart index 01c978e..97ab938 100644 --- a/lib/core/di/injection_container.config.dart +++ b/lib/core/di/injection_container.config.dart @@ -13,6 +13,8 @@ import 'package:dio/dio.dart' as _i361; import 'package:get_it/get_it.dart' as _i174; import 'package:injectable/injectable.dart' as _i526; +import '../../feature/auth/presentation/blocs/login_bloc/login_bloc.dart' + as _i1065; import '../../feature/basket/presentation/blocs/basket_bloc.dart' as _i728; import '../../feature/browse/presentation/blocs/browse_bloc/browse_bloc.dart' as _i991; @@ -40,6 +42,7 @@ extension GetItInjectableX on _i174.GetIt { final dioModule = _$DioModule(); gh.factory<_i1007.HomeBloc>(() => _i1007.HomeBloc()); gh.factory<_i728.BasketBloc>(() => _i728.BasketBloc()); + gh.factory<_i1065.LoginBloc>(() => _i1065.LoginBloc()); gh.factory<_i991.BrowseBloc>(() => _i991.BrowseBloc()); gh.factory<_i580.MainBloc>(() => _i580.MainBloc()); gh.factory<_i311.SplashBloc>(() => _i311.SplashBloc()); diff --git a/lib/core/helpers/formatters.dart b/lib/core/helpers/formatters.dart new file mode 100644 index 0000000..5ebf0f7 --- /dev/null +++ b/lib/core/helpers/formatters.dart @@ -0,0 +1,31 @@ +import 'package:mask_text_input_formatter/mask_text_input_formatter.dart'; + +abstract class Formatters { + static final phoneFormatter = MaskTextInputFormatter( + mask: '## ### ## ##', + filter: {"#": RegExp(r'[0-9]')}, + type: MaskAutoCompletionType.lazy, + ); + + static final cardFormatter = MaskTextInputFormatter( + mask: '#### #### #### ####', + filter: {"#": RegExp(r'[0-9]')}, + type: MaskAutoCompletionType.lazy, + ); + static final cardNumberFormatter = MaskTextInputFormatter( + mask: '#### #### #### ####', + filter: {"#": RegExp(r'[0-9]')}, + type: MaskAutoCompletionType.lazy, + ); + + static final cardExpirationDateFormatter = MaskTextInputFormatter( + mask: '##/##', + filter: {"#": RegExp(r'[0-9]')}, + type: MaskAutoCompletionType.lazy, + ); + static final moneyFormatter = MaskTextInputFormatter( + mask: '########', + filter: {"#": RegExp(r'[0-9]')}, + type: MaskAutoCompletionType.lazy, + ); +} diff --git a/lib/core/router/app_routes.dart b/lib/core/router/app_routes.dart index d5d5376..14a2e7c 100644 --- a/lib/core/router/app_routes.dart +++ b/lib/core/router/app_routes.dart @@ -1,4 +1,5 @@ import 'package:flutter/cupertino.dart'; +import 'package:food_delivery_client/feature/auth/presentation/pages/login_page/login_page.dart'; import 'package:food_delivery_client/feature/home/presentation/pages/restaurants_by_category_page/restaurants_by_category_page.dart'; import '../../food_delivery_client.dart'; @@ -12,6 +13,11 @@ class AppRoutes { initialLocation: Routes.splash, routes: [ GoRoute(path: Routes.splash, builder: (context, state) => SplashPage()), + GoRoute( + path: Routes.login, + pageBuilder: (context, state) => CupertinoPage(child: LoginPage()), + ), + GoRoute( path: Routes.main, pageBuilder: (context, state) => CupertinoPage(child: MainPage()), diff --git a/lib/core/services/request_handler_service.dart b/lib/core/services/request_handler_service.dart index 02dcdff..1907420 100644 --- a/lib/core/services/request_handler_service.dart +++ b/lib/core/services/request_handler_service.dart @@ -1,4 +1,3 @@ -import 'dart:developer'; import 'package:dartz/dartz.dart'; import 'package:dio/dio.dart'; diff --git a/lib/core/theme/app_colors.dart b/lib/core/theme/app_colors.dart index a3eb692..2f18847 100644 --- a/lib/core/theme/app_colors.dart +++ b/lib/core/theme/app_colors.dart @@ -24,5 +24,6 @@ abstract class AppColors { static const Color c05A357 = Color(0xFF05A357); static const Color cE8E8E8 = Color(0xFFE8E8E8); static const Color c660000 = Color(0x66000000); + static const Color c6A6E7F = Color(0xFF6A6E7F); } diff --git a/lib/core/theme/app_theme.dart b/lib/core/theme/app_theme.dart index b51a9fa..744a879 100644 --- a/lib/core/theme/app_theme.dart +++ b/lib/core/theme/app_theme.dart @@ -3,6 +3,18 @@ import '../../food_delivery_client.dart'; abstract class AppTheme { static ThemeData get lightTheme => ThemeData.light().copyWith( brightness: Brightness.light, + + // colorScheme: ColorScheme( + // brightness: Brightness.light, + // primary: AppColors.cFFFFFF, + // onPrimary:AppColors.cFFFFFF, + // secondary: AppColors.cFFFFFF, + // onSecondary: AppColors.cFFFFFF, + // error:AppColors.cRed, + // onError: AppColors.cRed, + // surface: AppColors.cFFFFFF, + // onSurface: AppColors.cFFFFFF, + // ), appBarTheme: AppBarTheme( elevation: 0, backgroundColor: AppColors.cFFFFFF, @@ -10,14 +22,28 @@ abstract class AppTheme { surfaceTintColor: AppColors.cTransparent, ), scaffoldBackgroundColor: AppColors.cFFFFFF, + + progressIndicatorTheme: ProgressIndicatorThemeData( + color: AppColors.cFFFFFF, + circularTrackColor: AppColors.cFFFFFF, + linearTrackColor: AppColors.cFFFFFF, + borderRadius: BorderRadiusGeometry.circular(30), + ), + + pageTransitionsTheme: PageTransitionsTheme( + builders: { + TargetPlatform.android: CupertinoPageTransitionsBuilder(), + TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), + }, + ), bottomNavigationBarTheme: BottomNavigationBarThemeData( - elevation:0, + elevation: 0, showSelectedLabels: true, showUnselectedLabels: true, backgroundColor: AppColors.cFFFFFF, type: BottomNavigationBarType.fixed, unselectedItemColor: AppColors.cB5B5B5, - selectedItemColor:AppColors.c000000, + selectedItemColor: AppColors.c000000, selectedLabelStyle: AppTextStyles.size10Regular.copyWith( color: Colors.red, ), diff --git a/lib/feature/auth/presentation/blocs/login_bloc/login_bloc.dart b/lib/feature/auth/presentation/blocs/login_bloc/login_bloc.dart new file mode 100644 index 0000000..4fb0d6d --- /dev/null +++ b/lib/feature/auth/presentation/blocs/login_bloc/login_bloc.dart @@ -0,0 +1,18 @@ +import 'package:food_delivery_client/food_delivery_client.dart'; + +part 'login_event.dart'; + +part 'login_state.dart'; + +part 'login_bloc.freezed.dart'; + +@injectable +class LoginBloc extends Bloc { + LoginBloc() : super(const LoginState()) { + on<_Login>(_onLogin); + } + + Future _onLogin(_Login event, Emitter emit) async { + emit(state.copyWith(status: RequestStatus.loading)); + } +} diff --git a/lib/feature/auth/presentation/blocs/login_bloc/login_event.dart b/lib/feature/auth/presentation/blocs/login_bloc/login_event.dart new file mode 100644 index 0000000..184525e --- /dev/null +++ b/lib/feature/auth/presentation/blocs/login_bloc/login_event.dart @@ -0,0 +1,8 @@ +part of 'login_bloc.dart'; + +@freezed +class LoginEvent with _$LoginEvent { + const factory LoginEvent.checked() = _Checked; + + const factory LoginEvent.login() = _Login; +} diff --git a/lib/feature/auth/presentation/blocs/login_bloc/login_state.dart b/lib/feature/auth/presentation/blocs/login_bloc/login_state.dart new file mode 100644 index 0000000..e13c125 --- /dev/null +++ b/lib/feature/auth/presentation/blocs/login_bloc/login_state.dart @@ -0,0 +1,8 @@ +part of 'login_bloc.dart'; + +@freezed +abstract class LoginState with _$LoginState { + const factory LoginState({ + @Default(RequestStatus.initial) RequestStatus status, + }) = _LoginState; +} diff --git a/lib/feature/auth/presentation/pages/login_page/login_page.dart b/lib/feature/auth/presentation/pages/login_page/login_page.dart new file mode 100644 index 0000000..06ac749 --- /dev/null +++ b/lib/feature/auth/presentation/pages/login_page/login_page.dart @@ -0,0 +1,21 @@ +import 'package:food_delivery_client/feature/auth/presentation/blocs/login_bloc/login_bloc.dart'; +import 'package:food_delivery_client/feature/auth/presentation/pages/login_page/widgets/login_body.dart'; + +import '../../../../../food_delivery_client.dart'; + +class LoginPage extends StatelessWidget { + const LoginPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => sl(), + child: WLayout( + child: Scaffold( + body: WLoginBody(), + ), + ), + ); + } +} + diff --git a/lib/feature/auth/presentation/pages/login_page/widgets/login_body.dart b/lib/feature/auth/presentation/pages/login_page/widgets/login_body.dart new file mode 100644 index 0000000..57c67ba --- /dev/null +++ b/lib/feature/auth/presentation/pages/login_page/widgets/login_body.dart @@ -0,0 +1,112 @@ +import 'package:flutter/cupertino.dart'; +import 'package:food_delivery_client/core/helpers/formatters.dart'; + +import '../../../../../../food_delivery_client.dart'; +import '../../../blocs/login_bloc/login_bloc.dart'; + +class WLoginBody extends StatefulWidget { + const WLoginBody({super.key}); + + @override + State createState() => _WLoginBodyState(); +} + +class _WLoginBodyState extends State { + late final TextEditingController _phoneController; + late final TextEditingController _passwordController; + final GlobalKey _formKey = GlobalKey(); + + @override + void initState() { + _phoneController = TextEditingController(); + _passwordController = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + _phoneController.dispose(); + _passwordController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Form( + key: _formKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 45.verticalSpace, + Align( + alignment: AlignmentGeometry.center, + child: Text("Let's go", style: AppTextStyles.size24Bold), + ), + 20.verticalSpace, + Text( + 'Phone number', + style: AppTextStyles.size14Regular.copyWith( + color: AppColors.c6A6E7F, + ), + ), + 5.verticalSpace, + AppTextFormField( + height: 50, + hintText: "Enter phone number", + prefixIcon: Text( + "+ 998", + style: AppTextStyles.size16Regular.copyWith(fontSize: 16), + ), + borderRadius: AppUtils.kBorderRadius8, + controller: _phoneController, + keyBoardType: TextInputType.number, + + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, + Formatters.phoneFormatter, + LengthLimitingTextInputFormatter(12), + ], + ), + 20.verticalSpace, + Text( + 'Password', + style: AppTextStyles.size14Regular.copyWith( + color: AppColors.c6A6E7F, + ), + ), + 5.verticalSpace, + AppTextFormField( + height: 50, + hintText: "Enter password", + keyBoardType: TextInputType.text, + borderRadius: AppUtils.kBorderRadius8, + controller: _passwordController, + ), + Align( + alignment: AlignmentGeometry.centerRight, + child: TextButton( + onPressed: () {}, + child: Text('Forgot password'), + ), + ), + + const Spacer(), + AppButton( + name: "Continue", + isLoading: state.status.isLoading(), + onPressed: () {}, + borderRadius: 15, + backgroundColor: AppColors.c34A853, + ), + 20.verticalSpace, + ], + ).paddingSymmetric(horizontal: 16), + ); + }, + ); + } +} diff --git a/lib/feature/common/presentation/widgets/app_button.dart b/lib/feature/common/presentation/widgets/app_button.dart index 6bb5f46..e343581 100644 --- a/lib/feature/common/presentation/widgets/app_button.dart +++ b/lib/feature/common/presentation/widgets/app_button.dart @@ -1,12 +1,10 @@ - import '../../../../food_delivery_client.dart'; - class AppButton extends StatelessWidget { const AppButton({ super.key, required this.name, - required this.onPressed, + this.onPressed, this.margin, this.backgroundColor, this.borderRadius, @@ -16,10 +14,11 @@ class AppButton extends StatelessWidget { this.action, this.trailing, this.mainAxisAlignment, + this.isLoading = false, }); final String name; - final VoidCallback onPressed; + final VoidCallback? onPressed; final EdgeInsets? margin; final Color? backgroundColor; final Color? textColor; @@ -28,6 +27,7 @@ class AppButton extends StatelessWidget { final double? height; final Widget? action; final Widget? trailing; + final bool isLoading; final MainAxisAlignment? mainAxisAlignment; @override @@ -45,16 +45,28 @@ class AppButton extends StatelessWidget { color: backgroundColor ?? AppColors.c000000, borderRadius: BorderRadius.circular(borderRadius ?? 0), ), - child: Row( - mainAxisAlignment: mainAxisAlignment ?? MainAxisAlignment.center, - children: [ - action ?? AppUtils.kSizedBox, - Text(name, style: AppTextStyles.size16Bold.copyWith( - color: AppColors.cFFFFFF - )), - trailing ?? AppUtils.kSizedBox, - ], - ), + child: isLoading + ? Center( + child: CircularProgressIndicator.adaptive( + padding: EdgeInsets.all(2), + valueColor: AlwaysStoppedAnimation(AppColors.cFFFFFF), + backgroundColor: AppColors.cFFFFFF, + ), + ) + : Row( + mainAxisAlignment: + mainAxisAlignment ?? MainAxisAlignment.center, + children: [ + action ?? AppUtils.kSizedBox, + Text( + name, + style: AppTextStyles.size16Bold.copyWith( + color: AppColors.cFFFFFF, + ), + ), + trailing ?? AppUtils.kSizedBox, + ], + ), ), ); } diff --git a/lib/feature/common/presentation/widgets/app_text_form_field.dart b/lib/feature/common/presentation/widgets/app_text_form_field.dart index 5a93406..7aa781a 100644 --- a/lib/feature/common/presentation/widgets/app_text_form_field.dart +++ b/lib/feature/common/presentation/widgets/app_text_form_field.dart @@ -17,7 +17,10 @@ class AppTextFormField extends StatelessWidget { this.obscureText = false, required this.controller, this.prefixIcon, - this.prefixSvgPath, this.focusNode, + this.prefixSvgPath, + this.focusNode, + this.borderRadius, + this.height, }); final int? maxLines; @@ -37,57 +40,57 @@ class AppTextFormField extends StatelessWidget { late final Widget? prefixIcon; final String? prefixSvgPath; final FocusNode? focusNode; + final BorderRadius? borderRadius; + final double? height; @override Widget build(BuildContext context) { - return TextFormField( - enabled: true, - autofocus: true, - maxLines: maxLines ?? 1, - minLines: minLines ?? 1, - onChanged: onChanged, - focusNode: focusNode, - inputFormatters: inputFormatters, - keyboardType: keyBoardType, - style: textStyle ?? AppTextStyles.size16Regular, - onTap: onTap, - textAlign: textAlign ?? TextAlign.start, - controller: controller, - validator: validator, - autovalidateMode: AutovalidateMode.onUserInteraction, - decoration: InputDecoration( + return SizedBox( + height: height ?? 44, + child: TextFormField( enabled: true, - filled: true, - fillColor: fillColor ?? AppColors.cEEEEEE, - hintText: hintText, - hintStyle: hintTextStyle, - prefixIconConstraints: BoxConstraints( - minHeight: 0, - maxHeight: 24, - maxWidth: 34, - minWidth: 0, - ), - prefixIcon: prefixIcon?.paddingOnly(left: 10).paddingAll(3), - contentPadding: EdgeInsets.symmetric(vertical: 12, horizontal: 12), - border: OutlineInputBorder( - borderRadius: AppUtils.kBorderRadius40, - borderSide: BorderSide.none, - ), - errorBorder: OutlineInputBorder( - borderRadius: AppUtils.kBorderRadius40, - borderSide: BorderSide.none, - ), - enabledBorder: OutlineInputBorder( - borderRadius: AppUtils.kBorderRadius40, - borderSide: BorderSide.none, - ), - focusedBorder: OutlineInputBorder( - borderRadius: AppUtils.kBorderRadius40, - borderSide: BorderSide.none, - ), - disabledBorder: OutlineInputBorder( - borderRadius: AppUtils.kBorderRadius40, - borderSide: BorderSide.none, + autofocus: true, + maxLines: maxLines ?? 1, + minLines: minLines ?? 1, + onChanged: onChanged, + focusNode: focusNode, + inputFormatters: inputFormatters, + keyboardType: keyBoardType, + style: textStyle ?? AppTextStyles.size16Regular, + onTap: onTap, + textAlign: textAlign ?? TextAlign.start, + controller: controller, + validator: validator, + autovalidateMode: AutovalidateMode.onUserInteraction, + decoration: InputDecoration( + enabled: true, + filled: true, + fillColor: fillColor ?? AppColors.cEEEEEE, + hintText: hintText, + hintStyle: hintTextStyle, + prefixIconConstraints: BoxConstraints(minHeight: 24, minWidth: 0), + prefixIcon: prefixIcon?.paddingOnly(left: 10).paddingAll(3), + contentPadding: EdgeInsets.symmetric(vertical: 12, horizontal: 12), + border: OutlineInputBorder( + borderRadius: borderRadius ?? AppUtils.kBorderRadius40, + borderSide: BorderSide.none, + ), + errorBorder: OutlineInputBorder( + borderRadius: borderRadius ?? AppUtils.kBorderRadius40, + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: borderRadius ?? AppUtils.kBorderRadius40, + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: borderRadius ?? AppUtils.kBorderRadius40, + borderSide: BorderSide.none, + ), + disabledBorder: OutlineInputBorder( + borderRadius: borderRadius ?? AppUtils.kBorderRadius40, + borderSide: BorderSide.none, + ), ), ), ); diff --git a/lib/feature/on_boarding/presentation/pages/splash_page/splash_page.dart b/lib/feature/on_boarding/presentation/pages/splash_page/splash_page.dart index 567c2fd..1936604 100644 --- a/lib/feature/on_boarding/presentation/pages/splash_page/splash_page.dart +++ b/lib/feature/on_boarding/presentation/pages/splash_page/splash_page.dart @@ -1,4 +1,3 @@ -import 'package:flutter_svg/flutter_svg.dart'; import 'package:food_delivery_client/feature/on_boarding/presentation/blocs/splash_bloc/splash_bloc.dart'; import '../../../../../food_delivery_client.dart'; @@ -13,7 +12,7 @@ class SplashPage extends StatelessWidget { child: BlocListener( listener: (context, state) { if (state.status.isLoaded()) { - context.go(Routes.main); + context.go(Routes.login); } }, child: Scaffold( diff --git a/pubspec.lock b/pubspec.lock index b02516c..15da394 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -477,6 +477,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + mask_text_input_formatter: + dependency: "direct main" + description: + name: mask_text_input_formatter + sha256: "978c58ec721c25621ceb468e633f4eef64b64d45424ac4540e0565d4f7c800cd" + url: "https://pub.dev" + source: hosted + version: "2.9.0" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 24c0f24..b24a690 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,8 @@ dependencies: carousel_slider: ^5.1.1 equatable: ^2.0.7 dartz: ^0.10.1 + mask_text_input_formatter: ^2.9.0 + #DI get_it: ^8.2.0