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

View File

@@ -0,0 +1,119 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get_it_mixin/get_it_mixin.dart';
import 'package:remever/common/resources.dart';
import 'package:remever/components/extensions/context.dart';
import 'package:remever/components/notifiers/home_screen_data.dart';
import 'package:remever/screens/collections/cubit/collection_cubit.dart';
@RoutePage()
class CollectionScreen extends StatelessWidget with GetItMixin {
CollectionScreen({super.key});
/// Флаг что надо показывать Fab
bool get _showFab {
return watchOnly<CollectionData, bool>((CollectionData d) => d.showFAB);
}
@override
Widget build(BuildContext context) {
return BlocProvider<CollectionCubit>(
create: (context) => CollectionCubit(),
child: Scaffold(
backgroundColor: AppColors.bg,
// appBar: const CollectionsAppBar(),
body: _buildMain(context),
floatingActionButton: Builder(
builder: (BuildContext context) {
return AnimatedOpacity(
opacity: _showFab ? 1.0 : 0.0,
duration: const Duration(milliseconds: 200),
child: FloatingActionButton(
backgroundColor: AppColors.primary,
onPressed: () {},
// context.read<HomeCubit>().toCrudCollection(CrudType.CREATE),
child: const Icon(Icons.add),
),
);
},
),
),
);
}
///
/// Построение основго экрана
///
Widget _buildMain(BuildContext context) {
return BlocBuilder<CollectionCubit, CollectionState>(
builder: (context, state) {
return state.when(
loading: () => _LoadingList(),
data:
() => const Column(
children: <Widget>[
// CollectionsFilters(),
_CollectionList(),
],
),
empty: () => _EmptyList(),
error: () => _ErrorList(),
);
},
);
}
}
class _LoadingList extends StatelessWidget {
const _LoadingList();
@override
Widget build(BuildContext context) {
return const Placeholder(color: Colors.green);
}
}
class _ErrorList extends StatelessWidget {
const _ErrorList();
@override
Widget build(BuildContext context) {
return const Placeholder(color: Colors.brown);
}
}
class _EmptyList extends StatelessWidget {
const _EmptyList();
@override
Widget build(BuildContext context) {
return const Placeholder(color: Colors.red);
}
}
class _CollectionList extends StatelessWidget {
const _CollectionList();
// @override
@override
Widget build(BuildContext context) {
print('build _CollectionList');
final CollectionCubit collectionCubit = context.read<CollectionCubit>();
collectionCubit.initScrollListener();
return Expanded(
child: ListView.builder(
controller: collectionCubit.collectionController,
itemCount: 20,
padding: const EdgeInsets.symmetric(horizontal: 16).r,
itemBuilder:
(BuildContext context, int index) => Padding(
padding: const EdgeInsets.only(bottom: 8).r,
// child: const CollectionCard(),
),
),
);
}
}

View File

@@ -0,0 +1,61 @@
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:remever/components/notifiers/home_screen_data.dart';
import 'package:remever/inject.dart';
part 'collection_state.dart';
part 'collection_cubit.freezed.dart';
class CollectionCubit extends Cubit<CollectionState> {
CollectionCubit() : super(CollectionState.loading());
/// Нотифаер домашнего экрана
CollectionData get _cd => getIt<CollectionData>();
/// Контроллер скролла для коллекции
final ScrollController collectionController = ScrollController();
/// Позиция скролле
double _previousScrollOffset = 0.0;
/// Индекс выбранной фильтрации коллекции
int collectionFiltersIndex = 0;
void initScrollListener() {
collectionController.addListener(() {
final double currentScrollOffset = collectionController.offset;
final bool isScrollingDown = currentScrollOffset > _previousScrollOffset;
if (isScrollingDown) {
_cd.showFab(false);
} else {
_cd.showFab(true);
}
_previousScrollOffset = currentScrollOffset;
});
}
@override
Future<void> close() {
collectionController.dispose();
return super.close();
}
Future<void> toLoadingState() async {
emit(CollectionState.loading());
}
Future<void> toDataState() async {
emit(CollectionState.data());
}
Future<void> toEmptyState() async {
emit(CollectionState.empty());
}
Future<void> toErrorState() async {
emit(CollectionState.error());
}
}

