first commit

This commit is contained in:
2025-03-03 20:59:42 +03:00
commit 273e68557a
1099 changed files with 17880 additions and 0 deletions

View 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),
),
),
);
}
}

View 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());
}
}

View 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;
}

View 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;
}

View 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),
);
}
}

View 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),
);
}
}

View 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);
}
}

View 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),
),
),
),
),
),
);
}
}

View 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 {},
);
}
}

View 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,
),
],
);
}
}