first commit
This commit is contained in:
43
lib/screens/auth/auth_screen.dart
Normal file
43
lib/screens/auth/auth_screen.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:remever/common/resources.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:remever/screens/auth/cubit/auth_cubit.dart';
|
||||
import 'package:remever/screens/auth/screens/code_auth.dart';
|
||||
import 'package:remever/screens/auth/screens/email_auth.dart';
|
||||
import 'package:remever/screens/auth/screens/initial_auth.dart';
|
||||
|
||||
@RoutePage()
|
||||
class AuthScreen extends StatelessWidget {
|
||||
const AuthScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<AuthCubit>(
|
||||
create: (BuildContext context) => AuthCubit(),
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
backgroundColor: AppColors.bg,
|
||||
body: SafeArea(child: _buildMain()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
/// Построение основного блока
|
||||
///
|
||||
Widget _buildMain() {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: BlocBuilder<AuthCubit, AuthState>(
|
||||
builder:
|
||||
(BuildContext context, AuthState state) => state.when(
|
||||
initial: () => InitialAuth(),
|
||||
email: () => EmailAuth(),
|
||||
code: (email, uuid) => CodeAuth(email: email, uuid: uuid),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
46
lib/screens/auth/cubit/auth_cubit.dart
Normal file
46
lib/screens/auth/cubit/auth_cubit.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:remever/common/functions.dart';
|
||||
import 'package:remever/inject.dart';
|
||||
import 'package:remever/router.gr.dart';
|
||||
import 'package:remever/services/auth_interface.dart';
|
||||
|
||||
part 'auth_state.dart';
|
||||
part 'auth_cubit.freezed.dart';
|
||||
|
||||
class AuthCubit extends Cubit<AuthState> {
|
||||
AuthCubit() : super(AuthState.initial());
|
||||
|
||||
final AuthInterface _authInterface = getIt<AuthInterface>();
|
||||
|
||||
Future<void> toInitialState() async {
|
||||
emit(AuthState.initial());
|
||||
}
|
||||
|
||||
Future<void> toEmailState() async {
|
||||
emit(AuthState.email());
|
||||
}
|
||||
|
||||
Future<void> toCodeState(String email, String uuid) async {
|
||||
emit(AuthState.code(email, uuid));
|
||||
}
|
||||
|
||||
/// Авторизация
|
||||
Future<void> login(String email) async {
|
||||
final String? uuid = await _authInterface.login(email);
|
||||
|
||||
if (uuid == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
toCodeState(email, uuid);
|
||||
}
|
||||
|
||||
Future<void> sendCode(String code, String uid) async {
|
||||
final bool res = await _authInterface.sendCode(code, uid);
|
||||
|
||||
if (!res) toInitialState();
|
||||
|
||||
globalRouter.replace(CollectionRoute());
|
||||
}
|
||||
}
|
||||
458
lib/screens/auth/cubit/auth_cubit.freezed.dart
Normal file
458
lib/screens/auth/cubit/auth_cubit.freezed.dart
Normal file
@@ -0,0 +1,458 @@
|
||||
// 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_cubit.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(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 extends Object?>({
|
||||
required TResult Function() initial,
|
||||
required TResult Function() email,
|
||||
required TResult Function(String email, String uuid) code,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? initial,
|
||||
TResult? Function()? email,
|
||||
TResult? Function(String email, String uuid)? code,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? initial,
|
||||
TResult Function()? email,
|
||||
TResult Function(String email, String uuid)? code,
|
||||
required TResult orElse(),
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Initial value) initial,
|
||||
required TResult Function(_Email value) email,
|
||||
required TResult Function(_Code value) code,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Initial value)? initial,
|
||||
TResult? Function(_Email value)? email,
|
||||
TResult? Function(_Code value)? code,
|
||||
}) => throw _privateConstructorUsedError;
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Initial value)? initial,
|
||||
TResult Function(_Email value)? email,
|
||||
TResult Function(_Code value)? code,
|
||||
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 _$$InitialImplCopyWith<$Res> {
|
||||
factory _$$InitialImplCopyWith(
|
||||
_$InitialImpl value,
|
||||
$Res Function(_$InitialImpl) then,
|
||||
) = __$$InitialImplCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$InitialImplCopyWithImpl<$Res>
|
||||
extends _$AuthStateCopyWithImpl<$Res, _$InitialImpl>
|
||||
implements _$$InitialImplCopyWith<$Res> {
|
||||
__$$InitialImplCopyWithImpl(
|
||||
_$InitialImpl _value,
|
||||
$Res Function(_$InitialImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of AuthState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$InitialImpl implements _Initial {
|
||||
const _$InitialImpl();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AuthState.initial()';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType && other is _$InitialImpl);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function() initial,
|
||||
required TResult Function() email,
|
||||
required TResult Function(String email, String uuid) code,
|
||||
}) {
|
||||
return initial();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? initial,
|
||||
TResult? Function()? email,
|
||||
TResult? Function(String email, String uuid)? code,
|
||||
}) {
|
||||
return initial?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? initial,
|
||||
TResult Function()? email,
|
||||
TResult Function(String email, String uuid)? code,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (initial != null) {
|
||||
return initial();
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Initial value) initial,
|
||||
required TResult Function(_Email value) email,
|
||||
required TResult Function(_Code value) code,
|
||||
}) {
|
||||
return initial(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Initial value)? initial,
|
||||
TResult? Function(_Email value)? email,
|
||||
TResult? Function(_Code value)? code,
|
||||
}) {
|
||||
return initial?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Initial value)? initial,
|
||||
TResult Function(_Email value)? email,
|
||||
TResult Function(_Code value)? code,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (initial != null) {
|
||||
return initial(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Initial implements AuthState {
|
||||
const factory _Initial() = _$InitialImpl;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$EmailImplCopyWith<$Res> {
|
||||
factory _$$EmailImplCopyWith(
|
||||
_$EmailImpl value,
|
||||
$Res Function(_$EmailImpl) then,
|
||||
) = __$$EmailImplCopyWithImpl<$Res>;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$EmailImplCopyWithImpl<$Res>
|
||||
extends _$AuthStateCopyWithImpl<$Res, _$EmailImpl>
|
||||
implements _$$EmailImplCopyWith<$Res> {
|
||||
__$$EmailImplCopyWithImpl(
|
||||
_$EmailImpl _value,
|
||||
$Res Function(_$EmailImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of AuthState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$EmailImpl implements _Email {
|
||||
const _$EmailImpl();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AuthState.email()';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType && other is _$EmailImpl);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => runtimeType.hashCode;
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function() initial,
|
||||
required TResult Function() email,
|
||||
required TResult Function(String email, String uuid) code,
|
||||
}) {
|
||||
return email();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? initial,
|
||||
TResult? Function()? email,
|
||||
TResult? Function(String email, String uuid)? code,
|
||||
}) {
|
||||
return email?.call();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? initial,
|
||||
TResult Function()? email,
|
||||
TResult Function(String email, String uuid)? code,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (email != null) {
|
||||
return email();
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Initial value) initial,
|
||||
required TResult Function(_Email value) email,
|
||||
required TResult Function(_Code value) code,
|
||||
}) {
|
||||
return email(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Initial value)? initial,
|
||||
TResult? Function(_Email value)? email,
|
||||
TResult? Function(_Code value)? code,
|
||||
}) {
|
||||
return email?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Initial value)? initial,
|
||||
TResult Function(_Email value)? email,
|
||||
TResult Function(_Code value)? code,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (email != null) {
|
||||
return email(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Email implements AuthState {
|
||||
const factory _Email() = _$EmailImpl;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$CodeImplCopyWith<$Res> {
|
||||
factory _$$CodeImplCopyWith(
|
||||
_$CodeImpl value,
|
||||
$Res Function(_$CodeImpl) then,
|
||||
) = __$$CodeImplCopyWithImpl<$Res>;
|
||||
@useResult
|
||||
$Res call({String email, String uuid});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$CodeImplCopyWithImpl<$Res>
|
||||
extends _$AuthStateCopyWithImpl<$Res, _$CodeImpl>
|
||||
implements _$$CodeImplCopyWith<$Res> {
|
||||
__$$CodeImplCopyWithImpl(_$CodeImpl _value, $Res Function(_$CodeImpl) _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? email = null, Object? uuid = null}) {
|
||||
return _then(
|
||||
_$CodeImpl(
|
||||
null == email
|
||||
? _value.email
|
||||
: email // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
null == uuid
|
||||
? _value.uuid
|
||||
: uuid // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
class _$CodeImpl implements _Code {
|
||||
const _$CodeImpl(this.email, this.uuid);
|
||||
|
||||
@override
|
||||
final String email;
|
||||
@override
|
||||
final String uuid;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AuthState.code(email: $email, uuid: $uuid)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$CodeImpl &&
|
||||
(identical(other.email, email) || other.email == email) &&
|
||||
(identical(other.uuid, uuid) || other.uuid == uuid));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType, email, uuid);
|
||||
|
||||
/// 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')
|
||||
_$$CodeImplCopyWith<_$CodeImpl> get copyWith =>
|
||||
__$$CodeImplCopyWithImpl<_$CodeImpl>(this, _$identity);
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult when<TResult extends Object?>({
|
||||
required TResult Function() initial,
|
||||
required TResult Function() email,
|
||||
required TResult Function(String email, String uuid) code,
|
||||
}) {
|
||||
return code(this.email, uuid);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? whenOrNull<TResult extends Object?>({
|
||||
TResult? Function()? initial,
|
||||
TResult? Function()? email,
|
||||
TResult? Function(String email, String uuid)? code,
|
||||
}) {
|
||||
return code?.call(this.email, uuid);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeWhen<TResult extends Object?>({
|
||||
TResult Function()? initial,
|
||||
TResult Function()? email,
|
||||
TResult Function(String email, String uuid)? code,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (code != null) {
|
||||
return code(this.email, uuid);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult map<TResult extends Object?>({
|
||||
required TResult Function(_Initial value) initial,
|
||||
required TResult Function(_Email value) email,
|
||||
required TResult Function(_Code value) code,
|
||||
}) {
|
||||
return code(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult? mapOrNull<TResult extends Object?>({
|
||||
TResult? Function(_Initial value)? initial,
|
||||
TResult? Function(_Email value)? email,
|
||||
TResult? Function(_Code value)? code,
|
||||
}) {
|
||||
return code?.call(this);
|
||||
}
|
||||
|
||||
@override
|
||||
@optionalTypeArgs
|
||||
TResult maybeMap<TResult extends Object?>({
|
||||
TResult Function(_Initial value)? initial,
|
||||
TResult Function(_Email value)? email,
|
||||
TResult Function(_Code value)? code,
|
||||
required TResult orElse(),
|
||||
}) {
|
||||
if (code != null) {
|
||||
return code(this);
|
||||
}
|
||||
return orElse();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Code implements AuthState {
|
||||
const factory _Code(final String email, final String uuid) = _$CodeImpl;
|
||||
|
||||
String get email;
|
||||
String get uuid;
|
||||
|
||||
/// Create a copy of AuthState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$CodeImplCopyWith<_$CodeImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
8
lib/screens/auth/cubit/auth_state.dart
Normal file
8
lib/screens/auth/cubit/auth_state.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
part of 'auth_cubit.dart';
|
||||
|
||||
@freezed
|
||||
class AuthState with _$AuthState {
|
||||
const factory AuthState.initial() = _Initial;
|
||||
const factory AuthState.email() = _Email;
|
||||
const factory AuthState.code(String email, String uuid) = _Code;
|
||||
}
|
||||
139
lib/screens/auth/screens/code_auth.dart
Normal file
139
lib/screens/auth/screens/code_auth.dart
Normal file
@@ -0,0 +1,139 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:remever/common/resources.dart';
|
||||
import 'package:remever/common/widgets/loose_focus.dart';
|
||||
import 'package:remever/common/widgets/typography.dart';
|
||||
import 'package:remever/common/widgets/wspace.dart';
|
||||
import 'package:remever/components/extensions/context.dart';
|
||||
import 'package:remever/gen/assets.gen.dart';
|
||||
import 'package:remever/screens/auth/cubit/auth_cubit.dart';
|
||||
import 'package:remever/screens/auth/widgets/pin_code.dart';
|
||||
import 'package:remever/screens/auth/widgets/timer.dart';
|
||||
import 'package:remever/widgets/primary_button.dart';
|
||||
|
||||
///
|
||||
/// Отрисовка экрана ввода кода подтверждения
|
||||
///
|
||||
class CodeAuth extends StatefulWidget {
|
||||
const CodeAuth({super.key, required this.email, required this.uuid});
|
||||
final String email;
|
||||
final String uuid;
|
||||
|
||||
@override
|
||||
CodeAuthState createState() => CodeAuthState();
|
||||
}
|
||||
|
||||
class CodeAuthState extends State<CodeAuth> {
|
||||
final TextEditingController _pinController = TextEditingController();
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
|
||||
// Константы для стилей и отступов
|
||||
static final EdgeInsetsGeometry _padding = EdgeInsets.all(16).r;
|
||||
static final _regular14 = Regular14px();
|
||||
static final _medium14 = Medium14px();
|
||||
|
||||
void _onEnterTap() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
await context.read<AuthCubit>().sendCode(_pinController.text, widget.uuid);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LooseFocus(
|
||||
child: Scaffold(
|
||||
backgroundColor: AppColors.bg,
|
||||
body: Padding(
|
||||
padding: _padding,
|
||||
child: SingleChildScrollView(
|
||||
physics: BouncingScrollPhysics(),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
HSpace(148),
|
||||
_buildLogo(),
|
||||
const HSpace(60),
|
||||
_buildEmailField(),
|
||||
const HSpace(24),
|
||||
_buildInstructions(),
|
||||
HSpace(16),
|
||||
_buildPinCode(),
|
||||
HSpace(12),
|
||||
_buildResendTimer(),
|
||||
HSpace(50),
|
||||
_buildLoginButton(),
|
||||
const HSpace(104),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLogo() {
|
||||
return Assets.images.logo.image(width: 308.w, height: 100.h);
|
||||
}
|
||||
|
||||
Widget _buildEmailField() {
|
||||
return Container(
|
||||
height: 48.h,
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.white,
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)).r,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12).r,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
AppTypography(widget.email, type: _medium14),
|
||||
Spacer(),
|
||||
_buildCloseButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCloseButton() {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
// Добавьте логику для смены e-mail или закрытия экрана
|
||||
},
|
||||
child: SizedBox.square(
|
||||
dimension: 24.r,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.bg,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(Icons.close, color: AppColors.disabled, size: 16.r),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInstructions() {
|
||||
return AppTypography(
|
||||
'Ниже введите код, который получили на e-mail',
|
||||
type: _regular14,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPinCode() {
|
||||
return Form(key: _formKey, child: PinCode(pinController: _pinController));
|
||||
}
|
||||
|
||||
Widget _buildResendTimer() {
|
||||
return ResendTimer(onTap: () async {});
|
||||
}
|
||||
|
||||
Widget _buildLoginButton() {
|
||||
return PrimaryButton(
|
||||
onTap: _onEnterTap,
|
||||
child: AppTypography('Войти', type: _medium14, color: AppColors.white),
|
||||
);
|
||||
}
|
||||
}
|
||||
99
lib/screens/auth/screens/email_auth.dart
Normal file
99
lib/screens/auth/screens/email_auth.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:remever/common/resources.dart';
|
||||
import 'package:remever/common/widgets/typography.dart';
|
||||
import 'package:remever/common/widgets/wspace.dart';
|
||||
import 'package:remever/components/extensions/context.dart';
|
||||
import 'package:remever/gen/assets.gen.dart';
|
||||
import 'package:remever/screens/auth/cubit/auth_cubit.dart';
|
||||
import 'package:remever/screens/auth/widgets/auth_text_field.dart';
|
||||
import 'package:remever/widgets/primary_button.dart';
|
||||
|
||||
///
|
||||
/// Отрисовка экрана авторизации по e-mail
|
||||
///
|
||||
class EmailAuth extends StatefulWidget {
|
||||
const EmailAuth({super.key});
|
||||
|
||||
@override
|
||||
EmailAuthState createState() => EmailAuthState();
|
||||
}
|
||||
|
||||
class EmailAuthState extends State<EmailAuth> {
|
||||
final TextEditingController _emailController = TextEditingController();
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
|
||||
// Константы для стилей и отступов
|
||||
static final EdgeInsetsGeometry _padding = EdgeInsets.all(16).r;
|
||||
static final _regular14 = Regular14px();
|
||||
static final _medium14 = Medium14px();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_emailController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onEnterTap() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
await context.read<AuthCubit>().login(_emailController.text);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.bg,
|
||||
body: Padding(
|
||||
padding: _padding,
|
||||
child: SingleChildScrollView(
|
||||
physics: BouncingScrollPhysics(),
|
||||
child: Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
HSpace(148),
|
||||
_buildLogo(),
|
||||
const HSpace(60),
|
||||
_buildEmailField(),
|
||||
const HSpace(24),
|
||||
_buildInstructions(),
|
||||
HSpace(145),
|
||||
_buildLoginButton(),
|
||||
const HSpace(104),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLogo() {
|
||||
return Assets.images.logo.image(width: 308.w, height: 100.h);
|
||||
}
|
||||
|
||||
Widget _buildEmailField() {
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: AuthTextField(email: _emailController, autofocus: true),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInstructions() {
|
||||
return AppTypography(
|
||||
'Для авторизации в приложении мы отправим код на указанный e-mail',
|
||||
type: _regular14,
|
||||
maxLines: 3,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoginButton() {
|
||||
return PrimaryButton(
|
||||
onTap: _onEnterTap,
|
||||
child: AppTypography('Войти', type: _medium14, color: AppColors.white),
|
||||
);
|
||||
}
|
||||
}
|
||||
101
lib/screens/auth/screens/initial_auth.dart
Normal file
101
lib/screens/auth/screens/initial_auth.dart
Normal file
@@ -0,0 +1,101 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
// Project imports:
|
||||
import 'package:remever/common/resources.dart';
|
||||
import 'package:remever/common/widgets/typography.dart';
|
||||
import 'package:remever/common/widgets/wspace.dart';
|
||||
import 'package:remever/components/extensions/context.dart';
|
||||
import 'package:remever/gen/assets.gen.dart';
|
||||
import 'package:remever/screens/auth/cubit/auth_cubit.dart';
|
||||
import 'package:remever/widgets/primary_button.dart';
|
||||
|
||||
///
|
||||
/// Отрисовка инициализации авторизации
|
||||
///
|
||||
class InitialAuth extends StatelessWidget {
|
||||
const InitialAuth({super.key});
|
||||
|
||||
// Константы для стилей и отступов
|
||||
static final EdgeInsetsGeometry _padding = EdgeInsets.all(16).r;
|
||||
static final _regular14 = Regular14px();
|
||||
|
||||
void _onEnterTap(BuildContext context) {
|
||||
context.read<AuthCubit>().toEmailState();
|
||||
}
|
||||
|
||||
void _onPolicyTap() {}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: AppColors.bg,
|
||||
body: Padding(
|
||||
padding: _padding,
|
||||
child: SingleChildScrollView(
|
||||
physics: BouncingScrollPhysics(),
|
||||
child: Expanded(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
HSpace(148),
|
||||
_buildLogo(),
|
||||
_buildQuote(),
|
||||
HSpace(245),
|
||||
_buildLoginButton(context),
|
||||
HSpace(28),
|
||||
_buildPolicyLink(),
|
||||
_buildBottomSpacing(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLogo() {
|
||||
return Assets.images.logo.image(width: 308.w, height: 100.h);
|
||||
}
|
||||
|
||||
Widget _buildQuote() {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
HSpace(16),
|
||||
Assets.images.quote.image(width: 324.w, height: 64.h),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoginButton(BuildContext context) {
|
||||
return PrimaryButton(
|
||||
onTap: () => _onEnterTap(context),
|
||||
child: AppTypography(
|
||||
'Войти по e-mail',
|
||||
type: _regular14,
|
||||
color: AppColors.white,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPolicyLink() {
|
||||
return InkWell(
|
||||
onTap: () => _onPolicyTap(),
|
||||
child: AppTypography.rich(
|
||||
'Отправляя данные, Вы подтверждаете согласие с ',
|
||||
type: _regular14,
|
||||
color: AppColors.disabled,
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: 'Политикой обработки персональных данных',
|
||||
style: _regular14.style.copyWith(color: AppColors.primary),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBottomSpacing() {
|
||||
return HSpace(40);
|
||||
}
|
||||
}
|
||||
64
lib/screens/auth/widgets/auth_text_field.dart
Normal file
64
lib/screens/auth/widgets/auth_text_field.dart
Normal file
@@ -0,0 +1,64 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:remever/common/resources.dart';
|
||||
import 'package:remever/common/typography.dart';
|
||||
import 'package:remever/components/extensions/context.dart';
|
||||
|
||||
class AuthTextField extends StatelessWidget {
|
||||
const AuthTextField({required this.email, super.key, this.autofocus = true});
|
||||
|
||||
final TextEditingController email;
|
||||
final bool autofocus;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final OutlineInputBorder border = OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)).r,
|
||||
borderSide: BorderSide(color: AppColors.gray),
|
||||
);
|
||||
|
||||
return TextFormField(
|
||||
autofocus: autofocus,
|
||||
controller: email,
|
||||
validator: (String? value) {
|
||||
if (value == null || value == '') return 'Поле не может быть пустым';
|
||||
if (!value.contains('@') || !value.contains('.')) {
|
||||
return 'Неверный e-mail';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
cursorColor: AppColors.primary,
|
||||
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: AppColors.white,
|
||||
focusedBorder: border,
|
||||
enabledBorder: border,
|
||||
errorBorder: border,
|
||||
focusedErrorBorder: border,
|
||||
hintText: 'Введите e-mail',
|
||||
hintStyle: const TextStyle(fontWeight: FontWeight.w400, height: 1.2),
|
||||
errorStyle: SemiBold12px().style.copyWith(color: AppColors.red),
|
||||
suffixIconConstraints: const BoxConstraints(minWidth: 0, minHeight: 0),
|
||||
suffixIcon: Padding(
|
||||
padding: const EdgeInsets.only(right: 12).r,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
email.clear();
|
||||
},
|
||||
child: SizedBox.square(
|
||||
dimension: 24.r,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.bg,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(Icons.close, color: AppColors.disabled, size: 16.r),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
50
lib/screens/auth/widgets/pin_code.dart
Normal file
50
lib/screens/auth/widgets/pin_code.dart
Normal file
@@ -0,0 +1,50 @@
|
||||
// Flutter imports:
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pin_code_fields/pin_code_fields.dart';
|
||||
import 'package:remever/common/resources.dart';
|
||||
import 'package:remever/components/extensions/context.dart';
|
||||
|
||||
class PinCode extends StatefulWidget {
|
||||
const PinCode({super.key, required this.pinController});
|
||||
|
||||
final TextEditingController pinController;
|
||||
|
||||
@override
|
||||
State<PinCode> createState() => _PinCodeState();
|
||||
}
|
||||
|
||||
class _PinCodeState extends State<PinCode> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PinCodeTextField(
|
||||
controller: widget.pinController,
|
||||
appContext: context,
|
||||
length: 6,
|
||||
keyboardType: TextInputType.number,
|
||||
animationType: AnimationType.slide,
|
||||
hapticFeedbackTypes: HapticFeedbackTypes.medium,
|
||||
autoFocus: true,
|
||||
enableActiveFill: true,
|
||||
cursorColor: AppColors.primary,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) return 'Поле не может быть пустым';
|
||||
if (value.length < 5) return 'Слишком мало символов';
|
||||
return null;
|
||||
},
|
||||
pinTheme: PinTheme(
|
||||
fieldWidth: 48.w,
|
||||
fieldHeight: 62.h,
|
||||
borderRadius: BorderRadius.circular(8).r,
|
||||
activeFillColor: AppColors.white,
|
||||
selectedFillColor: AppColors.white,
|
||||
inactiveColor: AppColors.white,
|
||||
|
||||
selectedColor: AppColors.white,
|
||||
activeColor: AppColors.white,
|
||||
shape: PinCodeFieldShape.box,
|
||||
inactiveFillColor: AppColors.white,
|
||||
),
|
||||
onCompleted: (String value) async {},
|
||||
);
|
||||
}
|
||||
}
|
||||
95
lib/screens/auth/widgets/timer.dart
Normal file
95
lib/screens/auth/widgets/timer.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
// Dart imports:
|
||||
import 'dart:async';
|
||||
|
||||
// Flutter imports:
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Package imports:
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:remever/common/resources.dart';
|
||||
import 'package:remever/common/widgets/typography.dart';
|
||||
import 'package:remever/common/widgets/w_if.dart';
|
||||
import 'package:remever/common/widgets/wspace.dart';
|
||||
import 'package:remever/components/extensions/context.dart';
|
||||
import 'package:remever/components/extensions/state.dart';
|
||||
|
||||
class ResendTimer extends StatefulWidget {
|
||||
const ResendTimer({required this.onTap, super.key});
|
||||
|
||||
final Function() onTap;
|
||||
|
||||
@override
|
||||
State<ResendTimer> createState() => _ResendTimerState();
|
||||
}
|
||||
|
||||
class _ResendTimerState extends State<ResendTimer> {
|
||||
Timer? _timer;
|
||||
int _start = 60;
|
||||
|
||||
void _startTimer() {
|
||||
const Duration oneSec = Duration(seconds: 1);
|
||||
_timer = Timer.periodic(oneSec, (Timer timer) {
|
||||
if (_start > 0) {
|
||||
safeSetState(() {
|
||||
_start--;
|
||||
});
|
||||
} else {
|
||||
_timer?.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_startTimer();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Wif(
|
||||
condition: _start == 0,
|
||||
builder: (BuildContext context) => _buildResend(),
|
||||
fallback: (BuildContext context) => _buildTimer(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildResend() {
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
await widget.onTap();
|
||||
safeSetState(() => _start = 60);
|
||||
_startTimer();
|
||||
},
|
||||
child: AppTypography(
|
||||
'Получить новый код на e-mail',
|
||||
type: Regular14px(),
|
||||
color: AppColors.primary,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTimer() {
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
AppTypography(
|
||||
'Получить новый код можно будет через: ',
|
||||
type: Regular14px(),
|
||||
color: AppColors.disabled,
|
||||
),
|
||||
AppTypography(
|
||||
'$_start сек',
|
||||
type: Regular14px(),
|
||||
color: AppColors.disabled,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user