View File

@@ -0,0 +1,560 @@
// 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 'collection_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 _$CollectionState {
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() loading,
required TResult Function() data,
required TResult Function() empty,
required TResult Function() error,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? data,
TResult? Function()? empty,
TResult? Function()? error,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? data,
TResult Function()? empty,
TResult Function()? error,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Loading value) loading,
required TResult Function(_Data value) data,
required TResult Function(_Empty value) empty,
required TResult Function(_Error value) error,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Loading value)? loading,
TResult? Function(_Data value)? data,
TResult? Function(_Empty value)? empty,
TResult? Function(_Error value)? error,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Loading value)? loading,
TResult Function(_Data value)? data,
TResult Function(_Empty value)? empty,
TResult Function(_Error value)? error,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $CollectionStateCopyWith<$Res> {
factory $CollectionStateCopyWith(
CollectionState value,
$Res Function(CollectionState) then,
) = _$CollectionStateCopyWithImpl<$Res, CollectionState>;
}
/// @nodoc
class _$CollectionStateCopyWithImpl<$Res, $Val extends CollectionState>
implements $CollectionStateCopyWith<$Res> {
_$CollectionStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of CollectionState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
abstract class _$$LoadingImplCopyWith<$Res> {
factory _$$LoadingImplCopyWith(
_$LoadingImpl value,
$Res Function(_$LoadingImpl) then,
) = __$$LoadingImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$LoadingImplCopyWithImpl<$Res>
extends _$CollectionStateCopyWithImpl<$Res, _$LoadingImpl>
implements _$$LoadingImplCopyWith<$Res> {
__$$LoadingImplCopyWithImpl(
_$LoadingImpl _value,
$Res Function(_$LoadingImpl) _then,
) : super(_value, _then);
/// Create a copy of CollectionState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$LoadingImpl implements _Loading {
const _$LoadingImpl();
@override
String toString() {
return 'CollectionState.loading()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$LoadingImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() loading,
required TResult Function() data,
required TResult Function() empty,
required TResult Function() error,
}) {
return loading();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? data,
TResult? Function()? empty,
TResult? Function()? error,
}) {
return loading?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? data,
TResult Function()? empty,
TResult Function()? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Loading value) loading,
required TResult Function(_Data value) data,
required TResult Function(_Empty value) empty,
required TResult Function(_Error value) error,
}) {
return loading(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Loading value)? loading,
TResult? Function(_Data value)? data,
TResult? Function(_Empty value)? empty,
TResult? Function(_Error value)? error,
}) {
return loading?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Loading value)? loading,
TResult Function(_Data value)? data,
TResult Function(_Empty value)? empty,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (loading != null) {
return loading(this);
}
return orElse();
}
}
abstract class _Loading implements CollectionState {
const factory _Loading() = _$LoadingImpl;
}
/// @nodoc
abstract class _$$DataImplCopyWith<$Res> {
factory _$$DataImplCopyWith(
_$DataImpl value,
$Res Function(_$DataImpl) then,
) = __$$DataImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$DataImplCopyWithImpl<$Res>
extends _$CollectionStateCopyWithImpl<$Res, _$DataImpl>
implements _$$DataImplCopyWith<$Res> {
__$$DataImplCopyWithImpl(_$DataImpl _value, $Res Function(_$DataImpl) _then)
: super(_value, _then);
/// Create a copy of CollectionState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$DataImpl implements _Data {
const _$DataImpl();
@override
String toString() {
return 'CollectionState.data()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$DataImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() loading,
required TResult Function() data,
required TResult Function() empty,
required TResult Function() error,
}) {
return data();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? data,
TResult? Function()? empty,
TResult? Function()? error,
}) {
return data?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? data,
TResult Function()? empty,
TResult Function()? error,
required TResult orElse(),
}) {
if (data != null) {
return data();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Loading value) loading,
required TResult Function(_Data value) data,
required TResult Function(_Empty value) empty,
required TResult Function(_Error value) error,
}) {
return data(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Loading value)? loading,
TResult? Function(_Data value)? data,
TResult? Function(_Empty value)? empty,
TResult? Function(_Error value)? error,
}) {
return data?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Loading value)? loading,
TResult Function(_Data value)? data,
TResult Function(_Empty value)? empty,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (data != null) {
return data(this);
}
return orElse();
}
}
abstract class _Data implements CollectionState {
const factory _Data() = _$DataImpl;
}
/// @nodoc
abstract class _$$EmptyImplCopyWith<$Res> {
factory _$$EmptyImplCopyWith(
_$EmptyImpl value,
$Res Function(_$EmptyImpl) then,
) = __$$EmptyImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$EmptyImplCopyWithImpl<$Res>
extends _$CollectionStateCopyWithImpl<$Res, _$EmptyImpl>
implements _$$EmptyImplCopyWith<$Res> {
__$$EmptyImplCopyWithImpl(
_$EmptyImpl _value,
$Res Function(_$EmptyImpl) _then,
) : super(_value, _then);
/// Create a copy of CollectionState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$EmptyImpl implements _Empty {
const _$EmptyImpl();
@override
String toString() {
return 'CollectionState.empty()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$EmptyImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() loading,
required TResult Function() data,
required TResult Function() empty,
required TResult Function() error,
}) {
return empty();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? data,
TResult? Function()? empty,
TResult? Function()? error,
}) {
return empty?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? data,
TResult Function()? empty,
TResult Function()? error,
required TResult orElse(),
}) {
if (empty != null) {
return empty();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Loading value) loading,
required TResult Function(_Data value) data,
required TResult Function(_Empty value) empty,
required TResult Function(_Error value) error,
}) {
return empty(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Loading value)? loading,
TResult? Function(_Data value)? data,
TResult? Function(_Empty value)? empty,
TResult? Function(_Error value)? error,
}) {
return empty?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Loading value)? loading,
TResult Function(_Data value)? data,
TResult Function(_Empty value)? empty,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (empty != null) {
return empty(this);
}
return orElse();
}
}
abstract class _Empty implements CollectionState {
const factory _Empty() = _$EmptyImpl;
}
/// @nodoc
abstract class _$$ErrorImplCopyWith<$Res> {
factory _$$ErrorImplCopyWith(
_$ErrorImpl value,
$Res Function(_$ErrorImpl) then,
) = __$$ErrorImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$ErrorImplCopyWithImpl<$Res>
extends _$CollectionStateCopyWithImpl<$Res, _$ErrorImpl>
implements _$$ErrorImplCopyWith<$Res> {
__$$ErrorImplCopyWithImpl(
_$ErrorImpl _value,
$Res Function(_$ErrorImpl) _then,
) : super(_value, _then);
/// Create a copy of CollectionState
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$ErrorImpl implements _Error {
const _$ErrorImpl();
@override
String toString() {
return 'CollectionState.error()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$ErrorImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() loading,
required TResult Function() data,
required TResult Function() empty,
required TResult Function() error,
}) {
return error();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? loading,
TResult? Function()? data,
TResult? Function()? empty,
TResult? Function()? error,
}) {
return error?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? loading,
TResult Function()? data,
TResult Function()? empty,
TResult Function()? error,
required TResult orElse(),
}) {
if (error != null) {
return error();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Loading value) loading,
required TResult Function(_Data value) data,
required TResult Function(_Empty value) empty,
required TResult Function(_Error value) error,
}) {
return error(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Loading value)? loading,
TResult? Function(_Data value)? data,
TResult? Function(_Empty value)? empty,
TResult? Function(_Error value)? error,
}) {
return error?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Loading value)? loading,
TResult Function(_Data value)? data,
TResult Function(_Empty value)? empty,
TResult Function(_Error value)? error,
required TResult orElse(),
}) {
if (error != null) {
return error(this);
}
return orElse();
}
}
abstract class _Error implements CollectionState {
const factory _Error() = _$ErrorImpl;
}

