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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
119
lib/screens/collections/collections_screen.dart
Normal file
119
lib/screens/collections/collections_screen.dart
Normal 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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
61
lib/screens/collections/cubit/collection_cubit.dart
Normal file
61
lib/screens/collections/cubit/collection_cubit.dart
Normal 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());
|
||||
}
|
||||
}
|
||||
560
lib/screens/collections/cubit/collection_cubit.freezed.dart
Normal file
560
lib/screens/collections/cubit/collection_cubit.freezed.dart
Normal 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;
|
||||
}
|
||||
9
lib/screens/collections/cubit/collection_state.dart
Normal file
9
lib/screens/collections/cubit/collection_state.dart
Normal 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;
|
||||
}
|
||||
87
lib/screens/collections/widgets/collections_app_bar.dart
Normal file
87
lib/screens/collections/widgets/collections_app_bar.dart
Normal 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)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
12
lib/screens/crud_collection/crud_collection.dart
Normal file
12
lib/screens/crud_collection/crud_collection.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
76
lib/screens/dialogs/dialog_header.dart
Normal file
76
lib/screens/dialogs/dialog_header.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
69
lib/screens/dialogs/dialog_item.dart
Normal file
69
lib/screens/dialogs/dialog_item.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
61
lib/screens/dialogs/filters_dialog.dart
Normal file
61
lib/screens/dialogs/filters_dialog.dart
Normal 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(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
136
lib/screens/home/home_screen.dart
Normal file
136
lib/screens/home/home_screen.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
12
lib/screens/settings/settings_screen.dart
Normal file
12
lib/screens/settings/settings_screen.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
78
lib/screens/splash/splash_screen.dart
Normal file
78
lib/screens/splash/splash_screen.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
12
lib/screens/statistick/statistick_screen.dart
Normal file
12
lib/screens/statistick/statistick_screen.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user