From 66e257861997f4d38dc4fcd88e888fd44ccad3f3 Mon Sep 17 00:00:00 2001 From: BLoC Date: Mon, 17 Feb 2025 12:32:09 +0300 Subject: [PATCH] feat: webview registration --- android/app/build.gradle | 2 +- lib/core/app_router.dart | 20 +- lib/core/locator_service.dart | 6 + .../auth/auth_screen/auth_screen.dart | 123 ---- lib/features/auth/const/strings.dart | 22 - .../auth/models/credentials_model.dart | 15 + .../models/credentials_model.freezed.dart | 184 +++++ .../auth/models/credentials_model.g.dart | 21 + lib/features/auth/models/token_model.dart | 14 + .../auth/models/token_model.freezed.dart | 164 +++++ lib/features/auth/models/token_model.g.dart | 17 + .../auth/presentation/bloc/auth_cubit.dart | 37 + .../auth/presentation/bloc/auth_state.dart | 18 + .../presentation/bloc/auth_state.freezed.dart | 632 ++++++++++++++++++ .../auth/presentation/pages/auth_screen.dart | 151 +++++ .../auth/repositories/auth_repository.dart | 25 + lib/features/auth/services/auth_service.dart | 16 + .../auth/services/auth_service.g.dart | 75 +++ .../pages/webview_registration_screen.dart | 45 ++ 19 files changed, 1429 insertions(+), 158 deletions(-) delete mode 100644 lib/features/auth/auth_screen/auth_screen.dart delete mode 100644 lib/features/auth/const/strings.dart create mode 100644 lib/features/auth/models/credentials_model.dart create mode 100644 lib/features/auth/models/credentials_model.freezed.dart create mode 100644 lib/features/auth/models/credentials_model.g.dart create mode 100644 lib/features/auth/models/token_model.dart create mode 100644 lib/features/auth/models/token_model.freezed.dart create mode 100644 lib/features/auth/models/token_model.g.dart create mode 100644 lib/features/auth/presentation/bloc/auth_cubit.dart create mode 100644 lib/features/auth/presentation/bloc/auth_state.dart create mode 100644 lib/features/auth/presentation/bloc/auth_state.freezed.dart create mode 100644 lib/features/auth/presentation/pages/auth_screen.dart create mode 100644 lib/features/auth/repositories/auth_repository.dart create mode 100644 lib/features/auth/services/auth_service.dart create mode 100644 lib/features/auth/services/auth_service.g.dart create mode 100644 lib/features/registration/presentation/pages/webview_registration_screen.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index a0e928b8..97f9b433 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -13,7 +13,7 @@ if (keystorePropertiesFile.exists()) { android { namespace = "com.eatvioapp.app" compileSdk = flutter.compileSdkVersion - ndkVersion = "27.0.12077973" + ndkVersion = "25.1.8937393" compileOptions { sourceCompatibility = JavaVersion.VERSION_21 diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 156d8566..f62fbef9 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -1,7 +1,7 @@ import 'package:eatvio_app/core/initialization_cubit/ws_manager.dart'; import 'package:eatvio_app/core/locator_service.dart'; import 'package:eatvio_app/core/token_storage.dart'; -import 'package:eatvio_app/features/auth/auth_screen/auth_screen.dart'; +import 'package:eatvio_app/features/auth/presentation/pages/auth_screen.dart'; import 'package:eatvio_app/features/blog_post/presentation/pages/blog_post_screen.dart'; import 'package:eatvio_app/features/blog_post_create/presentation/pages/blog_post_create_screen.dart'; import 'package:eatvio_app/features/bottom_navigation_bar/presentation/widgets/bottom_navigation_bar_widget.dart'; @@ -34,6 +34,7 @@ import 'package:eatvio_app/features/profile/presentation/pages/subs_screen.dart' import 'package:eatvio_app/features/recipes/presentation/pages/about_recipes_screen.dart'; import 'package:eatvio_app/features/recipes/presentation/pages/recipe_create_screen.dart'; import 'package:eatvio_app/features/registration/presentation/pages/registration_screen.dart'; +import 'package:eatvio_app/features/registration/presentation/pages/webview_registration_screen.dart'; import 'package:eatvio_app/features/settings/entities/settings_entity.dart'; import 'package:eatvio_app/features/settings/presentation/pages/change_email_screen.dart'; import 'package:eatvio_app/features/settings/presentation/pages/change_password_screen.dart'; @@ -64,18 +65,13 @@ class AppRouter { } return null; }, - builder: (context, state) { - final bool needLogout; - if (state.extra == null) { - needLogout = false; - } else { - needLogout = true; - } - return AuthScreen( - needLogout: needLogout, - ); - }, + builder: (context, state) => const AuthScreen(), routes: [ + GoRoute( + path: 'webView-registration-screen', + name: 'webViewRegistrationScreen', + builder: (context, state) => const WebViewRegistrationScreen(), + ), GoRoute( path: 'registration-screen', name: 'registrationScreen', diff --git a/lib/core/locator_service.dart b/lib/core/locator_service.dart index c0377a79..4dbfd90e 100644 --- a/lib/core/locator_service.dart +++ b/lib/core/locator_service.dart @@ -2,6 +2,9 @@ import 'package:dio/dio.dart'; import 'package:eatvio_app/core/const.dart'; import 'package:eatvio_app/core/initialization_cubit/ws_manager.dart'; import 'package:eatvio_app/core/initialization_cubit/ws_service.dart'; +import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit.dart'; +import 'package:eatvio_app/features/auth/repositories/auth_repository.dart'; +import 'package:eatvio_app/features/auth/services/auth_service.dart'; import 'package:eatvio_app/features/create_diet/presentation/cubit/create_diet_cubit.dart'; import 'package:eatvio_app/features/create_diet/repositories/create_diet_repository.dart'; import 'package:eatvio_app/features/create_diet/services/create_diet_service.dart'; @@ -104,6 +107,7 @@ Future init() async { sl.registerFactory(() => MyPlansCubit(myPlansRepository: sl())); sl.registerFactory(() => UserDetailsCubit(profileRepository: sl())); sl.registerFactory(() => ConsultantsCubit(feedRepository: sl())); + sl.registerFactory(() => AuthCubit(authRepository: sl())); // Repository sl.registerLazySingleton(() => ChatRepository(chatService: sl())); @@ -123,6 +127,7 @@ Future init() async { sl.registerLazySingleton(() => PlanRepository(planService: sl())); sl.registerLazySingleton(() => MyDietsRepository(myDietsService: sl())); sl.registerLazySingleton(() => MyPlansRepository(myPlansService: sl())); + sl.registerLazySingleton(() => AuthRepository(authService: sl())); // Services sl.registerLazySingleton(() => ChatService(sl())); @@ -143,6 +148,7 @@ Future init() async { sl.registerLazySingleton(() => MyDietsService(sl())); sl.registerLazySingleton(() => MyPlansService(sl())); sl.registerLazySingleton(() => WsService(sl())); + sl.registerLazySingleton(() => AuthService(sl())); // External sl.registerLazySingleton( diff --git a/lib/features/auth/auth_screen/auth_screen.dart b/lib/features/auth/auth_screen/auth_screen.dart deleted file mode 100644 index 9d1a45ab..00000000 --- a/lib/features/auth/auth_screen/auth_screen.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'dart:convert'; -import 'package:eatvio_app/core/const.dart'; -import 'package:eatvio_app/core/credentials_storage.dart'; -import 'package:eatvio_app/core/locator_service.dart'; -import 'package:eatvio_app/core/token_storage.dart'; -import 'package:eatvio_app/features/auth/const/strings.dart'; -import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_native_splash/flutter_native_splash.dart'; -import 'package:go_router/go_router.dart'; -import 'package:http/http.dart' as http; -import 'package:webview_flutter/webview_flutter.dart'; - -class AuthScreen extends StatefulWidget { - final bool needLogout; - - const AuthScreen({ - this.needLogout = false, - }); - - @override - State createState() => _AuthScreenState(); -} - -class _AuthScreenState extends State { - bool checkboxState = false; - bool hidePassword = true; - final baseUrlController = TextEditingController(); - late final WebViewController _controller; - - String email = ''; - String password = ''; - bool needLogout = false; - - Future logoutFromWeb() async { - await CredentialsStorage.removeCredentials(); - await _controller.runJavaScript(''' - document.querySelector('.header-list__link button[type="submit"]').click(); - '''); - } - - Future loginInApplication() async { - final credentialsFromStorage = await CredentialsStorage.getCredentialsFromStorage(); - final Map credentials = { - 'email': credentialsFromStorage['email']!, - 'password': credentialsFromStorage['password']!, - }; - final response = await http.post( - Uri.parse(baseUrl + '/api/login'), - headers: {'Content-Type': 'application/json'}, - body: json.encode(credentials), - encoding: utf8, - ); - var res = await jsonDecode(response.body); - final token = res['token']; - await TokenStorage.saveTokenInStorage(token); - await Future.delayed(Duration.zero, () => GoRouter.of(context).go('/my-profile-screen')); - await sl().getMyProfile(); - } - - @override - void initState() { - super.initState(); - needLogout = widget.needLogout; - - _controller = WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..addJavaScriptChannel( - 'FlutterChannel', - onMessageReceived: (JavaScriptMessage message) { - final data = jsonDecode(message.message); - email = data['email']; - password = data['password']; - }, - ) - ..setNavigationDelegate( - NavigationDelegate( - onPageFinished: (String url) async { - final bool tokenInStorage = await TokenStorage.haveTokenInStorage(); - final bool credentialsInStorage = await CredentialsStorage.haveCredentialsInStorage(); - - if (needLogout) { - await logoutFromWeb(); - needLogout = false; - } else if (!url.contains('profile') && !url.contains('registration')) { - await _controller.runJavaScript(jsChannelAuthScript); - } else if (url.contains('profile')) { - if (credentialsInStorage) { - await loginInApplication(); - } else { - if (email.isNotEmpty && password.isNotEmpty) { - await CredentialsStorage.saveCredentials(email, password); - await loginInApplication(); - } else { - await logoutFromWeb(); - } - } - } else { - if (tokenInStorage && credentialsInStorage) { - GoRouter.of(context).go('/my-profile-screen'); - } else if (!tokenInStorage && credentialsInStorage) { - await loginInApplication(); - } - } - }, - ), - ) - ..loadRequest(Uri.parse(baseUrl)); - } - - @override - Widget build(BuildContext context) { - FlutterNativeSplash.remove(); - return Scaffold( - appBar: AppBar( - toolbarHeight: 0, - ), - body: SafeArea( - child: WebViewWidget(controller: _controller), - ), - ); - } -} diff --git a/lib/features/auth/const/strings.dart b/lib/features/auth/const/strings.dart deleted file mode 100644 index 66d05c4d..00000000 --- a/lib/features/auth/const/strings.dart +++ /dev/null @@ -1,22 +0,0 @@ -const String jsChannelAuthScript = ''' - function initializeListeners() { - const emailInput = document.getElementById("inputEmail_main"); - const passwordInput = document.getElementById("inputPassword_main"); - if (emailInput && passwordInput) { - function sendToFlutter() { - const email = emailInput.value; - const password = passwordInput.value; - const data = JSON.stringify({ email: email, password: password }); - if (window.FlutterChannel) { - window.FlutterChannel.postMessage(data); - } - } - emailInput.addEventListener("input", sendToFlutter); - passwordInput.addEventListener("input", sendToFlutter); - } else { - console.error("Email or Password input not found."); - } - } - document.addEventListener("DOMContentLoaded", initializeListeners); - initializeListeners(); - '''; diff --git a/lib/features/auth/models/credentials_model.dart b/lib/features/auth/models/credentials_model.dart new file mode 100644 index 00000000..c5c02ca4 --- /dev/null +++ b/lib/features/auth/models/credentials_model.dart @@ -0,0 +1,15 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'credentials_model.freezed.dart'; + +part 'credentials_model.g.dart'; + +@freezed +class CredentialsModel with _$CredentialsModel { + const factory CredentialsModel({ + required String email, + required String password, + }) = _CredentialsModel; + + factory CredentialsModel.fromJson(Map json) => _$CredentialsModelFromJson(json); +} diff --git a/lib/features/auth/models/credentials_model.freezed.dart b/lib/features/auth/models/credentials_model.freezed.dart new file mode 100644 index 00000000..270a5a99 --- /dev/null +++ b/lib/features/auth/models/credentials_model.freezed.dart @@ -0,0 +1,184 @@ +// 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 'credentials_model.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#adding-getters-and-methods-to-our-models'); + +CredentialsModel _$CredentialsModelFromJson(Map json) { + return _CredentialsModel.fromJson(json); +} + +/// @nodoc +mixin _$CredentialsModel { + String get email => throw _privateConstructorUsedError; + String get password => throw _privateConstructorUsedError; + + /// Serializes this CredentialsModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of CredentialsModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CredentialsModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CredentialsModelCopyWith<$Res> { + factory $CredentialsModelCopyWith( + CredentialsModel value, $Res Function(CredentialsModel) then) = + _$CredentialsModelCopyWithImpl<$Res, CredentialsModel>; + @useResult + $Res call({String email, String password}); +} + +/// @nodoc +class _$CredentialsModelCopyWithImpl<$Res, $Val extends CredentialsModel> + implements $CredentialsModelCopyWith<$Res> { + _$CredentialsModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CredentialsModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? email = null, + Object? password = null, + }) { + return _then(_value.copyWith( + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + password: null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$CredentialsModelImplCopyWith<$Res> + implements $CredentialsModelCopyWith<$Res> { + factory _$$CredentialsModelImplCopyWith(_$CredentialsModelImpl value, + $Res Function(_$CredentialsModelImpl) then) = + __$$CredentialsModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String email, String password}); +} + +/// @nodoc +class __$$CredentialsModelImplCopyWithImpl<$Res> + extends _$CredentialsModelCopyWithImpl<$Res, _$CredentialsModelImpl> + implements _$$CredentialsModelImplCopyWith<$Res> { + __$$CredentialsModelImplCopyWithImpl(_$CredentialsModelImpl _value, + $Res Function(_$CredentialsModelImpl) _then) + : super(_value, _then); + + /// Create a copy of CredentialsModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? email = null, + Object? password = null, + }) { + return _then(_$CredentialsModelImpl( + email: null == email + ? _value.email + : email // ignore: cast_nullable_to_non_nullable + as String, + password: null == password + ? _value.password + : password // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$CredentialsModelImpl implements _CredentialsModel { + const _$CredentialsModelImpl({required this.email, required this.password}); + + factory _$CredentialsModelImpl.fromJson(Map json) => + _$$CredentialsModelImplFromJson(json); + + @override + final String email; + @override + final String password; + + @override + String toString() { + return 'CredentialsModel(email: $email, password: $password)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CredentialsModelImpl && + (identical(other.email, email) || other.email == email) && + (identical(other.password, password) || + other.password == password)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, email, password); + + /// Create a copy of CredentialsModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CredentialsModelImplCopyWith<_$CredentialsModelImpl> get copyWith => + __$$CredentialsModelImplCopyWithImpl<_$CredentialsModelImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$CredentialsModelImplToJson( + this, + ); + } +} + +abstract class _CredentialsModel implements CredentialsModel { + const factory _CredentialsModel( + {required final String email, + required final String password}) = _$CredentialsModelImpl; + + factory _CredentialsModel.fromJson(Map json) = + _$CredentialsModelImpl.fromJson; + + @override + String get email; + @override + String get password; + + /// Create a copy of CredentialsModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CredentialsModelImplCopyWith<_$CredentialsModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/auth/models/credentials_model.g.dart b/lib/features/auth/models/credentials_model.g.dart new file mode 100644 index 00000000..eb2c98e8 --- /dev/null +++ b/lib/features/auth/models/credentials_model.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'credentials_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$CredentialsModelImpl _$$CredentialsModelImplFromJson( + Map json) => + _$CredentialsModelImpl( + email: json['email'] as String, + password: json['password'] as String, + ); + +Map _$$CredentialsModelImplToJson( + _$CredentialsModelImpl instance) => + { + 'email': instance.email, + 'password': instance.password, + }; diff --git a/lib/features/auth/models/token_model.dart b/lib/features/auth/models/token_model.dart new file mode 100644 index 00000000..ca352260 --- /dev/null +++ b/lib/features/auth/models/token_model.dart @@ -0,0 +1,14 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'token_model.freezed.dart'; + +part 'token_model.g.dart'; + +@freezed +class TokenModel with _$TokenModel { + const factory TokenModel({ + required String token, + }) = _TokenModel; + + factory TokenModel.fromJson(Map json) => _$TokenModelFromJson(json); +} diff --git a/lib/features/auth/models/token_model.freezed.dart b/lib/features/auth/models/token_model.freezed.dart new file mode 100644 index 00000000..ec0210f4 --- /dev/null +++ b/lib/features/auth/models/token_model.freezed.dart @@ -0,0 +1,164 @@ +// 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 'token_model.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#adding-getters-and-methods-to-our-models'); + +TokenModel _$TokenModelFromJson(Map json) { + return _TokenModel.fromJson(json); +} + +/// @nodoc +mixin _$TokenModel { + String get token => throw _privateConstructorUsedError; + + /// Serializes this TokenModel to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of TokenModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $TokenModelCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TokenModelCopyWith<$Res> { + factory $TokenModelCopyWith( + TokenModel value, $Res Function(TokenModel) then) = + _$TokenModelCopyWithImpl<$Res, TokenModel>; + @useResult + $Res call({String token}); +} + +/// @nodoc +class _$TokenModelCopyWithImpl<$Res, $Val extends TokenModel> + implements $TokenModelCopyWith<$Res> { + _$TokenModelCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of TokenModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? token = null, + }) { + return _then(_value.copyWith( + token: null == token + ? _value.token + : token // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TokenModelImplCopyWith<$Res> + implements $TokenModelCopyWith<$Res> { + factory _$$TokenModelImplCopyWith( + _$TokenModelImpl value, $Res Function(_$TokenModelImpl) then) = + __$$TokenModelImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String token}); +} + +/// @nodoc +class __$$TokenModelImplCopyWithImpl<$Res> + extends _$TokenModelCopyWithImpl<$Res, _$TokenModelImpl> + implements _$$TokenModelImplCopyWith<$Res> { + __$$TokenModelImplCopyWithImpl( + _$TokenModelImpl _value, $Res Function(_$TokenModelImpl) _then) + : super(_value, _then); + + /// Create a copy of TokenModel + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? token = null, + }) { + return _then(_$TokenModelImpl( + token: null == token + ? _value.token + : token // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TokenModelImpl implements _TokenModel { + const _$TokenModelImpl({required this.token}); + + factory _$TokenModelImpl.fromJson(Map json) => + _$$TokenModelImplFromJson(json); + + @override + final String token; + + @override + String toString() { + return 'TokenModel(token: $token)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TokenModelImpl && + (identical(other.token, token) || other.token == token)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, token); + + /// Create a copy of TokenModel + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$TokenModelImplCopyWith<_$TokenModelImpl> get copyWith => + __$$TokenModelImplCopyWithImpl<_$TokenModelImpl>(this, _$identity); + + @override + Map toJson() { + return _$$TokenModelImplToJson( + this, + ); + } +} + +abstract class _TokenModel implements TokenModel { + const factory _TokenModel({required final String token}) = _$TokenModelImpl; + + factory _TokenModel.fromJson(Map json) = + _$TokenModelImpl.fromJson; + + @override + String get token; + + /// Create a copy of TokenModel + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$TokenModelImplCopyWith<_$TokenModelImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/auth/models/token_model.g.dart b/lib/features/auth/models/token_model.g.dart new file mode 100644 index 00000000..f2ea4bd9 --- /dev/null +++ b/lib/features/auth/models/token_model.g.dart @@ -0,0 +1,17 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'token_model.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$TokenModelImpl _$$TokenModelImplFromJson(Map json) => + _$TokenModelImpl( + token: json['token'] as String, + ); + +Map _$$TokenModelImplToJson(_$TokenModelImpl instance) => + { + 'token': instance.token, + }; diff --git a/lib/features/auth/presentation/bloc/auth_cubit.dart b/lib/features/auth/presentation/bloc/auth_cubit.dart new file mode 100644 index 00000000..84917344 --- /dev/null +++ b/lib/features/auth/presentation/bloc/auth_cubit.dart @@ -0,0 +1,37 @@ +import 'package:eatvio_app/core/app_router.dart'; +import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/core/token_storage.dart'; +import 'package:eatvio_app/features/auth/presentation/bloc/auth_state.dart'; +import 'package:eatvio_app/features/auth/repositories/auth_repository.dart'; +import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; + +class AuthCubit extends Cubit { + final AuthRepository authRepository; + + AuthCubit({ + required this.authRepository, + }) : super(AuthState()); + + getTokenByCredentials(String email, String password) async { + emit(AuthState.loading()); + try { + final String token = (await authRepository.getTokenByCredentials( + email, + password, + )) + .token; + emit( + AuthState.loaded( + token: token, + ), + ); + await TokenStorage.saveTokenInStorage(token); + await sl().getMyProfile(); + AppRouter.navigatorKey.currentContext?.go('/my-profile-screen'); + } catch (e) { + emit(AuthState.error(errorMessage: '$e')); + } + } +} diff --git a/lib/features/auth/presentation/bloc/auth_state.dart b/lib/features/auth/presentation/bloc/auth_state.dart new file mode 100644 index 00000000..994ec3a0 --- /dev/null +++ b/lib/features/auth/presentation/bloc/auth_state.dart @@ -0,0 +1,18 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'auth_state.freezed.dart'; + +@freezed +class AuthState with _$AuthState { + const factory AuthState() = _AuthState; + + const factory AuthState.loading() = AuthStateLoading; + + const factory AuthState.loaded({ + required String token, + }) = AuthStateLoaded; + + const factory AuthState.error({ + required String errorMessage, + }) = AuthStateError; +} diff --git a/lib/features/auth/presentation/bloc/auth_state.freezed.dart b/lib/features/auth/presentation/bloc/auth_state.freezed.dart new file mode 100644 index 00000000..c0be41a8 --- /dev/null +++ b/lib/features/auth/presentation/bloc/auth_state.freezed.dart @@ -0,0 +1,632 @@ +// 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_state.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#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$AuthState { + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(String token) loaded, + required TResult Function(String errorMessage) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(String token)? loaded, + TResult? Function(String errorMessage)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(String token)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map( + TResult Function(_AuthState value) $default, { + required TResult Function(AuthStateLoading value) loading, + required TResult Function(AuthStateLoaded value) loaded, + required TResult Function(AuthStateError value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_AuthState value)? $default, { + TResult? Function(AuthStateLoading value)? loading, + TResult? Function(AuthStateLoaded value)? loaded, + TResult? Function(AuthStateError value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap( + TResult Function(_AuthState value)? $default, { + TResult Function(AuthStateLoading value)? loading, + TResult Function(AuthStateLoaded value)? loaded, + TResult Function(AuthStateError value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AuthStateCopyWith<$Res> { + factory $AuthStateCopyWith(AuthState value, $Res Function(AuthState) then) = + _$AuthStateCopyWithImpl<$Res, AuthState>; +} + +/// @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; + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$AuthStateImplCopyWith<$Res> { + factory _$$AuthStateImplCopyWith( + _$AuthStateImpl value, $Res Function(_$AuthStateImpl) then) = + __$$AuthStateImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$AuthStateImplCopyWithImpl<$Res> + extends _$AuthStateCopyWithImpl<$Res, _$AuthStateImpl> + implements _$$AuthStateImplCopyWith<$Res> { + __$$AuthStateImplCopyWithImpl( + _$AuthStateImpl _value, $Res Function(_$AuthStateImpl) _then) + : super(_value, _then); + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$AuthStateImpl implements _AuthState { + const _$AuthStateImpl(); + + @override + String toString() { + return 'AuthState()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$AuthStateImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(String token) loaded, + required TResult Function(String errorMessage) error, + }) { + return $default(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(String token)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return $default?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(String token)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_AuthState value) $default, { + required TResult Function(AuthStateLoading value) loading, + required TResult Function(AuthStateLoaded value) loaded, + required TResult Function(AuthStateError value) error, + }) { + return $default(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_AuthState value)? $default, { + TResult? Function(AuthStateLoading value)? loading, + TResult? Function(AuthStateLoaded value)? loaded, + TResult? Function(AuthStateError value)? error, + }) { + return $default?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_AuthState value)? $default, { + TResult Function(AuthStateLoading value)? loading, + TResult Function(AuthStateLoaded value)? loaded, + TResult Function(AuthStateError value)? error, + required TResult orElse(), + }) { + if ($default != null) { + return $default(this); + } + return orElse(); + } +} + +abstract class _AuthState implements AuthState { + const factory _AuthState() = _$AuthStateImpl; +} + +/// @nodoc +abstract class _$$AuthStateLoadingImplCopyWith<$Res> { + factory _$$AuthStateLoadingImplCopyWith(_$AuthStateLoadingImpl value, + $Res Function(_$AuthStateLoadingImpl) then) = + __$$AuthStateLoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$AuthStateLoadingImplCopyWithImpl<$Res> + extends _$AuthStateCopyWithImpl<$Res, _$AuthStateLoadingImpl> + implements _$$AuthStateLoadingImplCopyWith<$Res> { + __$$AuthStateLoadingImplCopyWithImpl(_$AuthStateLoadingImpl _value, + $Res Function(_$AuthStateLoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$AuthStateLoadingImpl implements AuthStateLoading { + const _$AuthStateLoadingImpl(); + + @override + String toString() { + return 'AuthState.loading()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$AuthStateLoadingImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(String token) loaded, + required TResult Function(String errorMessage) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(String token)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(String token)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_AuthState value) $default, { + required TResult Function(AuthStateLoading value) loading, + required TResult Function(AuthStateLoaded value) loaded, + required TResult Function(AuthStateError value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_AuthState value)? $default, { + TResult? Function(AuthStateLoading value)? loading, + TResult? Function(AuthStateLoaded value)? loaded, + TResult? Function(AuthStateError value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_AuthState value)? $default, { + TResult Function(AuthStateLoading value)? loading, + TResult Function(AuthStateLoaded value)? loaded, + TResult Function(AuthStateError value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class AuthStateLoading implements AuthState { + const factory AuthStateLoading() = _$AuthStateLoadingImpl; +} + +/// @nodoc +abstract class _$$AuthStateLoadedImplCopyWith<$Res> { + factory _$$AuthStateLoadedImplCopyWith(_$AuthStateLoadedImpl value, + $Res Function(_$AuthStateLoadedImpl) then) = + __$$AuthStateLoadedImplCopyWithImpl<$Res>; + @useResult + $Res call({String token}); +} + +/// @nodoc +class __$$AuthStateLoadedImplCopyWithImpl<$Res> + extends _$AuthStateCopyWithImpl<$Res, _$AuthStateLoadedImpl> + implements _$$AuthStateLoadedImplCopyWith<$Res> { + __$$AuthStateLoadedImplCopyWithImpl( + _$AuthStateLoadedImpl _value, $Res Function(_$AuthStateLoadedImpl) _then) + : super(_value, _then); + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? token = null, + }) { + return _then(_$AuthStateLoadedImpl( + token: null == token + ? _value.token + : token // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$AuthStateLoadedImpl implements AuthStateLoaded { + const _$AuthStateLoadedImpl({required this.token}); + + @override + final String token; + + @override + String toString() { + return 'AuthState.loaded(token: $token)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AuthStateLoadedImpl && + (identical(other.token, token) || other.token == token)); + } + + @override + int get hashCode => Object.hash(runtimeType, token); + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AuthStateLoadedImplCopyWith<_$AuthStateLoadedImpl> get copyWith => + __$$AuthStateLoadedImplCopyWithImpl<_$AuthStateLoadedImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(String token) loaded, + required TResult Function(String errorMessage) error, + }) { + return loaded(token); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(String token)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return loaded?.call(token); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(String token)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(token); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_AuthState value) $default, { + required TResult Function(AuthStateLoading value) loading, + required TResult Function(AuthStateLoaded value) loaded, + required TResult Function(AuthStateError value) error, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_AuthState value)? $default, { + TResult? Function(AuthStateLoading value)? loading, + TResult? Function(AuthStateLoaded value)? loaded, + TResult? Function(AuthStateError value)? error, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_AuthState value)? $default, { + TResult Function(AuthStateLoading value)? loading, + TResult Function(AuthStateLoaded value)? loaded, + TResult Function(AuthStateError value)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class AuthStateLoaded implements AuthState { + const factory AuthStateLoaded({required final String token}) = + _$AuthStateLoadedImpl; + + String get token; + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AuthStateLoadedImplCopyWith<_$AuthStateLoadedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$AuthStateErrorImplCopyWith<$Res> { + factory _$$AuthStateErrorImplCopyWith(_$AuthStateErrorImpl value, + $Res Function(_$AuthStateErrorImpl) then) = + __$$AuthStateErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String errorMessage}); +} + +/// @nodoc +class __$$AuthStateErrorImplCopyWithImpl<$Res> + extends _$AuthStateCopyWithImpl<$Res, _$AuthStateErrorImpl> + implements _$$AuthStateErrorImplCopyWith<$Res> { + __$$AuthStateErrorImplCopyWithImpl( + _$AuthStateErrorImpl _value, $Res Function(_$AuthStateErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? errorMessage = null, + }) { + return _then(_$AuthStateErrorImpl( + errorMessage: null == errorMessage + ? _value.errorMessage + : errorMessage // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$AuthStateErrorImpl implements AuthStateError { + const _$AuthStateErrorImpl({required this.errorMessage}); + + @override + final String errorMessage; + + @override + String toString() { + return 'AuthState.error(errorMessage: $errorMessage)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AuthStateErrorImpl && + (identical(other.errorMessage, errorMessage) || + other.errorMessage == errorMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, errorMessage); + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AuthStateErrorImplCopyWith<_$AuthStateErrorImpl> get copyWith => + __$$AuthStateErrorImplCopyWithImpl<_$AuthStateErrorImpl>( + this, _$identity); + + @override + @optionalTypeArgs + TResult when( + TResult Function() $default, { + required TResult Function() loading, + required TResult Function(String token) loaded, + required TResult Function(String errorMessage) error, + }) { + return error(errorMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull( + TResult? Function()? $default, { + TResult? Function()? loading, + TResult? Function(String token)? loaded, + TResult? Function(String errorMessage)? error, + }) { + return error?.call(errorMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen( + TResult Function()? $default, { + TResult Function()? loading, + TResult Function(String token)? loaded, + TResult Function(String errorMessage)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(errorMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map( + TResult Function(_AuthState value) $default, { + required TResult Function(AuthStateLoading value) loading, + required TResult Function(AuthStateLoaded value) loaded, + required TResult Function(AuthStateError value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull( + TResult? Function(_AuthState value)? $default, { + TResult? Function(AuthStateLoading value)? loading, + TResult? Function(AuthStateLoaded value)? loaded, + TResult? Function(AuthStateError value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap( + TResult Function(_AuthState value)? $default, { + TResult Function(AuthStateLoading value)? loading, + TResult Function(AuthStateLoaded value)? loaded, + TResult Function(AuthStateError value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class AuthStateError implements AuthState { + const factory AuthStateError({required final String errorMessage}) = + _$AuthStateErrorImpl; + + String get errorMessage; + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AuthStateErrorImplCopyWith<_$AuthStateErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/auth/presentation/pages/auth_screen.dart b/lib/features/auth/presentation/pages/auth_screen.dart new file mode 100644 index 00000000..9a43569d --- /dev/null +++ b/lib/features/auth/presentation/pages/auth_screen.dart @@ -0,0 +1,151 @@ +import 'package:eatvio_app/core/const.dart'; +import 'package:eatvio_app/core/locator_service.dart'; +import 'package:eatvio_app/core/theme/theme_helper.dart'; +import 'package:eatvio_app/core/utils/image_constant.dart'; +import 'package:eatvio_app/core/widgets/custom_text_form_field.dart'; +import 'package:eatvio_app/features/auth/presentation/bloc/auth_cubit.dart'; +import 'package:eatvio_app/generated/l10n.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_native_splash/flutter_native_splash.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class AuthScreen extends StatefulWidget { + const AuthScreen(); + + @override + State createState() => _AuthScreenState(); +} + +class _AuthScreenState extends State { + final emailController = TextEditingController(); + final passwordController = TextEditingController(); + bool checkboxState = false; + bool hidePassword = true; + + @override + Widget build(BuildContext context) { + final s = S.of(context); + FlutterNativeSplash.remove(); + return BlocProvider( + create: (BuildContext context) => sl(), + child: Scaffold( + appBar: AppBar( + centerTitle: true, + title: SvgPicture.asset( + ImageConstant.imgLogoEatvio, + height: 48, + fit: BoxFit.contain, + ), + ), + body: SafeArea( + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(s.email), + const SizedBox(height: 4), + CustomTextFormField( + controller: emailController, + textInputAction: TextInputAction.next, + keyboardType: TextInputType.emailAddress, + textInputType: TextInputType.emailAddress, + hintText: 'Введите email', + ), + const SizedBox(height: 16), + Text(s.password), + const SizedBox(height: 4), + CustomTextFormField( + obscureText: hidePassword, + controller: passwordController, + hintText: 'Введите пароль', + suffix: IconButton( + onPressed: () { + setState(() { + hidePassword = !hidePassword; + }); + }, + icon: Icon( + hidePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined, + color: Colors.grey, + ), + ), + ), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Нет аккаунта? '), + GestureDetector( + onTap: () => GoRouter.of(context).goNamed('webViewRegistrationScreen'), + child: Text( + 'Зарегистрироваться', + style: TextStyle( + color: appTheme.blueA400, + decorationColor: appTheme.blueA400, + decoration: TextDecoration.underline, + ), + ), + ), + ], + ), + Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Нажимая "Войти", Вы соглашаетесь с ', + style: TextStyle( + fontSize: 12, + ), + ), + GestureDetector( + onTap: () async { + await launchUrl(Uri.parse(baseUrl + '/oferta_bs.pdf')); + }, + child: Text( + 'Публичной офертой', + style: TextStyle( + fontSize: 12, + color: appTheme.blueA400, + decorationColor: appTheme.blueA400, + decoration: TextDecoration.underline, + ), + ), + ), + ], + ), + const SizedBox(height: 16), + Container( + height: 48, + width: MediaQuery.of(context).size.width - 32, + child: FloatingActionButton.extended( + heroTag: 'authButton', + highlightElevation: 0, + elevation: 0, + focusElevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + backgroundColor: appTheme.blueA400, + onPressed: () => context.read().getTokenByCredentials( + emailController.text, + passwordController.text, + ), + label: Text( + 'Войти', + style: theme.textTheme.titleSmall!.copyWith(color: Colors.white), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/features/auth/repositories/auth_repository.dart b/lib/features/auth/repositories/auth_repository.dart new file mode 100644 index 00000000..d0bfcce5 --- /dev/null +++ b/lib/features/auth/repositories/auth_repository.dart @@ -0,0 +1,25 @@ +import 'package:eatvio_app/features/auth/models/credentials_model.dart'; +import 'package:eatvio_app/features/auth/models/token_model.dart'; +import 'package:eatvio_app/features/auth/services/auth_service.dart'; + +class AuthRepository { + final AuthService authService; + + AuthRepository({required this.authService}); + + Future getTokenByCredentials( + String email, + String password, + ) async { + try { + return await authService.getTokenByCredentials( + credentials: CredentialsModel( + email: email, + password: password, + ), + ); + } catch (e) { + rethrow; + } + } +} diff --git a/lib/features/auth/services/auth_service.dart b/lib/features/auth/services/auth_service.dart new file mode 100644 index 00000000..89a2d49a --- /dev/null +++ b/lib/features/auth/services/auth_service.dart @@ -0,0 +1,16 @@ +import 'package:dio/dio.dart'; +import 'package:eatvio_app/features/auth/models/credentials_model.dart'; +import 'package:eatvio_app/features/auth/models/token_model.dart'; +import 'package:retrofit/retrofit.dart'; + +part 'auth_service.g.dart'; + +@RestApi() +abstract class AuthService { + factory AuthService(Dio dio, {String? baseUrl}) = _AuthService; + + @GET('/api/login') + Future getTokenByCredentials({ + @Body() required CredentialsModel credentials, + }); +} diff --git a/lib/features/auth/services/auth_service.g.dart b/lib/features/auth/services/auth_service.g.dart new file mode 100644 index 00000000..d74516a4 --- /dev/null +++ b/lib/features/auth/services/auth_service.g.dart @@ -0,0 +1,75 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'auth_service.dart'; + +// ************************************************************************** +// RetrofitGenerator +// ************************************************************************** + +// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element,unnecessary_string_interpolations + +class _AuthService implements AuthService { + _AuthService(this._dio, {this.baseUrl, this.errorLogger}); + + final Dio _dio; + + String? baseUrl; + + final ParseErrorLogger? errorLogger; + + @override + Future getTokenByCredentials({ + required CredentialsModel credentials, + }) async { + final _extra = {}; + final queryParameters = {}; + final _headers = {}; + final _data = credentials; + final _options = _setStreamType( + Options(method: 'GET', headers: _headers, extra: _extra) + .compose( + _dio.options, + '/api/login', + queryParameters: queryParameters, + data: _data, + ) + .copyWith(baseUrl: _combineBaseUrls(_dio.options.baseUrl, baseUrl)), + ); + final _result = await _dio.fetch>(_options); + late TokenModel _value; + try { + _value = TokenModel.fromJson(_result.data!); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + + RequestOptions _setStreamType(RequestOptions requestOptions) { + if (T != dynamic && + !(requestOptions.responseType == ResponseType.bytes || + requestOptions.responseType == ResponseType.stream)) { + if (T == String) { + requestOptions.responseType = ResponseType.plain; + } else { + requestOptions.responseType = ResponseType.json; + } + } + return requestOptions; + } + + String _combineBaseUrls(String dioBaseUrl, String? baseUrl) { + if (baseUrl == null || baseUrl.trim().isEmpty) { + return dioBaseUrl; + } + + final url = Uri.parse(baseUrl); + + if (url.isAbsolute) { + return url.toString(); + } + + return Uri.parse(dioBaseUrl).resolveUri(url).toString(); + } +} diff --git a/lib/features/registration/presentation/pages/webview_registration_screen.dart b/lib/features/registration/presentation/pages/webview_registration_screen.dart new file mode 100644 index 00000000..5292ba61 --- /dev/null +++ b/lib/features/registration/presentation/pages/webview_registration_screen.dart @@ -0,0 +1,45 @@ +import 'package:eatvio_app/core/const.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class WebViewRegistrationScreen extends StatefulWidget { + const WebViewRegistrationScreen(); + + @override + State createState() => _WebViewRegistrationScreenState(); +} + +class _WebViewRegistrationScreenState extends State { + late final WebViewController _controller; + + @override + void initState() { + super.initState(); + _controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setNavigationDelegate( + NavigationDelegate( + onNavigationRequest: (NavigationRequest request) { + if (request.url.contains('profile')) { + GoRouter.of(context).go('authScreen'); + } + return NavigationDecision.navigate; + }, + ), + ) + ..loadRequest(Uri.parse(baseUrl + '/registration/step1')); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + toolbarHeight: 0, + ), + body: SafeArea( + child: WebViewWidget(controller: _controller), + ), + ); + } +}