View File

@@ -0,0 +1,9 @@
part of 'collection_cubit.dart';
@freezed
class CollectionState with _$CollectionState {
const factory CollectionState.loading() = _Loading;
const factory CollectionState.data() = _Data;
const factory CollectionState.empty() = _Empty;
const factory CollectionState.error() = _Error;
}

View File

@@ -0,0 +1,87 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:remever/common/functions.dart';
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/router.gr.dart';
import 'package:remever/screens/dialogs/filters_dialog.dart';
import 'package:remever/screens/home/home_screen.dart';
class CollectionsAppBar extends StatelessWidget implements PreferredSizeWidget {
const CollectionsAppBar({super.key});
@override
Size get preferredSize => Size.fromHeight(66.h);
@override
Widget build(BuildContext context) {
return AppBar(
toolbarHeight: 66.h,
backgroundColor: AppColors.white,
shadowColor: Colors.transparent,
title: Row(
children: <Widget>[
GestureDetector(
// onLongPress: () => context.pushRoute(const SandboxRoute()),
child: AppTypography(
'Коллекции',
type: SemiBold28px(),
color: AppColors.body_text,
),
),
const WSpace(2),
Container(
height: 22.h,
width: 38.w,
decoration: BoxDecoration(
color: AppColors.secondary,
borderRadius: BorderRadius.circular(40).r,
),
child: Center(
child: AppTypography(
'2213',
type: Regular12px(),
color: AppColors.body_text,
),
),
),
],
),
actions: <Widget>[
AppBarIconButton(icon: Assets.icons.typeSearch, onTap: () {}),
AppBarIconButton(icon: Assets.icons.typeDownload, onTap: () {}),
AppBarIconButton(
icon: Assets.icons.typeSort,
onTap: () {
showCuperModalBottomSheet(
context: context,
height: 424.h,
builder: (BuildContext context) => const FiltersDialog(),
);
},
),
],
);
}
}
class AppBarIconButton extends StatelessWidget {
const AppBarIconButton({required this.icon, required this.onTap, super.key});
final AssetGenImage icon;
final void Function()? onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: SizedBox(
width: 48.h,
child: Center(child: icon.image(height: 24.h, width: 24.w)),
),
);
}
}

View File

@@ -0,0 +1,12 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class CrudCollection extends StatelessWidget {
const CrudCollection({super.key});
@override
Widget build(BuildContext context) {
return const Placeholder(color: Colors.blue);
}
}

View File

@@ -0,0 +1,76 @@
// Flutter imports:
import 'package:flutter/material.dart';
// Project imports:
import 'package:remever/common/resources.dart';
import 'package:remever/common/widgets/typography.dart';
import 'package:remever/components/extensions/context.dart';
import 'package:remever/gen/assets.gen.dart';
class DialogHeader extends StatelessWidget {
const DialogHeader({
super.key,
this.title = '',
this.paddingSize = 28,
this.action,
});
final String title;
final double paddingSize;
final Widget? action;
// Константы для стилей и отступов
static final double _headerHeight = 56.h;
static const BoxDecoration _headerDecoration = BoxDecoration(
border: Border(bottom: BorderSide(color: AppColors.gray, width: 0.5)),
);
static final _medium16Style = Medium16px();
static final double _iconSize = 24.r;
static const BoxDecoration _closeButtonDecoration = BoxDecoration(
shape: BoxShape.circle,
color: AppColors.bg,
);
@override
Widget build(BuildContext context) {
return Container(
height: _headerHeight.h,
decoration: _headerDecoration,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: paddingSize).r,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
_buildTitle(),
if (action != null) action!,
_buildCloseButton(context),
],
),
),
);
}
/// Построение заголовка диалога
Widget _buildTitle() {
return AppTypography(title, type: _medium16Style);
}
/// Построение кнопки закрытия диалога
Widget _buildCloseButton(BuildContext context) {
return GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
height: _iconSize.h,
width: _iconSize.w,
decoration: _closeButtonDecoration,
child: Center(
child: Assets.icons.typeClose.image(
color: AppColors.gray,
height: _iconSize.h,
width: _iconSize.w,
),
),
),
);
}
}

View File

@@ -0,0 +1,69 @@
// Flutter imports:
import 'package:flutter/material.dart';
// Project imports:
import 'package:remever/common/resources.dart';
import 'package:remever/common/widgets/typography.dart';
import 'package:remever/components/extensions/context.dart';
class DialogItem extends StatelessWidget {
const DialogItem({
super.key,
required this.onTap,
this.child,
this.title = '',
this.dimension = 24,
this.color,
});
final VoidCallback? onTap;
final Widget? child;
final String title;
final Color? color;
final double dimension;
// Константы для стилей и отступов
static final double _itemHeight = 56.h;
static const BoxDecoration _itemDecoration = BoxDecoration(
border: Border(bottom: BorderSide(color: AppColors.gray, width: 0.5)),
);
static final EdgeInsetsGeometry _itemPadding =
EdgeInsets.symmetric(horizontal: 28).r;
static final EdgeInsetsGeometry _iconPadding = EdgeInsets.only(right: 8).r;
static final _regular17Style = Regular17px();
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
height: _itemHeight.h,
decoration: _itemDecoration,
child: Padding(
padding: _itemPadding,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[_buildIcon(), _buildTitle()],
),
),
),
);
}
/// Построение иконки
Widget _buildIcon() {
return Padding(
padding: _iconPadding,
child: SizedBox.square(dimension: dimension.r, child: child),
);
}
/// Построение заголовка
Widget _buildTitle() {
return AppTypography(
title,
color: color ?? AppColors.black,
type: _regular17Style,
);
}
}

View File

@@ -0,0 +1,61 @@
import 'package:flutter/material.dart';
import 'package:remever/common/widgets/bottom_safe_space.dart';
import 'package:remever/gen/assets.gen.dart';
import 'package:remever/screens/dialogs/dialog_header.dart';
import 'package:remever/screens/dialogs/dialog_item.dart';
class FiltersDialog extends StatelessWidget {
const FiltersDialog({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
const DialogHeader(title: 'Сортировка'),
DialogItem(
title: 'Сначала Мои коллекции',
child: Assets.icons.typeSortA.image(),
onTap: () {
Navigator.pop(context);
},
),
DialogItem(
title: 'Сначала коллекции на изучении',
child: Assets.icons.typeSortA.image(),
onTap: () {
Navigator.pop(context);
},
),
DialogItem(
title: 'По дате обновления',
child: Assets.icons.typeSortDown.image(),
onTap: () {
Navigator.pop(context);
},
),
DialogItem(
title: 'По уровню изученности',
child: Assets.icons.typeSortDown.image(),
onTap: () {
Navigator.pop(context);
},
),
DialogItem(
title: 'По популярностии',
child: Assets.icons.typeSortDown.image(),
onTap: () {
Navigator.pop(context);
},
),
DialogItem(
title: 'По количеству карточек',
child: Assets.icons.typeSortDown.image(),
onTap: () {
Navigator.pop(context);
},
),
const BottomSafeSpace(),
],
);
}
}

View File

@@ -0,0 +1,136 @@
// Flutter imports:
import 'package:flutter/material.dart';
// Package imports:
import 'package:auto_route/auto_route.dart';
import 'package:remever/common/resources.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/router.gr.dart';
@RoutePage()
class HomeScreen extends StatelessWidget {
///
/// Основной экран на котором расположен бар навигации
///
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
print('build home screen');
return SafeArea(
top: false,
child: AutoTabsScaffold(
routes: <PageRouteInfo>[
SettingsRoute(),
StatistickRoute(),
CrudCollection(),
CollectionRoute(),
],
bottomNavigationBuilder: (_, TabsRouter tabsRouter) {
return SizedBox(
height: 73.h,
child: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
_buildBackgroundBar(tabsRouter),
_buildCentralButton(),
],
),
);
},
),
);
}
/// Построение заднего фона и кнопок нижнего бара
Widget _buildBackgroundBar(TabsRouter tabsRouter) {
return Container(
height: 64.h,
color: AppColors.white,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32).r,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
_BottomBarIcon(
icon: Assets.icons.typeSetting,
isActive: tabsRouter.activeIndex == 0,
onTap: () => tabsRouter.setActiveIndex(0),
),
_BottomBarIcon(
icon: Assets.icons.typeStat,
isActive: tabsRouter.activeIndex == 1,
onTap: () => tabsRouter.setActiveIndex(1),
),
const WSpace(60),
_BottomBarIcon(
icon: Assets.icons.typeCreateCard,
isActive: tabsRouter.activeIndex == 2,
onTap: () => tabsRouter.setActiveIndex(2),
),
_BottomBarIcon(
icon: Assets.icons.typeCollection,
isActive: tabsRouter.activeIndex == 3,
onTap: () => tabsRouter.setActiveIndex(3),
),
],
),
),
);
}
/// Построение центральной кнопки
Widget _buildCentralButton() {
return Align(
alignment: Alignment.topCenter,
child: GestureDetector(
onTap: () {
// Логика нажатия на центральную кнопку
},
child: SizedBox.square(
dimension: 60.r,
child: DecoratedBox(
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: AppColors.primary,
),
child: Center(
child: Assets.icons.typeLearn.image(
color: AppColors.white,
height: 24.h,
width: 24.w,
),
),
),
),
),
);
}
}
class _BottomBarIcon extends StatelessWidget {
final AssetGenImage icon;
final bool isActive;
final VoidCallback onTap;
const _BottomBarIcon({
required this.icon,
required this.isActive,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: icon.image(
height: 24.h,
width: 24.w,
color: isActive ? AppColors.primary : Colors.black,
),
);
}
}

View File

@@ -0,0 +1,12 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class SettingsScreen extends StatelessWidget {
const SettingsScreen({super.key});
@override
Widget build(BuildContext context) {
return const Placeholder(color: Colors.red);
}
}

View File

@@ -0,0 +1,78 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:remever/common/resources.dart';
import 'package:remever/common/widgets/wspace.dart';
import 'package:remever/components/extensions/context.dart';
import 'package:remever/components/extensions/state.dart';
import 'package:remever/gen/assets.gen.dart';
import 'package:remever/router.gr.dart';
import 'package:remever/services/auth_interface.dart';
@RoutePage()
class SplashScreen extends StatefulWidget {
///
/// Стартовый экран
///
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
///
/// Состояние экрана
///
class _SplashScreenState extends State<SplashScreen> {
/// Сервисы
final AuthInterface _authIf = GetIt.I.get<AuthInterface>();
/// Запуск анимации лого
bool _launchLogo = false;
///
/// Навигация на основной экран
///
void _navigate() async {
// context.replaceRoute(const HomeRoute());
// return;
final bool isAuth = await _authIf.isAuth;
if (!mounted) return;
context.replaceRoute(isAuth ? CollectionRoute() : const AuthRoute());
}
@override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) {
Future<void>.delayed(const Duration(milliseconds: 900), () {
safeSetState(() => _launchLogo = !_launchLogo);
});
Future<void>.delayed(const Duration(seconds: 4), _navigate);
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.bg,
body: SafeArea(
child: Column(
children: <Widget>[
HSpace(148),
AnimatedOpacity(
opacity: _launchLogo ? 1 : 0,
duration: const Duration(seconds: 3),
child: Assets.images.logo.image(width: 308.w, height: 100.h),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,12 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
@RoutePage()
class StatistickScreen extends StatelessWidget {
const StatistickScreen({super.key});
@override
Widget build(BuildContext context) {
return const Placeholder(color: Colors.orange);
}
}